diff --git a/ImportScolars.py b/ImportScolars.py new file mode 100644 index 00000000..c819551e --- /dev/null +++ b/ImportScolars.py @@ -0,0 +1,781 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +""" Importation des etudiants à partir de fichiers CSV +""" + +import os, sys, time, pdb + +from sco_utils import * +from notesdb import * +from notes_log import log +import scolars +import sco_formsemestre +import sco_groups +import sco_excel +import sco_groups_view +import sco_news +from sco_news import NEWS_INSCR, NEWS_NOTE, NEWS_FORM, NEWS_SEM, NEWS_MISC +from sco_formsemestre_inscriptions import do_formsemestre_inscription_with_modules +from gen_tables import GenTable + +# format description (relative to Product directory)) +FORMAT_FILE = "misc/format_import_etudiants.txt" + +# Champs modifiables via "Import données admission" +ADMISSION_MODIFIABLE_FIELDS = ( + "code_nip", + "code_ine", + "date_naissance", + "lieu_naissance", + "bac", + "specialite", + "annee_bac", + "math", + "physique", + "anglais", + "francais", + "type_admission", + "boursier_prec", + "qualite", + "rapporteur", + "score", + "commentaire", + "classement", + "apb_groupe", + "apb_classement_gr", + "nomlycee", + "villelycee", + "codepostallycee", + "codelycee", + # Adresse: + "email", + "emailperso", + "domicile", + "codepostaldomicile", + "villedomicile", + "paysdomicile", + "telephone", + "telephonemobile", + # Debouche + "debouche", + # Groupes + "groupes", +) + +# ---- + + +def sco_import_format(with_codesemestre=True): + "returns tuples (Attribut, Type, Table, AllowNulls, Description)" + r = [] + for l in open(SCO_SRCDIR + "/" + FORMAT_FILE): + l = l.strip() + if l and l[0] != "#": + fs = l.split(";") + if len(fs) < 5: + # Bug: invalid format file (fatal) + raise ScoException( + "file %s has invalid format (expected %d fields, got %d) (%s)" + % (FORMAT_FILE, 5, len(fs), l) + ) + fieldname = ( + fs[0].strip().lower().split()[0] + ) # titre attribut: normalize, 1er mot seulement (nom du champ en BD) + typ, table, allow_nulls, description = [x.strip() for x in fs[1:5]] + aliases = [x.strip() for x in fs[5:] if x.strip()] + if fieldname not in aliases: + aliases.insert(0, fieldname) # prepend + if with_codesemestre or fs[0] != "codesemestre": + r.append((fieldname, typ, table, allow_nulls, description, aliases)) + return r + + +def sco_import_format_dict(with_codesemestre=True): + """ Attribut: { 'type': , 'table', 'allow_nulls' , 'description' } + """ + fmt = sco_import_format(with_codesemestre=with_codesemestre) + R = collections.OrderedDict() + for l in fmt: + R[l[0]] = { + "type": l[1], + "table": l[2], + "allow_nulls": l[3], + "description": l[4], + "aliases": l[5], + } + return R + + +def sco_import_generate_excel_sample( + fmt, + with_codesemestre=True, + only_tables=None, + with_groups=True, + exclude_cols=[], + extra_cols=[], + group_ids=[], + context=None, + REQUEST=None, +): + """Generates an excel document based on format fmt + (format is the result of sco_import_format()) + If not None, only_tables can specify a list of sql table names + (only columns from these tables will be generated) + If group_ids, liste les etudiants de ces groupes + """ + style = sco_excel.Excel_MakeStyle(bold=True) + style_required = sco_excel.Excel_MakeStyle(bold=True, color="red") + titles = [] + titlesStyles = [] + for l in fmt: + name = strlower(l[0]) + if (not with_codesemestre) and name == "codesemestre": + continue # pas de colonne codesemestre + if only_tables is not None and strlower(l[2]) not in only_tables: + continue # table non demandée + if name in exclude_cols: + continue # colonne exclue + if int(l[3]): + titlesStyles.append(style) + else: + titlesStyles.append(style_required) + titles.append(name) + if with_groups and "groupes" not in titles: + titles.append("groupes") + titlesStyles.append(style) + titles += extra_cols + titlesStyles += [style] * len(extra_cols) + if group_ids and context: + groups_infos = sco_groups_view.DisplayedGroupsInfos( + context, group_ids, REQUEST=REQUEST + ) + members = groups_infos.members + log( + "sco_import_generate_excel_sample: group_ids=%s %d members" + % (group_ids, len(members)) + ) + titles = ["etudid"] + titles + titlesStyles = [style] + titlesStyles + # rempli table avec données actuelles + lines = [] + for i in members: + etud = context.getEtudInfo(etudid=i["etudid"], filled=True)[0] + l = [] + for field in titles: + if field == "groupes": + sco_groups.etud_add_group_infos( + context, etud, groups_infos.formsemestre, sep=";" + ) + l.append(etud["partitionsgroupes"]) + else: + key = strlower(field).split()[0] + l.append(etud.get(key, "")) + lines.append(l) + else: + lines = [[]] # empty content, titles only + return sco_excel.Excel_SimpleTable( + titles=titles, titlesStyles=titlesStyles, SheetName="Etudiants", lines=lines + ) + + +def students_import_excel( + context, + csvfile, + REQUEST=None, + formsemestre_id=None, + check_homonyms=True, + require_ine=False, +): + "import students from Excel file" + diag = scolars_import_excel_file( + csvfile, + context.Notes, + REQUEST, + formsemestre_id=formsemestre_id, + check_homonyms=check_homonyms, + require_ine=require_ine, + exclude_cols=["photo_filename"], + ) + if REQUEST: + if formsemestre_id: + dest = "formsemestre_status?formsemestre_id=%s" % formsemestre_id + else: + dest = REQUEST.URL1 + H = [context.sco_header(REQUEST, page_title="Import etudiants")] + H.append("") + H.append("

Import terminé !

") + H.append('

Continuer

' % dest) + return "\n".join(H) + context.sco_footer(REQUEST) + + +def scolars_import_excel_file( + datafile, + context, + REQUEST, + formsemestre_id=None, + check_homonyms=True, + require_ine=False, + exclude_cols=[], +): + """Importe etudiants depuis fichier Excel + et les inscrit dans le semestre indiqué (et à TOUS ses modules) + """ + log("scolars_import_excel_file: formsemestre_id=%s" % formsemestre_id) + cnx = context.GetDBConnexion(autocommit=False) + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + annee_courante = time.localtime()[0] + always_require_ine = context.get_preference("always_require_ine") + exceldata = datafile.read() + if not exceldata: + raise ScoValueError("Ficher excel vide ou invalide") + diag, data = sco_excel.Excel_to_list(exceldata) + if not data: # probably a bug + raise ScoException("scolars_import_excel_file: empty file !") + + formsemestre_to_invalidate = Set() + + # 1- --- check title line + titles = {} + fmt = sco_import_format() + for l in fmt: + tit = strlower(l[0]).split()[0] # titles in lowercase, and take 1st word + if ( + (not formsemestre_id) or (tit != "codesemestre") + ) and tit not in exclude_cols: + titles[tit] = l[1:] # title : (Type, Table, AllowNulls, Description) + + # log("titles=%s" % titles) + # remove quotes, downcase and keep only 1st word + try: + fs = [strlower(stripquotes(s)).split()[0] for s in data[0]] + except: + raise ScoValueError("Titres de colonnes invalides (ou vides ?)") + # log("excel: fs='%s'\ndata=%s" % (str(fs), str(data))) + + # check columns titles + if len(fs) != len(titles): + missing = {}.fromkeys(titles.keys()) + unknown = [] + for f in fs: + if missing.has_key(f): + del missing[f] + else: + unknown.append(f) + raise ScoValueError( + "Nombre de colonnes incorrect (devrait être %d, et non %d)
(colonnes manquantes: %s, colonnes invalides: %s)" + % (len(titles), len(fs), missing.keys(), unknown) + ) + titleslist = [] + for t in fs: + if not titles.has_key(t): + raise ScoValueError('Colonne invalide: "%s"' % t) + titleslist.append(t) # + # ok, same titles + # Start inserting data, abort whole transaction in case of error + created_etudids = [] + NbImportedHomonyms = 0 + GroupIdInferers = {} + try: # --- begin DB transaction + linenum = 0 + for line in data[1:]: + linenum += 1 + # Read fields, check and convert type + values = {} + fs = line + # remove quotes + for i in range(len(fs)): + if fs[i] and ( + (fs[i][0] == '"' and fs[i][-1] == '"') + or (fs[i][0] == "'" and fs[i][-1] == "'") + ): + fs[i] = fs[i][1:-1] + for i in range(len(fs)): + val = fs[i].strip() + typ, table, an, descr, aliases = tuple(titles[titleslist[i]]) + # log('field %s: %s %s %s %s'%(titleslist[i], table, typ, an, descr)) + if not val and not an: + raise ScoValueError( + "line %d: null value not allowed in column %s" + % (linenum, titleslist[i]) + ) + if val == "": + val = None + else: + if typ == "real": + val = val.replace(",", ".") # si virgule a la française + try: + val = float(val) + except: + raise ScoValueError( + "valeur nombre reel invalide (%s) sur line %d, colonne %s" + % (val, linenum, titleslist[i]) + ) + elif typ == "integer": + try: + # on doit accepter des valeurs comme "2006.0" + val = val.replace(",", ".") # si virgule a la française + val = float(val) + if val % 1.0 > 1e-4: + raise ValueError() + val = int(val) + except: + raise ScoValueError( + "valeur nombre entier invalide (%s) sur ligne %d, colonne %s" + % (val, linenum, titleslist[i]) + ) + # xxx Ad-hoc checks (should be in format description) + if strlower(titleslist[i]) == "sexe": + try: + val = scolars.normalize_sexe(val) + except: + raise ScoValueError( + "valeur invalide pour 'SEXE' (doit etre 'M' ou 'MME' ou 'H' ou 'F', pas '%s') ligne %d, colonne %s" + % (val, linenum, titleslist[i]) + ) + # Excel date conversion: + if strlower(titleslist[i]) == "date_naissance": + if val: + if re.match("^[0-9]*\.?[0-9]*$", str(val)): + val = sco_excel.xldate_as_datetime(float(val)) + # INE + if ( + strlower(titleslist[i]) == "code_ine" + and always_require_ine + and not val + ): + raise ScoValueError( + "Code INE manquant sur ligne %d, colonne %s" + % (linenum, titleslist[i]) + ) + + # -- + values[titleslist[i]] = val + skip = False + is_new_ine = values["code_ine"] and _is_new_ine(cnx, values["code_ine"]) + if require_ine and not is_new_ine: + log("skipping %s (code_ine=%s)" % (values["nom"], values["code_ine"])) + skip = True + + if not skip: + if values["code_ine"] and not is_new_ine: + raise ScoValueError("Code INE dupliqué (%s)" % values["code_ine"]) + # Check nom/prenom + ok, NbHomonyms = scolars.check_nom_prenom( + cnx, nom=values["nom"], prenom=values["prenom"] + ) + if not ok: + raise ScoValueError( + "nom ou prénom invalide sur la ligne %d" % (linenum) + ) + if NbHomonyms: + NbImportedHomonyms += 1 + # Insert in DB tables + formsemestre_to_invalidate.add( + _import_one_student( + context, + cnx, + REQUEST, + formsemestre_id, + values, + GroupIdInferers, + annee_courante, + created_etudids, + linenum, + ) + ) + + # Verification proportion d'homonymes: si > 10%, abandonne + log("scolars_import_excel_file: detected %d homonyms" % NbImportedHomonyms) + if check_homonyms and NbImportedHomonyms > len(created_etudids) / 10: + log("scolars_import_excel_file: too many homonyms") + raise ScoValueError( + "Il y a trop d'homonymes (%d étudiants)" % NbImportedHomonyms + ) + except: + cnx.rollback() + log("scolars_import_excel_file: aborting transaction !") + # Nota: db transaction is sometimes partly commited... + # here we try to remove all created students + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + for etudid in created_etudids: + log("scolars_import_excel_file: deleting etudid=%s" % etudid) + cursor.execute( + "delete from notes_moduleimpl_inscription where etudid=%(etudid)s", + {"etudid": etudid}, + ) + cursor.execute( + "delete from notes_formsemestre_inscription where etudid=%(etudid)s", + {"etudid": etudid}, + ) + cursor.execute( + "delete from scolar_events where etudid=%(etudid)s", {"etudid": etudid} + ) + cursor.execute( + "delete from adresse where etudid=%(etudid)s", {"etudid": etudid} + ) + cursor.execute( + "delete from admissions where etudid=%(etudid)s", {"etudid": etudid} + ) + cursor.execute( + "delete from group_membership where etudid=%(etudid)s", + {"etudid": etudid}, + ) + cursor.execute( + "delete from identite where etudid=%(etudid)s", {"etudid": etudid} + ) + cnx.commit() + log("scolars_import_excel_file: re-raising exception") + raise + + diag.append("Import et inscription de %s étudiants" % len(created_etudids)) + + sco_news.add( + context, + REQUEST, + typ=NEWS_INSCR, + text="Inscription de %d étudiants" # peuvent avoir ete inscrits a des semestres differents + % len(created_etudids), + object=formsemestre_id, + ) + + log("scolars_import_excel_file: completing transaction") + cnx.commit() + + # Invalide les caches des semestres dans lesquels on a inscrit des etudiants: + context.Notes._inval_cache(formsemestre_id_list=formsemestre_to_invalidate) + + return diag + + +def _import_one_student( + context, + cnx, + REQUEST, + formsemestre_id, + values, + GroupIdInferers, + annee_courante, + created_etudids, + linenum, +): + """ + Import d'un étudiant et inscription dans le semestre. + Return: id du semestre dans lequel il a été inscrit. + """ + log( + "scolars_import_excel_file: formsemestre_id=%s values=%s" + % (formsemestre_id, str(values)) + ) + # Identite + args = values.copy() + etudid = scolars.identite_create(cnx, args, context=context, REQUEST=REQUEST) + created_etudids.append(etudid) + # Admissions + args["etudid"] = etudid + args["annee"] = annee_courante + adm_id = scolars.admission_create(cnx, args) + # Adresse + args["typeadresse"] = "domicile" + args["description"] = "(infos admission)" + adresse_id = scolars.adresse_create(cnx, args) + # Inscription au semestre + args["etat"] = "I" # etat insc. semestre + if formsemestre_id: + args["formsemestre_id"] = formsemestre_id + else: + args["formsemestre_id"] = values["codesemestre"] + formsemestre_id = values["codesemestre"] + # recupere liste des groupes: + if formsemestre_id not in GroupIdInferers: + GroupIdInferers[formsemestre_id] = sco_groups.GroupIdInferer( + context, formsemestre_id + ) + gi = GroupIdInferers[formsemestre_id] + if args["groupes"]: + groupes = args["groupes"].split(";") + else: + groupes = [] + group_ids = [gi[group_name] for group_name in groupes] + group_ids = {}.fromkeys(group_ids).keys() # uniq + if None in group_ids: + raise ScoValueError( + "groupe invalide sur la ligne %d (groupe %s)" % (linenum, groupes) + ) + + do_formsemestre_inscription_with_modules( + context, + args["formsemestre_id"], + etudid, + group_ids, + etat="I", + REQUEST=REQUEST, + method="import_csv_file", + ) + return args["formsemestre_id"] + + +def _is_new_ine(cnx, code_ine): + "True if this code is not in DB" + etuds = scolars.identite_list(cnx, {"code_ine": code_ine}) + return not etuds + + +# ------ Fonction ré-écrite en nov 2016 pour lire des fichiers sans etudid (fichiers APB) +def scolars_import_admission( + datafile, context, REQUEST, formsemestre_id=None, type_admission=None +): + """Importe données admission depuis un fichier Excel quelconque + par exemple ceux utilisés avec APB + + Cherche dans ce fichier les étudiants qui correspondent à des inscrits du + semestre formsemestre_id. + Le fichier n'a pas l'INE ni le NIP ni l'etudid, la correspondance se fait + via les noms/prénoms qui doivent être égaux (la casse, les accents et caractères spéciaux + étant ignorés). + + On tolère plusieurs variantes pour chaque nom de colonne (ici aussi, la casse, les espaces + et les caractères spéciaux sont ignorés. Ainsi, la colonne "Prénom:" sera considéré comme "prenom". + + Le parametre type_admission remplace les valeurs vides (dans la base ET dans le fichier importé) du champ type_admission. + Si une valeur existe ou est présente dans le fichier importé, ce paramètre est ignoré. + + TODO: + - choix onglet du classeur + """ + + log("scolars_import_admission: formsemestre_id=%s" % formsemestre_id) + members = sco_groups.get_group_members( + context, sco_groups.get_default_group(context, formsemestre_id) + ) + etuds_by_nomprenom = {} # { nomprenom : etud } + diag = [] + for m in members: + np = (adm_normalize_string(m["nom"]), adm_normalize_string(m["prenom"])) + if np in etuds_by_nomprenom: + msg = "Attention: hononymie pour %s %s" % (m["nom"], m["prenom"]) + log(msg) + diag.append(msg) + etuds_by_nomprenom[np] = m + + exceldata = datafile.read() + diag2, data = sco_excel.Excel_to_list(exceldata, convert_to_string=False) + if not data: + raise ScoException("scolars_import_admission: empty file !") + diag += diag2 + cnx = context.GetDBConnexion() + + titles = data[0] + # idx -> ('field', convertor) + fields = adm_get_fields(titles, formsemestre_id) + idx_nom = None + idx_prenom = None + for idx in fields: + if fields[idx][0] == "nom": + idx_nom = idx + if fields[idx][0] == "prenom": + idx_prenom = idx + if (idx_nom is None) or (idx_prenom is None): + log("fields indices=" + ", ".join([str(x) for x in fields])) + log("fields titles =" + ", ".join([fields[x][0] for x in fields])) + raise FormatError( + "scolars_import_admission: colonnes nom et prenom requises", + dest_url="form_students_import_infos_admissions?formsemestre_id=%s" + % formsemestre_id, + ) + + modifiable_fields = Set(ADMISSION_MODIFIABLE_FIELDS) + + nline = 2 # la premiere ligne de donnees du fichier excel est 2 + n_import = 0 + for line in data[1:]: + # Retrouve l'étudiant parmi ceux du semestre par (nom, prenom) + nom = adm_normalize_string(line[idx_nom]) + prenom = adm_normalize_string(line[idx_prenom]) + if not (nom, prenom) in etuds_by_nomprenom: + log( + "unable to find %s %s among members" % (line[idx_nom], line[idx_prenom]) + ) + else: + etud = etuds_by_nomprenom[(nom, prenom)] + cur_adm = scolars.admission_list(cnx, args={"etudid": etud["etudid"]})[0] + # peuple les champs presents dans le tableau + args = {} + for idx in fields: + field_name, convertor = fields[idx] + if field_name in modifiable_fields: + try: + val = convertor(line[idx]) + except ValueError: + raise FormatError( + 'scolars_import_admission: valeur invalide, ligne %d colonne %s: "%s"' + % (nline, field_name, line[idx]), + dest_url="form_students_import_infos_admissions?formsemestre_id=%s" + % formsemestre_id, + ) + if val is not None: # note: ne peut jamais supprimer une valeur + args[field_name] = val + if args: + args["etudid"] = etud["etudid"] + args["adm_id"] = cur_adm["adm_id"] + # Type admission: traitement particulier + if not cur_adm["type_admission"] and not args.get("type_admission"): + args["type_admission"] = type_admission + scolars.etudident_edit(cnx, args) + adr = scolars.adresse_list(cnx, args={"etudid": etud["etudid"]}) + if adr: + args["adresse_id"] = adr[0]["adresse_id"] + scolars.adresse_edit( + cnx, args + ) # ne passe pas le contexte: pas de notification ici + else: + args["typeadresse"] = "domicile" + args["description"] = "(infos admission)" + adresse_id = scolars.adresse_create(cnx, args) + # log('import_adm: %s' % args ) + # Change les groupes si nécessaire: + if args["groupes"]: + gi = sco_groups.GroupIdInferer(context, formsemestre_id) + groupes = args["groupes"].split(";") + group_ids = [gi[group_name] for group_name in groupes] + group_ids = {}.fromkeys(group_ids).keys() # uniq + if None in group_ids: + raise ScoValueError( + "groupe invalide sur la ligne %d (groupe %s)" + % (nline, groupes) + ) + + for group_id in group_ids: + sco_groups.change_etud_group_in_partition( + context, args["etudid"], group_id, REQUEST=REQUEST + ) + # + diag.append("import de %s" % (etud["nomprenom"])) + n_import += 1 + nline += 1 + diag.append("%d lignes importées" % n_import) + if n_import > 0: + context._inval_cache(formsemestre_id=formsemestre_id) + return diag + + +_ADM_PATTERN = re.compile(r"[\W]+", re.UNICODE) # supprime tout sauf alphanum + + +def adm_normalize_string(s): # normalize unicode title + return suppression_diacritics(_ADM_PATTERN.sub("", s.strip().lower())).replace( + "_", "" + ) + + +def adm_get_fields(titles, formsemestre_id): + """Cherche les colonnes importables dans les titres (ligne 1) du fichier excel + return: { idx : (field_name, convertor) } + """ + # log('adm_get_fields: titles=%s' % titles) + Fmt = sco_import_format_dict() + fields = {} + idx = 0 + for title in titles: + title_n = adm_normalize_string(title) + for k in Fmt: + for v in Fmt[k]["aliases"]: + if adm_normalize_string(v) == title_n: + typ = Fmt[k]["type"] + if typ == "real": + convertor = adm_convert_real + elif typ == "integer" or typ == "int": + convertor = adm_convert_int + else: + convertor = adm_convert_text + # doublons ? + if k in [x[0] for x in fields.values()]: + raise FormatError( + 'scolars_import_admission: titre "%s" en double (ligne 1)' + % (title), + dest_url="form_students_import_infos_admissions_apb?formsemestre_id=%s" + % formsemestre_id, + ) + fields[idx] = (k, convertor) + idx += 1 + + return fields + + +def adm_convert_text(v): + if type(v) == FloatType: + return "{:g}".format(v) # evite "1.0" + return v + + +def adm_convert_int(v): + if type(v) != IntType and not v: + return None + return int(float(v)) # accept "10.0" + + +def adm_convert_real(v): + if type(v) != FloatType and not v: + return None + return float(v) + + +def adm_table_description_format(context): + """Table HTML (ou autre format) decrivant les donnees d'admissions importables + """ + Fmt = sco_import_format_dict(with_codesemestre=False) + for k in Fmt: + Fmt[k]["attribute"] = k + Fmt[k]["aliases_str"] = ", ".join(Fmt[k]["aliases"]) + if not Fmt[k]["allow_nulls"]: + Fmt[k]["required"] = "*" + if k in ADMISSION_MODIFIABLE_FIELDS: + Fmt[k]["writable"] = "oui" + else: + Fmt[k]["writable"] = "non" + titles = { + "attribute": "Attribut", + "type": "Type", + "required": "Requis", + "writable": "Modifiable", + "description": "Description", + "aliases_str": "Titres (variantes)", + } + columns_ids = ("attribute", "type", "writable", "description", "aliases_str") + + tab = GenTable( + titles=titles, + columns_ids=columns_ids, + rows=Fmt.values(), + html_sortable=True, + html_class="table_leftalign", + preferences=context.get_preferences(), + ) + return tab diff --git a/SuppressAccents.py b/SuppressAccents.py new file mode 100644 index 00000000..f0b4d34f --- /dev/null +++ b/SuppressAccents.py @@ -0,0 +1,206 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +"""Suppression des accents d'une chaine + +Source: http://wikipython.flibuste.net/moin.py/JouerAvecUnicode#head-1213938516c633958921591439c33d202244e2f4 +""" + +_reptable = {} + + +def _fill_reptable(): + _corresp = [ + ( + u"A", + [0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x0100, 0x0102, 0x0104], + ), + (u"AE", [0x00C6]), + ( + u"a", + [0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x0101, 0x0103, 0x0105], + ), + (u"ae", [0x00E6]), + (u"C", [0x00C7, 0x0106, 0x0108, 0x010A, 0x010C]), + (u"c", [0x00E7, 0x0107, 0x0109, 0x010B, 0x010D]), + (u"D", [0x00D0, 0x010E, 0x0110]), + (u"d", [0x00F0, 0x010F, 0x0111]), + ( + u"E", + [0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x0112, 0x0114, 0x0116, 0x0118, 0x011A], + ), + ( + u"e", + [ + 0x00E8, + 0xE9, + 0x00E9, + 0x00EA, + 0xEB, + 0x00EB, + 0x0113, + 0x0115, + 0x0117, + 0x0119, + 0x011B, + ], + ), + (u"G", [0x011C, 0x011E, 0x0120, 0x0122]), + (u"g", [0x011D, 0x011F, 0x0121, 0x0123]), + (u"H", [0x0124, 0x0126]), + (u"h", [0x0125, 0x0127]), + ( + u"I", + [0x00CC, 0x00CD, 0x00CE, 0x00CF, 0x0128, 0x012A, 0x012C, 0x012E, 0x0130], + ), + ( + u"i", + [0x00EC, 0x00ED, 0x00EE, 0x00EF, 0x0129, 0x012B, 0x012D, 0x012F, 0x0131], + ), + (u"IJ", [0x0132]), + (u"ij", [0x0133]), + (u"J", [0x0134]), + (u"j", [0x0135]), + (u"K", [0x0136]), + (u"k", [0x0137, 0x0138]), + (u"L", [0x0139, 0x013B, 0x013D, 0x013F, 0x0141]), + (u"l", [0x013A, 0x013C, 0x013E, 0x0140, 0x0142]), + (u"N", [0x00D1, 0x0143, 0x0145, 0x0147, 0x014A]), + (u"n", [0x00F1, 0x0144, 0x0146, 0x0148, 0x0149, 0x014B]), + ( + u"O", + [0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D8, 0x014C, 0x014E, 0x0150], + ), + ( + u"o", + [0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F8, 0x014D, 0x014F, 0x0151], + ), + (u"OE", [0x0152]), + (u"oe", [0x0153]), + (u"R", [0x0154, 0x0156, 0x0158]), + (u"r", [0x0155, 0x0157, 0x0159]), + (u"S", [0x015A, 0x015C, 0x015E, 0x0160]), + (u"s", [0x015B, 0x015D, 0x015F, 0x01610, 0x017F, 0x0218]), + (u"T", [0x0162, 0x0164, 0x0166]), + (u"t", [0x0163, 0x0165, 0x0167]), + ( + u"U", + [ + 0x00D9, + 0x00DA, + 0x00DB, + 0x00DC, + 0x0168, + 0x016A, + 0x016C, + 0x016E, + 0x0170, + 0x172, + ], + ), + ( + u"u", + [ + 0x00F9, + 0x00FA, + 0x00FB, + 0x00FC, + 0x0169, + 0x016B, + 0x016D, + 0x016F, + 0x0171, + 0xB5, + ], + ), + (u"W", [0x0174]), + (u"w", [0x0175]), + (u"Y", [0x00DD, 0x0176, 0x0178]), + (u"y", [0x00FD, 0x00FF, 0x0177]), + (u"Z", [0x0179, 0x017B, 0x017D]), + (u"z", [0x017A, 0x017C, 0x017E]), + ( + u"", + [ + 0x80, + 0x81, + 0x82, + 0x83, + 0x84, + 0x85, + 0x86, + 0x87, + 0x88, + 0x89, + 0x8A, + 0x8B, + 0x8C, + 0x8D, + 0x8E, + 0x8F, + 0x90, + 0x91, + 0x92, + 0x93, + 0x94, + 0x95, + 0x96, + 0x97, + 0x98, + 0x99, + 0x9A, + 0x9B, + 0x9C, + 0x9D, + 0x9E, + 0x9F, + ], + ), # misc controls + (u" ", [0x00A0]), #   + (u"!", [0xA1]), # ¡ + (u"c", [0xA2]), # cent + (u"L", [0xA3]), # pound + (u"o", [0xA4]), # currency symbol + (u"Y", [0xA5]), # yen + (u"|", [0xA6]), # Broken Bar ¦ + (u"S", [0xA7]), # section + (u"", [0xA8]), # diaeresis ¨ + (u"", [0xA9]), # copyright + (u'"', [0xAB, 0xBA]), # «, » <<, >> + (u" ", [0xAC]), # Math Not Sign + (u"", [0xAD]), # DashPunctuation + (u"(r)", [0xAE]), # registred + (u"-", [0xAF]), # macron + (u"", [0xB0]), # degre + (u"+-", [0xB1]), # +- + (u"2", [0x00B2, 0xB2]), # deux exposant + (u"3", [0xB3]), # 3 exposant + (u".", [0xB7]), # ·, + (u"1/4", [0xBC]), # 1/4 + (u"1/2", [0xBD]), # 1/2 + (u"3/4", [0xBE]), # 3/4 + (u"e", [0x20AC]), # euro + (u"--", [0x2013]), # EN DASH + (u"'", [0x2018, 0x2019, 0x201A]), # LEFT, RIGHT SINGLE QUOTATION MARK + (u" ", [0x2020]), # dagger + ] + global _reptable + for repchar, codes in _corresp: + for code in codes: + _reptable[code] = repchar + + +_fill_reptable() + + +def suppression_diacritics(s): + """Suppression des accents et autres marques. + + @param s: le texte à nettoyer. + @type s: str ou unicode + @return: le texte nettoyé de ses marques diacritiques. + @rtype: unicode + """ + if isinstance(s, str): + s = unicode(s, "utf8", "replace") + return s.translate(_reptable) diff --git a/TODO b/TODO new file mode 100644 index 00000000..4041532f --- /dev/null +++ b/TODO @@ -0,0 +1,238 @@ + + NOTES EN VRAC / Brouillon / Trucs obsoletes + + +#do_moduleimpl_list\(\{"([a-z_]*)"\s*:\s*(.*)\}\) +#do_moduleimpl_list( $1 = $2 ) + +#do_moduleimpl_list\([\s\n]*args[\s\n]*=[\s\n]*\{"([a-z_]*)"[\s\n]*:[\s\n]*(.*)[\s\n]*\}[\s\n]*\) + +Upgrade JavaScript + - jquery-ui-1.12.1 introduit un problème d'affichage de la barre de menu. + Il faudrait la revoir entièrement pour upgrader. + On reste donc à jquery-ui-1.10.4.custom + Or cette version est incompatible avec jQuery 3 (messages d'erreur dans la console) + On reste donc avec jQuery 1.12.14 + + +Suivi des requêtes utilisateurs: + table sql: id, ip, authuser, request + + +* Optim: +porcodeb4, avant memorisation des moy_ue: +S1 SEM14133 cold start: min 9s, max 12s, avg > 11s + inval (add note): 1.33s (pas de recalcul des autres) + inval (add abs) : min8s, max 12s (recalcule tout :-() +LP SEM14946 cold start: 0.7s - 0.86s + + + +----------------- LISTE OBSOLETE (très ancienne, à trier) ----------------------- +BUGS +---- + + - formsemestre_inscription_with_modules + si inscription 'un etud deja inscrit, IntegrityError + +FEATURES REQUESTS +----------------- + +* Bulletins: + . logos IUT et Univ sur bull PDF + . nom departement: nom abbrégé (CJ) ou complet (Carrière Juridiques) + . bulletin: deplacer la barre indicateur (cf OLDGEA S2: gêne) + . bulletin: click nom titre -> ficheEtud + + . formsemestre_pagebulletin_dialog: marges en mm: accepter "2,5" et "2.5" + et valider correctement le form ! + +* Jury + . recapcomplet: revenir avec qq lignes au dessus de l'étudiant en cours + + +* Divers + . formsemestre_editwithmodules: confirmer suppression modules + (et pour l'instant impossible si evaluations dans le module) + +* Modules et UE optionnelles: + . UE capitalisées: donc dispense possible dans semestre redoublé. + traitable en n'inscrivant pas l'etudiant au modules + de cette UE: faire interface utilisateur + + . page pour inscription d'un etudiant a un module + . page pour visualiser les modules auquel un etudiant est inscrit, + et le desinscrire si besoin. + + . ficheEtud indiquer si inscrit au module sport + +* Absences + . EtatAbsences : verifier dates (en JS) + . Listes absences pdf et listes groupes pdf + emargements (cf mail Nathalie) + . absences par demi-journées sur EtatAbsencesDate (? à vérifier) + . formChoixSemestreGroupe: utilisé par Absences/index_html + a améliorer + + +* Notes et évaluations: + . Exception "Not an OLE file": generer page erreur plus explicite + . Dates evaluation: utiliser JS pour calendrier + . Saisie des notes: si une note invalide, l'indiquer dans le listing (JS ?) + . et/ou: notes invalides: afficher les noms des etudiants concernes + dans le message d'erreur. + . upload excel: message erreur peu explicite: + * Feuille "Saisie notes", 17 lignes + * Erreur: la feuille contient 1 notes invalides + * Notes invalides pour les id: ['10500494'] + (pas de notes modifiées) + Notes chargées. <<< CONTRADICTOIRE !! + + . recap complet semestre: + Options: + - choix groupes + - critère de tri (moy ou alphab) + - nb de chiffres a afficher + + + definir des "catégories" d'évaluations (eg "théorie","pratique") + afin de n'afficher que des moyennes "de catégorie" dans + le bulletin. + + . liste des absents à une eval et croisement avec BD absences + + . notes_evaluation_listenotes + - afficher groupes, moyenne, #inscrits, #absents, #manquantes dans l'en-tete. + - lien vers modif notes (selon role) + + . Export excel des notes d'evaluation: indiquer date, et autres infos en haut. + . Génération PDF listes notes + . Page recap notes moyennes par groupes (choisir type de groupe?) + + . (GEA) edition tableau notes avec tous les evals d'un module + (comme notes_evaluation_listenotes mais avec tt les evals) + + +* Non prioritaire: + . optimiser scolar_news_summary + . recapitulatif des "nouvelles" + - dernieres notes + - changement de statuts (demissions,inscriptions) + - annotations + - entreprises + + . notes_table: pouvoir changer decision sans invalider tout le cache + . navigation: utiliser Session pour montrer historique pages vues ? + + + +------------------------------------------------------------------------ + + +A faire: + - fiche etud: code dec jury sur ligne 1 + si ancien, indiquer autorisation inscription sous le parcours + + - saisie notes: undo + - saisie notes: validation +- ticket #18: +UE capitalisées: donc dispense possible dans semestre redoublé. Traitable en n'inscrivant pas l'etudiant aux modules de cette UE: faire interface utilisateur. + +Prévoir d'entrer une UE capitalisée avec sa note, date d'obtention et un commentaire. Coupler avec la désincription aux modules (si l'étudiant a été inscrit avec ses condisciples). + + + - Ticket #4: Afin d'éviter les doublons, vérifier qu'il n'existe pas d'homonyme proche lors de la création manuelle d'un étudiant. (confirmé en ScoDoc 6, vérifier aussi les imports Excel) + + - Ticket #74: Il est possible d'inscrire un étudiant sans prénom par un import excel !!! + + - Ticket #64: saisir les absences pour la promo entiere (et pas par groupe). Des fois, je fais signer une feuille de presence en amphi a partir de la liste de tous les etudiants. Ensuite pour reporter les absents par groupe, c'est galere. + + - Ticket #62: Lors des exports Excel, le format des cellules n'est pas reconnu comme numérique sous Windows (pas de problèmes avec Macintosh et Linux). + +A confirmer et corriger. + + - Ticket #75: On peut modifier une décision de jury (et les autorisations de passage associées), mais pas la supprimer purement et simplement. +Ajoute ce choix dans les "décisions manuelles". + + - Ticket #37: Page recap notes moyennes par groupes +Construire une page avec les moyennes dans chaque UE ou module par groupe d'étudiants. +Et aussi pourquoi pas ventiler par type de bac, sexe, parcours (nombre de semestre de parcours) ? +redemandé par CJ: à faire avant mai 2008 ! + + - Ticket #75: Synchro Apogée: choisir les etudiants +Sur la page de syncho Apogée (formsemestre_synchro_etuds), on peut choisir (cocher) les étudiants Apogée à importer. mais on ne peut pas le faire s'ils sont déjà dans ScoDoc: il faudrait ajouter des checkboxes dans toutes les listes. + + - Ticket #9: Format des valeurs de marges des bulletins. +formsemestre_pagebulletin_dialog: marges en mm: accepter "2,5" et "2.5" et valider correctement le form ! + + - Ticket #17: Suppression modules dans semestres +formsemestre_editwithmodules: confirmer suppression modules + + - Ticket #29: changer le stoquage des photos, garder une version HD. + + - bencher NotesTable sans calcul de moyennes. Etudier un cache des moyennes de modules. + - listes d'utilisateurs (modules): remplacer menus par champs texte + completions javascript + - documenter archives sur Wiki + - verifier paquet Debian pour font pdf (reportab: helvetica ... plante si font indisponible) + - chercher comment obtenir une page d'erreur correcte pour les pages POST + (eg: si le font n'existe pas, archive semestre echoue sans page d'erreur) + ? je ne crois pas que le POST soit en cause. HTTP status=500 + ne se produit pas avec Safari + - essayer avec IE / Win98 + - faire apparaitre les diplômés sur le graphe des parcours + - démission: formulaire: vérifier que la date est bien dans le semestre + + + graphe parcours: aligner en colonnes selon les dates (de fin), placer les diplomes + dans la même colone que le semestre terminal. + + - modif gestion utilisateurs (donner droits en fct du dept. d'appartenance, bug #57) + - modif form def. utilisateur (dept appartenance) + - utilisateurs: source externe + - archivage des semestres + + + o-------------------------------------o + +* Nouvelle gestion utilisateurs: + objectif: dissocier l'authentification de la notion "d'enseignant" + On a une source externe "d'utilisateurs" (annuaire LDAP ou base SQL) + qui permet seulement de: + - authentifier un utilisateur (login, passwd) + - lister un utilisateur: login => firstname, lastname, email + - lister les utilisateurs + + et une base interne ScoDoc "d'acteurs" (enseignants, administratifs). + Chaque acteur est défini par: + - actor_id, firstname, lastname + date_creation, date_expiration, + roles, departement, + email (+flag indiquant s'il faut utiliser ce mail ou celui de + l'utilisateur ?) + state (on, off) (pour desactiver avant expiration ?) + user_id (login) => lien avec base utilisateur + + On offrira une source d'utilisateurs SQL (base partagée par tous les dept. + d'une instance ScoDoc), mais dans la plupart des cas les gens utiliseront + un annuaire LDAP. + + La base d'acteurs remplace ScoUsers. Les objets ScoDoc (semestres, + modules etc) font référence à des acteurs (eg responsable_id est un actor_id). + + Le lien entre les deux ? + Loger un utilisateur => authentification utilisateur + association d'un acteur + Cela doit se faire au niveau d'un UserFolder Zope, pour avoir les + bons rôles et le contrôle d'accès adéquat. + (Il faut donc coder notre propre UserFolder). + On ne peut associer qu'un acteur à l'état 'on' et non expiré. + + Opérations ScoDoc: + - paramétrage: choisir et paramétrer source utilisateurs + - ajouter utilisateur: choisir un utilisateur dans la liste + et lui associer un nouvel acteur (choix des rôles, des dates) + + éventuellement: synchro d'un ensemble d'utilisateurs, basé sur + une requête (eg LDAP) précise (quelle interface utilisateur proposer ?) + + - régulièrement (cron) aviser quelqu'un (le chef) de l'expiration des acteurs. + - changer etat d'un acteur (on/off) + + + o-------------------------------------o + diff --git a/TrivialFormulator.py b/TrivialFormulator.py new file mode 100644 index 00000000..788d95da --- /dev/null +++ b/TrivialFormulator.py @@ -0,0 +1,769 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + + +"""Simple form generator/validator + + E. Viennet 2005 - 2008 + + v 1.2 +""" + +from types import * + + +def TrivialFormulator( + form_url, + values, + formdescription=(), + initvalues={}, + method="post", + enctype=None, + submitlabel="OK", + name=None, + formid="tf", + cssclass="", + cancelbutton=None, + submitbutton=True, + submitbuttonattributes=[], + top_buttons=False, # place buttons at top of form + bottom_buttons=True, # buttons after form + html_foot_markup="", + readonly=False, + is_submitted=False, +): + """ + form_url : URL for this form + initvalues : dict giving default values + values : dict with all HTML form variables (may start empty) + is_submitted: handle form as if already submitted + + Returns (status, HTML form, values) + status = 0 (html to display), + 1 (ok, validated values in "values") + -1 cancel (if cancelbutton specified) + HTML form: html string (form to insert in your web page) + values: None or, when the form is submitted and correctly filled, + a dictionnary with the requeted values. + formdescription: sequence [ (field, description), ... ] + where description is a dict with following (optional) keys: + default : default value for this field ('') + title : text titre (default to field name) + allow_null : if true, field can be left empty (default true) + type : 'string', 'int', 'float' (default to string), 'list' (only for hidden) + readonly : default False. if True, no form element, display current value. + convert_numbers: covert int and float values (from string) + allowed_values : list of possible values (default: any value) + validator : function validating the field (called with (value,field)). + min_value : minimum value (for floats and ints) + max_value : maximum value (for floats and ints) + explanation: text string to display next the input widget + title_buble: help bubble on field title (needs bubble.js or equivalent) + comment : comment, showed under input widget + withcheckbox: if true, place a checkbox at the left of the input + elem. Checked items will be returned in 'tf-checked' + attributes: a liste of strings to put in the HTML form element + template: HTML template for element + HTML elements: + input_type : 'text', 'textarea', 'password', + 'radio', 'menu', 'checkbox', + 'hidden', 'separator', 'file', 'date', 'boolcheckbox', + 'text_suggest' + (default text) + size : text field width + rows, cols: textarea geometry + labels : labels for radio or menu lists (associated to allowed_values) + vertical: for checkbox; if true, vertical layout + disabled_items: for checkbox, dict such that disabled_items[i] true if disabled checkbox + To use text_suggest elements, one must: + - specify options in text_suggest_options (a dict) + - HTML page must load JS AutoSuggest.js and CSS autosuggest_inquisitor.css + - bodyOnLoad must call JS function init_tf_form(formid) + """ + method = method.lower() + if method == "get": + enctype = None + t = TF( + form_url, + values, + formdescription, + initvalues, + method, + enctype, + submitlabel, + name, + formid, + cssclass, + cancelbutton=cancelbutton, + submitbutton=submitbutton, + submitbuttonattributes=submitbuttonattributes, + top_buttons=top_buttons, + bottom_buttons=bottom_buttons, + html_foot_markup=html_foot_markup, + readonly=readonly, + is_submitted=is_submitted, + ) + form = t.getform() + if t.canceled(): + res = -1 + elif t.submitted() and t.result: + res = 1 + else: + res = 0 + return res, form, t.result + + +class TF: + def __init__( + self, + form_url, + values, + formdescription=[], + initvalues={}, + method="POST", + enctype=None, + submitlabel="OK", + name=None, + formid="tf", + cssclass="", + cancelbutton=None, + submitbutton=True, + submitbuttonattributes=[], + top_buttons=False, # place buttons at top of form + bottom_buttons=True, # buttons after form + html_foot_markup="", # html snippet put at the end, just after the table + readonly=False, + is_submitted=False, + ): + self.form_url = form_url + self.values = values + self.formdescription = list(formdescription) + self.initvalues = initvalues + self.method = method + self.enctype = enctype + self.submitlabel = submitlabel + if name: + self.name = name + else: + self.name = formid # 'tf' + self.formid = formid + self.cssclass = cssclass + self.cancelbutton = cancelbutton + self.submitbutton = submitbutton + self.submitbuttonattributes = submitbuttonattributes + self.top_buttons = top_buttons + self.bottom_buttons = bottom_buttons + self.html_foot_markup = html_foot_markup + self.readonly = readonly + self.result = None + self.is_submitted = is_submitted + if readonly: + self.top_buttons = self.bottom_buttons = False + self.cssclass += " readonly" + + def submitted(self): + "true if form has been submitted" + if self.is_submitted: + return True + return self.values.get("%s-submitted" % self.formid, False) + + def canceled(self): + "true if form has been canceled" + return self.values.get("%s_cancel" % self.formid, False) + + def getform(self): + "return HTML form" + R = [] + msg = None + self.setdefaultvalues() + if self.submitted() and not self.readonly: + msg = self.checkvalues() + # display error message + R.append(tf_error_message(msg)) + # form or view + if self.readonly: + R = R + self._ReadOnlyVersion(self.formdescription) + else: + R = R + self._GenForm() + # + return "\n".join(R) + + __str__ = getform + __repr__ = getform + + def setdefaultvalues(self): + "set default values and convert numbers to strings" + for (field, descr) in self.formdescription: + # special case for boolcheckbox + if descr.get("input_type", None) == "boolcheckbox" and self.submitted(): + if not self.values.has_key(field): + self.values[field] = 0 + else: + self.values[field] = 1 + if not self.values.has_key(field): + if descr.has_key("default"): # first: default in form description + self.values[field] = descr["default"] + else: # then: use initvalues dict + self.values[field] = self.initvalues.get(field, "") + if self.values[field] == None: + self.values[field] = "" + + # convert numbers + if type(self.values[field]) == type(1) or type(self.values[field]) == type( + 1.0 + ): + self.values[field] = str(self.values[field]) + # + if not self.values.has_key("tf-checked"): + if self.submitted(): + # si rien n'est coché, tf-checked n'existe plus dans la reponse + self.values["tf-checked"] = [] + else: + self.values["tf-checked"] = self.initvalues.get("tf-checked", []) + self.values["tf-checked"] = [str(x) for x in self.values["tf-checked"]] + + def checkvalues(self): + "check values. Store .result and returns msg" + ok = 1 + msg = [] + for (field, descr) in self.formdescription: + val = self.values[field] + # do not check "unckecked" items + if descr.get("withcheckbox", False): + if not field in self.values["tf-checked"]: + continue + # null values + allow_null = descr.get("allow_null", True) + if not allow_null: + if val == "" or val == None: + msg.append( + "Le champ '%s' doit être renseigné" % descr.get("title", field) + ) + ok = 0 + # type + typ = descr.get("type", "string") + if val != "" and val != None: + # check only non-null values + if typ[:3] == "int": + try: + val = int(val) + self.values[field] = val + except: + msg.append( + "La valeur du champ '%s' doit être un nombre entier" % field + ) + ok = 0 + elif typ == "float" or typ == "real": + self.values[field] = self.values[field].replace(",", ".") + try: + val = float(val.replace(",", ".")) # allow , + self.values[field] = val + except: + msg.append( + "La valeur du champ '%s' doit être un nombre" % field + ) + ok = 0 + if typ[:3] == "int" or typ == "float" or typ == "real": + if descr.has_key("min_value") and val < descr["min_value"]: + msg.append( + "La valeur (%d) du champ '%s' est trop petite (min=%s)" + % (val, field, descr["min_value"]) + ) + ok = 0 + + if descr.has_key("max_value") and val > descr["max_value"]: + msg.append( + "La valeur (%s) du champ '%s' est trop grande (max=%s)" + % (val, field, descr["max_value"]) + ) + ok = 0 + + # allowed values + if descr.has_key("allowed_values"): + if descr.get("input_type", None) == "checkbox": + # for checkboxes, val is a list + for v in val: + if not v in descr["allowed_values"]: + msg.append( + "valeur invalide (%s) pour le champ '%s'" % (val, field) + ) + ok = 0 + elif descr.get("input_type", None) == "boolcheckbox": + pass + elif not val in descr["allowed_values"]: + msg.append("valeur invalide (%s) pour le champ '%s'" % (val, field)) + ok = 0 + if descr.has_key("validator"): + if not descr["validator"](val, field): + msg.append("valeur invalide (%s) pour le champ '%s'" % (val, field)) + ok = 0 + # boolean checkbox + if descr.get("input_type", None) == "boolcheckbox": + if int(val): + self.values[field] = 1 + else: + self.values[field] = 0 + # open('/tmp/toto','a').write('checkvalues: val=%s (%s) values[%s] = %s\n' % (val, type(val), field, self.values[field])) + if descr.get("convert_numbers", False): + if typ[:3] == "int": + self.values[field] = int(self.values[field]) + elif typ == "float" or typ == "real": + self.values[field] = float(self.values[field].replace(",", ".")) + if ok: + self.result = self.values + else: + self.result = None + return msg + + def _GenForm(self, method="", enctype=None, form_url=""): + values = self.values + add_no_enter_js = False # add JS function to prevent 'enter' -> submit + # form template + + # default template for each input element + itemtemplate = """ + %(label)s%(elem)s + + """ + hiddenitemtemplate = "%(elem)s" + separatortemplate = '%(label)s' + # ---- build form + buttons_markup = "" + if self.submitbutton: + buttons_markup += ( + '' + % ( + self.formid, + self.formid, + self.submitlabel, + " ".join(self.submitbuttonattributes), + ) + ) + if self.cancelbutton: + buttons_markup += ( + ' ' + % (self.formid, self.formid, self.cancelbutton) + ) + + R = [] + suggest_js = [] + if self.enctype is None: + if self.method == "post": + enctype = "multipart/form-data" + else: + enctype = "application/x-www-form-urlencoded" + if self.cssclass: + klass = ' class="%s"' % self.cssclass + else: + klass = "" + name = self.name + R.append( + '
' + % (self.form_url, self.method, self.formid, enctype, name, klass) + ) + R.append('' % self.formid) + if self.top_buttons: + R.append(buttons_markup + "

") + R.append('') + idx = 0 + for idx in range(len(self.formdescription)): + (field, descr) = self.formdescription[idx] + nextitemname = None + if idx < len(self.formdescription) - 2: + nextitemname = self.formdescription[idx + 1][0] + if descr.get("readonly", False): + R.append(self._ReadOnlyElement(field, descr)) + continue + wid = self.name + "_" + field + size = descr.get("size", 12) + rows = descr.get("rows", 5) + cols = descr.get("cols", 60) + title = descr.get("title", field.capitalize()) + title_bubble = descr.get("title_bubble", None) + withcheckbox = descr.get("withcheckbox", False) + input_type = descr.get("input_type", "text") + item_dom_id = descr.get("dom_id", "") + if item_dom_id: + item_dom_attr = ' id="%s"' % item_dom_id + else: + item_dom_attr = "" + # choix du template + etempl = descr.get("template", None) + if etempl is None: + if input_type == "hidden": + etempl = hiddenitemtemplate + elif input_type == "separator": + etempl = separatortemplate + R.append(etempl % {"label": title, "item_dom_attr": item_dom_attr}) + continue + else: + etempl = itemtemplate + lab = [] + lem = [] + if withcheckbox and input_type != "hidden": + if field in values["tf-checked"]: + checked = 'checked="checked"' + else: + checked = "" + lab.append( + '' + % ("tf-checked", field, checked) + ) + if title_bubble: + lab.append( + '%s' + % (title_bubble, title) + ) + else: + lab.append(title) + # + attribs = " ".join(descr.get("attributes", [])) + if ( + withcheckbox and not checked + ) or not descr.get( # desactive les element non coches: + "enabled", True + ): + attribs += ' disabled="true"' + # + if input_type == "text": + lem.append( + '') % values) + elif input_type == "password": + lem.append( + '') % values) + elif input_type == "radio": + labels = descr.get("labels", descr["allowed_values"]) + for i in range(len(labels)): + if descr["allowed_values"][i] == values[field]: + checked = 'checked="checked"' + else: + checked = "" + lem.append( + '%s' + % ( + field, + descr["allowed_values"][i], + checked, + attribs, + labels[i], + ) + ) + elif input_type == "menu": + lem.append('") + elif input_type == "checkbox" or input_type == "boolcheckbox": + if input_type == "checkbox": + labels = descr.get("labels", descr["allowed_values"]) + else: # boolcheckbox + labels = [""] + descr["allowed_values"] = ["0", "1"] + vertical = descr.get("vertical", False) + disabled_items = descr.get("disabled_items", {}) + if vertical: + lem.append("
") + for i in range(len(labels)): + if input_type == "checkbox": + # from notes_log import log # debug only + # log('checkbox: values[%s] = "%s"' % (field,repr(values[field]) )) + # log("descr['allowed_values'][%s] = '%s'" % (i, repr(descr['allowed_values'][i]))) + if descr["allowed_values"][i] in values[field]: + checked = 'checked="checked"' + else: + checked = "" + else: # boolcheckbox + # open('/tmp/toto','a').write('GenForm: values[%s] = %s (%s)\n' % (field, values[field], type(values[field]))) + try: + v = int(values[field]) + except: + v = 0 + if v: + checked = 'checked="checked"' + else: + checked = "" + if vertical: + lem.append("") + if vertical: + lem.append("
") + if disabled_items.get(i, False): + disab = 'disabled="1"' + ilab = ( + '' + + labels[i] + + " (non modifiable)" + ) + else: + disab = "" + ilab = "" + labels[i] + "" + lem.append( + '%s' + % ( + field, + descr["allowed_values"][i], + attribs, + disab, + checked, + ilab, + ) + ) + if vertical: + lem.append("
") + elif input_type == "textarea": + lem.append( + '' + % (field, wid, rows, cols, attribs, values[field]) + ) + elif input_type == "hidden": + if descr.get("type", "") == "list": + for v in values[field]: + lem.append( + '' + % (field, v, attribs) + ) + else: + lem.append( + '' + % (field, wid, values[field], attribs) + ) + elif input_type == "separator": + pass + elif input_type == "file": + lem.append( + '' + % (field, size, values[field], attribs) + ) + elif input_type == "date": # JavaScript widget for date input + lem.append( + '' + % (field, values[field]) + ) + elif input_type == "text_suggest": + lem.append( + '') % values) + suggest_js.append( + """var %s_opts = %s; + var %s_as = new bsn.AutoSuggest('%s', %s_opts); + """ + % ( + field, + dict2js(descr.get("text_suggest_options", {})), + field, + field, + field, + ) + ) + else: + raise ValueError("unkown input_type for form (%s)!" % input_type) + explanation = descr.get("explanation", "") + if explanation: + lem.append('%s' % explanation) + comment = descr.get("comment", "") + if comment: + lem.append('
%s' % comment) + R.append( + etempl + % { + "label": "\n".join(lab), + "elem": "\n".join(lem), + "item_dom_attr": item_dom_attr, + } + ) + R.append("") + + R.append(self.html_foot_markup) + + if self.bottom_buttons: + R.append("
" + buttons_markup) + + if add_no_enter_js: + R.append( + """ + """ + ) # enter_focus_next, ne focus que les champs text + if suggest_js: + # nota: formid is currently ignored + # => only one form with text_suggest field on a page. + R.append( + """""" + % "\n".join(suggest_js) + ) + # Javascript common to all forms: + R.append( + """""" + ) + R.append("
") + return R + + def _ReadOnlyElement(self, field, descr): + "Generate HTML for an element, read-only" + R = [] + title = descr.get("title", field.capitalize()) + withcheckbox = descr.get("withcheckbox", False) + input_type = descr.get("input_type", "text") + klass = descr.get("cssclass", "") + klass = " " + klass + if input_type == "hidden": + return "" + + R.append('' % klass) + + if input_type == "separator": # separator + R.append('%s' % title) + else: + R.append('' % klass) + R.append("%s" % title) + R.append('' % klass) + + if input_type == "text" or input_type == "text_suggest": + R.append(("%(" + field + ")s") % self.values) + elif input_type in ("radio", "menu", "checkbox", "boolcheckbox"): + if input_type == "boolcheckbox": + labels = descr.get( + "labels", descr.get("allowed_values", ["oui", "non"]) + ) + # XXX open('/tmp/log', 'w').write('%s labels=%s, val=%s\ndescr=%s\n'%(field, labels, self.values[field], descr)) + R.append(labels[int(self.values[field])]) + if int(self.values[field]): + R.append('' % field) + else: + labels = descr.get("labels", descr["allowed_values"]) + for i in range(len(labels)): + if str(descr["allowed_values"][i]) == str(self.values[field]): + R.append('%s' % labels[i]) + elif input_type == "textarea": + R.append('
%s
' % self.values[field]) + elif input_type == "separator" or input_type == "hidden": + pass + elif input_type == "file": + R.append("'%s'" % self.values[field]) + else: + raise ValueError("unkown input_type for form (%s)!" % input_type) + + explanation = descr.get("explanation", "") + if explanation: + R.append('%s' % explanation) + + R.append("") + + return "\n".join(R) + + def _ReadOnlyVersion(self, formdescription): + "Generate HTML for read-only view of the form" + R = [''] + for (field, descr) in formdescription: + R.append(self._ReadOnlyElement(field, descr)) + R.append("
") + return R + + +def dict2js(d): + """convert Python dict to JS code""" + r = [] + for k in d: + v = d[k] + if type(v) == BooleanType: + if v: + v = "true" + else: + v = "false" + elif type(v) == StringType: + v = '"' + v + '"' + + r.append("%s: %s" % (k, v)) + return "{" + ",\n".join(r) + "}" + + +def tf_error_message(msg): + """html for form error message""" + if not msg: + return "" + if type(msg) == StringType: + msg = [msg] + return ( + '' + % '
  • '.join(msg) + ) diff --git a/VERSION.py b/VERSION.py new file mode 100644 index 00000000..853ccfbb --- /dev/null +++ b/VERSION.py @@ -0,0 +1,349 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +SCOVERSION = "7.18" + +SCONAME = "ScoDoc" + +SCONEWS = """ +

    Année 2020

    +
      +
    • Nouveau site scodoc.org pour la documentation.
    • +
    • Enregistrement de semestres extérieurs
    • +
    • Améliorations PV de Jury
    • +
    • Contributions J.-M. Place: aide au diagnostic problèmes export Apogée +
    • +
    +

    Année 2019

    +
      +
    • Support Linux Debian 10
    • +
    • Petites améliorations: import groupes, droits de suppression notes pour vacataires, etc.
    • +
    • Exports listes pour Moodle
    • +
    • Fonction facilitant l'envoi de données pour l'assistance technique
    • +
    +

    Année 2018

    +
      +
    • Affichage date finalisation inscription Apogée
    • +
    • Co-responsables de semestres
    • +
    • Amélioration page d'accueil département
    • +
    • Corrections diverses et petites améliorations
    • +
    • Avis de poursuites d'études plus robustes et configurables
    • +
    +

    Année 2017

    +
      +
    • Bonus/Malus sur les moyennes d'UE
    • +
    • Enregistrement des informations sur le devenir de l'étudiant
    • +
    • Export global des résultats d'un département (utile pour les rapports d'évaluation)
    • +
    • Compatibilité Linux Debian 9, et modernisation de certains composants
    • +
    • Génération des avis de poursuite d'études
    • +
    • Toilettage page liste programme pédagogiques
    • +
    • Amélioration exports résultats vers Apogée
    • +
    • Amélioration calcul des ECTS
    • +
    • Possibilité d'utilisation des adresses mail personnelles des étudiant
    • +
    • Corrections diverses
    • +
    + +

    Année 2016

    +
      +
    • Import des données d'admissions depuis fichiers APB ou autres
    • +
    • Nouveau formulaire saisie des notes
    • +
    • Export des résultats vers Apogée pour un ensemble de semestre
    • +
    • Enregistrement du classement lors de l'admission
    • +
    • Modification du calcul des coefficients des UE capitalisées
    • +
    + +

    Année 2015

    +
      +
    • Exports fichiers Apogée
    • +
    • Recherche étudiants sur plusieurs départements
    • +
    • Corrections diverses
    • +
    + + +

    Année 2014

    +
      +
    • Nouvelle interface pour listes groupes, photos, feuilles d'émargement.
    • +
    +

    Année 2013

    +
      +
    • Modernisation de nombreux composants logiciels (ScoDoc 7)
    • +
    • Saisie des absences par matières
    • +
    +

    Année 2012

    +
      +
    • Table lycées d'origine avec carte google
    • +
    • Amélioration des PV de jury (logos, ...)
    • +
    • Accélération du code de calcul des semestres
    • +
    • Changement documentation en ligne (nouveau site web)
    • +
    + + +

    Année 2011

    +
      +
    • Amélioration de la présentation des bulletins de notes, et possibilité de définir de nouveaux formats
    • +
    • Possibilité de modifier les moyennes d'UE via un "bonus" (sport/culture)
    • +
    • Ajout parcours spécifique pour UCAC (Cameroun)
    • +
    • Possibilité d'indiquer des mentions sur les PV
    • +
    • Evaluations de "rattrapage"
    • +
    • Support pour installation en Linux Debian "Squeeze"
    • +
    • Corrections diverses
    • +
    + + +

    Novembre 2010

    +
      +
    • Possibilité d'indiquer des évaluations avec publication immédiate des notes (même si incomplètes)
    • +
    + +

    Octobre 2010

    +
      +
    • Nouvelle API JSON
    • +
    • Possibilité d'associer 2 étapes Apogée au même semestre
    • +
    • Table "poursuite études"
    • +
    • Possibilité d'envoyer un mail auto aux étudiants absentéistes
    • +
    + +

    Août 2010

    +
      +
    • Définitions de parcours (DUT, LP, ...) avec prise en compte des spécificités (par ex., certaines barres d'UE différentes en LP)
    • +
    + +

    Avril - Juin 2010

    +
      +
    • Formules utilisateur pour le calcul des moyennes d'UE
    • +
    • Nouveau système de notification des absences par mail
    • +
    • Affichage optionnel des valeurs mini et maxi des moyennes sur les bulletins
    • +
    • Nouveau code de décision jury semestre: "RAT" : en attente de rattrapage
    • +
    + +

    Janvier 2010

    +
      +
    • Suivez l'actualité du développement sur Twitter: @ScoDoc
    • +
    • Nouveau menu "Groupes" pour faciliter la prise en main
    • +
    • Possibilité de définir des règles ad hoc de calcul des moyennes de modules (formules)
    • +
    • Possibilité d'inclure des images (logos) dans les bulletins PDF
    • +
    • Bandeau "provisoire" sur les bulletins en cours de semestre
    • +
    • Possibilite de valider (capitaliser) une UE passee hors ScoDoc
    • +
    • Amelioration de l'édition des programmes (formations)
    • +
    • Nombreuses améliorations mineures
    • +
    + +

    Novembre 2009

    +
      +
    • Gestion des partitions et groupes en nombres quelconques
    • +
    • Nouvelle gestion des photos
    • +
    • Imports d'étudiants excel incrémentaux
    • +
    • Optimisations et petites améliorations
    • +
    + +

    Septembre 2009

    +
      +
    • Traitement de "billets d'absences" (saisis par les étudiants sur le portail)
    • +
    + +

    Juin 2009

    +
      +
    • Nouveau système plus flexibles de gestion des préférences (ou "paramètres")
    • +
    • Possiblité d'associer une nouvelle version de programme à un semestre
    • +
    • Corrections et améliorations diverses +
    + +

    Juillet 2008: version 6.0

    +
      +
    • Installeur automatisé pour Linux
    • +
    • Amélioration ergonomie (barre menu pages semestres)
    • +
    • Refonte fiche étudiant (parcours)
    • +
    • Archivage des documents (PV)
    • +
    • Nouvel affichage des notes des évaluations
    • +
    • Nombreuses corrections et améliorations
    • +
    + +

    Juin 2008

    +
      +
    • Rangs sur les bulletins
    • +
    + +

    Février 2008

    +
      +
    • Statistiques et suivis de cohortes (chiffres et graphes)
    • +
    • Nombreuses petites corrections suites aux jurys de janvier
    • +
    + +

    Janvier 2008

    +
      +
    • Personnalisation des régles de calculs notes d'option (sport, culture)
    • +
    • Edition de PV de jury individuel
    • +
    + +

    Novembre 2007

    +
      +
    • Vérification des absences aux évaluations
    • +
    • Import des photos depuis portail, trombinoscopes en PDF
    • +
    + +

    Septembre 2007

    +
      +
    • Importation des etudiants depuis étapes Apogée
    • +
    • Inscription de groupes à des modules (options ou parcours)
    • +
    • Listes de étapes Apogée (importées du portail)
    • +
    + +

    Juillet 2007

    +
      +
    • Import utilisateurs depuis Excel
    • +
    • Nouvelle gestion des passage d'un semestre à l'autre
    • +
    + +

    Juin 2007: version 5.0

    +
      +
    • Suivi des parcours et règles de décision des jurys DUT
    • +
    • Capitalisation des UEs
    • +
    • Edition des PV de jurys et courriers aux étudiants
    • +
    • Feuilles (excel) pour préparation jurys
    • +
    • Nombreuses petites améliorations
    • +
    + +

    Avril 2007

    +
      +
    • Paramètres de mise en page des bulletins en PDF
    • +
    + +

    Février 2007

    + +
      +
    • Possibilité de ne pas publier les bulletins sur le portail
    • +
    • Gestion des notes "en attente" (publication d'évaluations sans correction de toutes les copies)
    • +
    • Amélioration formulaire saisie absences, saisie absences par semestre.
    • +
    + +

    Janvier 2007

    +
      +
    • Possibilité d'initialiser les notes manquantes d'une évaluation
    • +
    • Recupération des codes NIP depuis Apogée
    • +
    • Gestion des compensations inter-semestre DUT (en cours de développement)
    • +
    • Export trombinoscope en archive zip
    • +
    + +

    Octobre 2006

    +
      +
    • Réorganisation des pages d'accueil
    • +
    • Ajout des "nouvelles" (dernières opérations), avec flux RSS
    • +
    • Import/Export XML des formations, duplication d'une formation (versions)
    • +
    • Bulletins toujours sur une seule feuille (passage à ReportLab 2.0)
    • +
    • Suppression d'un utilisateur +
    +

    Septembre 2006

    +
      +
    • Page pour suppression des groupes.
    • +
    • Amélioration gestion des utilisateurs
    • +
    • "Verrouillage" des semestres
    • +
    • Liste d'enseignants (chargés de TD) associés à un module (et pouvant saisir des notes)
    • +
    • Noms de types de groupes (TD, TP, ...) modifiables
    • +
    • Tableau rudimentaire donnant la répartition des bacs dans un semestre
    • +
    • Amélioration mise en page des listes au format excel
    • +
    • Annulation des démissions
    • +
    + +

    Juillet 2006

    +
      +
    • Dialogue permettant au directeur des études de modifier +les options d'un semestre
    • +
    • Option pour ne pas afficher les UE validées sur les bulletins
    • +
    + +

    30 juin 2006

    +
      +
    • Option pour ne pas afficher les décisions sur les bulletins
    • +
    • Génération feuilles pour préparation jury
    • +
    • Gestion des modules optionnels
    • +
    • Prise en compte note "activités culturelles ou sportives"
    • +
    • Amélioration tableau de bord semestre
    • +
    • Import listes étudiants depuis Excel (avec code Apogée)
    • +
    + +

    12 juin 2006

    +
      +
    • Formulaire dynamique d'affectation aux groupes
    • +
    • Tri des tableaux (listes, récapitulatif)
    • +
    • Export XML des infos sur un etudiant et des groupes
    • +
    + +

    12 mai 2006

    +
      +
    • Possibilité de suppression d'un semestre
    • +
    • Export XML du tableau recapitulatif des notes du semestre
    • +
    • Possibilité de supression d'une formation complète
    • +
    + +

    24 avril 2006

    +
      +
    • Export bulletins en XML (expérimental)
    • +
    • Flag "gestion_absence" sur les semestres de formation
    • +
    + +

    4 mars 2006

    +
      +
    • Formulaire d'inscription au semestre suivant.
    • +
    • Format "nombre" dans les feuilles excel exportées.
    • +
    + +

    23 février 2006

    +
      +
    • Décisions jury sur bulletins.
    • +
    + +

    17 janvier 2006

    +
      +
    • Ajout et édition d'appréciations sur les bulletins.
    • +
    +

    12 janvier 2006

    +
      +
    • Envoi des bulletins en PDF par mail aux étudiants.
    • +
    + +

    6 janvier 2006

    +
      +
    • Affichage des ex-aequos.
    • +
    • Classeurs bulletins PDF en différentes versions.
    • +
    • Corrigé gestion des notes des démissionnaires.
    • +
    + +

    1er janvier 2006

    +
      +
    • Import du projet dans Subversion / LIPN.
    • +
    • Lecture des feuilles de notes Excel.
    • +
    + +

    31 décembre 2005

    +
      +
    • Listes générées au format Excel au lieu de CSV.
    • +
    • Bug fix (création/saisie evals).
    • +
    + +

    29 décembre 2005

    +
      +
    • Affichage des moyennes de chaque groupe dans tableau de bord module. +
    + +

    26 décembre 2005

    +
      +
    • Révision inscription/édition individuelle d'étudiants.
    • +
    • Amélioration fiche étudiant (cosmétique, liste formations, actions).
    • +
    • Listings notes d'évaluations anonymes (utilité douteuse ?).
    • +
    • Amélioration formulaire saisie notes ('enter' -> champ suivant).
    • +
    + +

    24 décembre 2005

    +
      +
    • Génération de bulletins PDF +
    • +
    • Suppression de notes (permet donc de supprimer une évaluation) +
    • +
    • Bulletins en versions courtes (seulement moyennes de chaque module), longues +(toutes les notes) et intermédiaire (moyenne de chaque module plus notes dans les évaluations sélectionnées). +
    • +
    • Notes moyennes sous les barres en rouge dans le tableau récapitulatif (seuil=10 sur la moyenne générale, et 8 sur chaque UE). +
    • +
    • Colonne "groupe de TD" dans le tableau récapitulatif des notes. +
    +""" diff --git a/ZAbsences.py b/ZAbsences.py new file mode 100644 index 00000000..1bdd11b7 --- /dev/null +++ b/ZAbsences.py @@ -0,0 +1,2516 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +""" Gestion des absences (v4) + +C'est la partie la plus ancienne de ScoDoc, et elle est à revoir. + +L'API de plus bas niveau est en gros: + + AnnuleAbsencesDatesNoJust(etudid, dates) + CountAbs(etudid, debut, fin, matin=None, moduleimpl_id=None) + CountAbsJust(etudid, debut, fin, matin=None, moduleimpl_id=None) + ListeAbsJust(etudid, datedebut) [pas de fin ?] + ListeAbsNonJust(etudid, datedebut) [pas de fin ?] + ListeJustifs(etudid, datedebut, datefin=None, only_no_abs=True) + + ListeAbsJour(date, am=True, pm=True, is_abs=None, is_just=None) + ListeAbsNonJustJour(date, am=True, pm=True) + + +""" + +import urllib + +from sco_zope import * + +# --------------- + +from notesdb import * +from notes_log import log +from scolog import logdb +from sco_utils import * + +# import notes_users +from TrivialFormulator import TrivialFormulator, TF +from gen_tables import GenTable +import scolars +import sco_formsemestre +import sco_groups +import sco_groups_view +import sco_excel +import sco_abs_notification, sco_abs_views +import sco_compute_moy +import string, re +import time, calendar +from mx.DateTime import DateTime as mxDateTime +from mx.DateTime.ISO import ParseDateTimeUTC + + +def _toboolean(x): + "convert a value to boolean (ensure backward compat with OLD intranet code)" + if type(x) == type(""): + x = x.lower() + if x and x != "false": # backward compat... + return True + else: + return False + + +def MonthNbDays(month, year): + "returns nb of days in month" + if month > 7: + month = month + 1 + if month % 2: + return 31 + elif month == 2: + if calendar.isleap(year): + return 29 + else: + return 28 + else: + return 30 + + +class ddmmyyyy: + """immutable dates""" + + def __init__(self, date=None, fmt="ddmmyyyy", work_saturday=False): + self.work_saturday = work_saturday + if date is None: + return + try: + if fmt == "ddmmyyyy": + self.day, self.month, self.year = string.split(date, "/") + elif fmt == "iso": + self.year, self.month, self.day = string.split(date, "-") + else: + raise ValueError("invalid format spec. (%s)" % fmt) + self.year = string.atoi(self.year) + self.month = string.atoi(self.month) + self.day = string.atoi(self.day) + except: + raise ScoValueError("date invalide: %s" % date) + # accept years YYYY or YY, uses 1970 as pivot + if self.year < 1970: + if self.year > 100: + raise ScoInvalidDateError("Année invalide: %s" % self.year) + if self.year < 70: + self.year = self.year + 2000 + else: + self.year = self.year + 1900 + if self.month < 1 or self.month > 12: + raise ScoInvalidDateError("Mois invalide: %s" % self.month) + + if self.day < 1 or self.day > MonthNbDays(self.month, self.year): + raise ScoInvalidDateError("Jour invalide: %s" % self.day) + + # weekday in 0-6, where 0 is monday + self.weekday = calendar.weekday(self.year, self.month, self.day) + + self.time = time.mktime((self.year, self.month, self.day, 0, 0, 0, 0, 0, 0)) + + def iswork(self): + "returns true if workable day" + if self.work_saturday: + nbdays = 6 + else: + nbdays = 5 + if ( + self.weekday >= 0 and self.weekday < nbdays + ): # monday-friday or monday-saturday + return 1 + else: + return 0 + + def __repr__(self): + return "'%02d/%02d/%04d'" % (self.day, self.month, self.year) + + def __str__(self): + return "%02d/%02d/%04d" % (self.day, self.month, self.year) + + def ISO(self): + "iso8601 representation of the date" + return "%04d-%02d-%02d" % (self.year, self.month, self.day) + + def next(self, days=1): + "date for the next day (nota: may be a non workable day)" + day = self.day + days + month = self.month + year = self.year + + while day > MonthNbDays(month, year): + day = day - MonthNbDays(month, year) + month = month + 1 + if month > 12: + month = 1 + year = year + 1 + return self.__class__( + "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday + ) + + def prev(self, days=1): + "date for previous day" + day = self.day - days + month = self.month + year = self.year + while day <= 0: + month = month - 1 + if month == 0: + month = 12 + year = year - 1 + day = day + MonthNbDays(month, year) + + return self.__class__( + "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday + ) + + def next_monday(self): + "date of next monday" + return self.next((7 - self.weekday) % 7) + + def prev_monday(self): + "date of last monday, but on sunday, pick next monday" + if self.weekday == 6: + return self.next_monday() + else: + return self.prev(self.weekday) + + def __cmp__(self, other): + """return a negative integer if self < other, + zero if self == other, a positive integer if self > other""" + return int(self.time - other.time) + + def __hash__(self): + "we are immutable !" + return hash(self.time) ^ hash(str(self)) + + +# d = ddmmyyyy( '21/12/99' ) + + +def YearTable( + context, + year, + events=[], + firstmonth=9, + lastmonth=7, + halfday=0, + dayattributes="", + pad_width=8, +): + """Generate a calendar table + events = list of tuples (date, text, color, href [,halfday]) + where date is a string in ISO format (yyyy-mm-dd) + halfday is boolean (true: morning, false: afternoon) + text = text to put in calendar (must be short, 1-5 cars) (optional) + if halfday, generate 2 cells per day (morning, afternoon) + """ + T = [ + '' + ] + T.append("") + month = firstmonth + while 1: + T.append('") + if month == lastmonth: + break + month = month + 1 + if month > 12: + month = 1 + year = year + 1 + T.append("
    ') + T.append(MonthTableHead(month)) + T.append( + MonthTableBody( + month, + year, + events, + halfday, + dayattributes, + context.is_work_saturday(), + pad_width=pad_width, + ) + ) + T.append(MonthTableTail()) + T.append("
    ") + return string.join(T, "\n") + + +# --------------- + + +class ZAbsences( + ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit +): + + "ZAbsences object" + + meta_type = "ZAbsences" + security = ClassSecurityInfo() + + # This is the list of the methods associated to 'tabs' in the ZMI + # Be aware that The first in the list is the one shown by default, so if + # the 'View' tab is the first, you will never see your tabs by cliquing + # on the object. + manage_options = ( + ({"label": "Contents", "action": "manage_main"},) + + PropertyManager.manage_options # add the 'Properties' tab + + ({"label": "View", "action": "index_html"},) + + Item.manage_options # add the 'Undo' & 'Owner' tab + + RoleManager.manage_options # add the 'Security' tab + ) + + # no permissions, only called from python + def __init__(self, id, title): + "initialise a new instance" + self.id = id + self.title = title + + # The form used to edit this object + def manage_editZAbsences(self, title, RESPONSE=None): + "Changes the instance values" + self.title = title + self._p_changed = 1 + RESPONSE.redirect("manage_editForm") + + # -------------------------------------------------------------------- + # + # ABSENCES (top level) + # + # -------------------------------------------------------------------- + # used to view content of the object + security.declareProtected(ScoView, "index_html") + index_html = sco_abs_views.absences_index_html + + security.declareProtected(ScoView, "EtatAbsences") + EtatAbsences = sco_abs_views.EtatAbsences + + security.declareProtected(ScoView, "CalAbs") + CalAbs = sco_abs_views.CalAbs + + security.declareProtected(ScoAbsChange, "SignaleAbsenceEtud") + SignaleAbsenceEtud = sco_abs_views.SignaleAbsenceEtud + security.declareProtected(ScoAbsChange, "doSignaleAbsence") + doSignaleAbsence = sco_abs_views.doSignaleAbsence + + security.declareProtected(ScoAbsChange, "JustifAbsenceEtud") + JustifAbsenceEtud = sco_abs_views.JustifAbsenceEtud + security.declareProtected(ScoAbsChange, "doJustifAbsence") + doJustifAbsence = sco_abs_views.doJustifAbsence + + security.declareProtected(ScoAbsChange, "AnnuleAbsenceEtud") + AnnuleAbsenceEtud = sco_abs_views.AnnuleAbsenceEtud + security.declareProtected(ScoAbsChange, "doAnnuleAbsence") + doAnnuleAbsence = sco_abs_views.doAnnuleAbsence + security.declareProtected(ScoAbsChange, "doAnnuleJustif") + doAnnuleJustif = sco_abs_views.doAnnuleJustif + + security.declareProtected(ScoView, "ListeAbsEtud") + ListeAbsEtud = sco_abs_views.ListeAbsEtud + + # -------------------------------------------------------------------- + # + # SQL METHODS + # + # -------------------------------------------------------------------- + + def _AddAbsence( + self, + etudid, + jour, + matin, + estjust, + REQUEST, + description=None, + moduleimpl_id=None, + ): + "Ajoute une absence dans la bd" + # unpublished + if self._isFarFutur(jour): + raise ScoValueError("date absence trop loin dans le futur !") + estjust = _toboolean(estjust) + matin = _toboolean(matin) + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "insert into absences (etudid,jour,estabs,estjust,matin,description, moduleimpl_id) values (%(etudid)s, %(jour)s, TRUE, %(estjust)s, %(matin)s, %(description)s, %(moduleimpl_id)s )", + vars(), + ) + logdb( + REQUEST, + cnx, + "AddAbsence", + etudid=etudid, + msg="JOUR=%(jour)s,MATIN=%(matin)s,ESTJUST=%(estjust)s,description=%(description)s,moduleimpl_id=%(moduleimpl_id)s" + % vars(), + ) + cnx.commit() + invalidateAbsEtudDate(self, etudid, jour) + sco_abs_notification.abs_notify(self, etudid, jour) + + def _AddJustif(self, etudid, jour, matin, REQUEST, description=None): + "Ajoute un justificatif dans la base" + # unpublished + if self._isFarFutur(jour): + raise ScoValueError("date justificatif trop loin dans le futur !") + matin = _toboolean(matin) + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "insert into absences (etudid,jour,estabs,estjust,matin, description) values (%(etudid)s,%(jour)s, FALSE, TRUE, %(matin)s, %(description)s )", + vars(), + ) + logdb( + REQUEST, + cnx, + "AddJustif", + etudid=etudid, + msg="JOUR=%(jour)s,MATIN=%(matin)s" % vars(), + ) + cnx.commit() + invalidateAbsEtudDate(self, etudid, jour) + + def _AnnuleAbsence(self, etudid, jour, matin, moduleimpl_id=None, REQUEST=None): + """Annule une absence ds base + Si moduleimpl_id, n'annule que pour ce module + """ + # unpublished + matin = _toboolean(matin) + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + req = "delete from absences where jour=%(jour)s and matin=%(matin)s and etudid=%(etudid)s and estabs" + if moduleimpl_id: + req += " and moduleimpl_id=%(moduleimpl_id)s" + cursor.execute(req, vars()) + logdb( + REQUEST, + cnx, + "AnnuleAbsence", + etudid=etudid, + msg="JOUR=%(jour)s,MATIN=%(matin)s,moduleimpl_id=%(moduleimpl_id)s" + % vars(), + ) + cnx.commit() + invalidateAbsEtudDate(self, etudid, jour) + + def _AnnuleJustif(self, etudid, jour, matin, REQUEST=None): + "Annule un justificatif" + # unpublished + matin = _toboolean(matin) + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "delete from absences where jour=%(jour)s and matin=%(matin)s and etudid=%(etudid)s and ESTJUST AND NOT ESTABS", + vars(), + ) + cursor.execute( + "update absences set estjust=false where jour=%(jour)s and matin=%(matin)s and etudid=%(etudid)s", + vars(), + ) + logdb( + REQUEST, + cnx, + "AnnuleJustif", + etudid=etudid, + msg="JOUR=%(jour)s,MATIN=%(matin)s" % vars(), + ) + cnx.commit() + invalidateAbsEtudDate(self, etudid, jour) + + # Fonction inutile à supprimer (gestion moduleimpl_id incorrecte): + # def _AnnuleAbsencesPeriodNoJust(self, etudid, datedebut, datefin, + # moduleimpl_id=None, REQUEST=None): + # """Supprime les absences entre ces dates (incluses). + # mais ne supprime pas les justificatifs. + # """ + # # unpublished + # cnx = self.GetDBConnexion() + # cursor = cnx.cursor(cursor_factory=ScoDocCursor) + # # supr les absences non justifiees + # cursor.execute("delete from absences where etudid=%(etudid)s and (not estjust) and moduleimpl_id=(moduleimpl_id)s and jour BETWEEN %(datedebut)s AND %(datefin)s", + # vars() ) + # # s'assure que les justificatifs ne sont pas "absents" + # cursor.execute("update absences set estabs=FALSE where etudid=%(etudid)s and jour and moduleimpl_id=(moduleimpl_id)s BETWEEN %(datedebut)s AND %(datefin)s", vars()) + # logdb(REQUEST, cnx, 'AnnuleAbsencesPeriodNoJust', etudid=etudid, + # msg='%(datedebut)s - %(datefin)s - (moduleimpl_id)s'%vars()) + # cnx.commit() + # invalidateAbsEtudDate(self, etudid, datedebut) + # invalidateAbsEtudDate(self, etudid, datefin) # si un semestre commence apres datedebut et termine avant datefin, il ne sera pas invalide. Tant pis ;-) + + security.declareProtected(ScoAbsChange, "AnnuleAbsencesDatesNoJust") + + def AnnuleAbsencesDatesNoJust( + self, etudid, dates, moduleimpl_id=None, REQUEST=None + ): + """Supprime les absences aux dates indiquées + mais ne supprime pas les justificatifs. + """ + # log('AnnuleAbsencesDatesNoJust: moduleimpl_id=%s' % moduleimpl_id) + if not dates: + return + date0 = dates[0] + if len(date0.split(":")) == 2: + # am/pm is present + for date in dates: + jour, ampm = date.split(":") + if ampm == "am": + matin = 1 + elif ampm == "pm": + matin = 0 + else: + raise ValueError("invalid ampm !") + self._AnnuleAbsence(etudid, jour, matin, moduleimpl_id, REQUEST) + return + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + # supr les absences non justifiees + for date in dates: + cursor.execute( + "delete from absences where etudid=%(etudid)s and (not estjust) and jour=%(date)s and moduleimpl_id=%(moduleimpl_id)s", + vars(), + ) + invalidateAbsEtudDate(self, etudid, date) + # s'assure que les justificatifs ne sont pas "absents" + for date in dates: + cursor.execute( + "update absences set estabs=FALSE where etudid=%(etudid)s and jour=%(date)s and moduleimpl_id=%(moduleimpl_id)s", + vars(), + ) + if dates: + date0 = dates[0] + else: + date0 = None + if len(dates) > 1: + date1 = dates[1] + else: + date1 = None + logdb( + REQUEST, + cnx, + "AnnuleAbsencesDatesNoJust", + etudid=etudid, + msg="%s - %s - %s" % (date0, date1, moduleimpl_id), + ) + cnx.commit() + + security.declareProtected(ScoView, "CountAbs") + + def CountAbs(self, etudid, debut, fin, matin=None, moduleimpl_id=None): + """CountAbs + matin= 1 ou 0. + """ + if matin != None: + matin = _toboolean(matin) + ismatin = " AND A.MATIN = %(matin)s " + else: + ismatin = "" + if moduleimpl_id: + modul = " AND A.MODULEIMPL_ID = %(moduleimpl_id)s " + else: + modul = "" + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + """SELECT COUNT(*) AS NbAbs FROM ( + SELECT DISTINCT A.JOUR, A.MATIN + FROM ABSENCES A + WHERE A.ETUDID = %(etudid)s + AND A.ESTABS""" + + ismatin + + modul + + """ + AND A.JOUR BETWEEN %(debut)s AND %(fin)s + ) AS tmp + """, + vars(), + ) + res = cursor.fetchone()[0] + return res + + security.declareProtected(ScoView, "CountAbsJust") + + def CountAbsJust(self, etudid, debut, fin, matin=None, moduleimpl_id=None): + if matin != None: + matin = _toboolean(matin) + ismatin = " AND A.MATIN = %(matin)s " + else: + ismatin = "" + if moduleimpl_id: + modul = " AND A.MODULEIMPL_ID = %(moduleimpl_id)s " + else: + modul = "" + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + """SELECT COUNT(*) AS NbAbsJust FROM ( + SELECT DISTINCT A.JOUR, A.MATIN + FROM ABSENCES A, ABSENCES B + WHERE A.ETUDID = %(etudid)s + AND A.ETUDID = B.ETUDID + AND A.JOUR = B.JOUR AND A.MATIN = B.MATIN + AND A.JOUR BETWEEN %(debut)s AND %(fin)s + AND A.ESTABS AND (A.ESTJUST OR B.ESTJUST)""" + + ismatin + + modul + + """ +) AS tmp + """, + vars(), + ) + res = cursor.fetchone()[0] + return res + + def _ListeAbsDate(self, etudid, beg_date, end_date): + # Liste des absences et justifs entre deux dates + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + """SELECT jour, matin, estabs, estjust, description FROM ABSENCES A + WHERE A.ETUDID = %(etudid)s + AND A.jour >= %(beg_date)s + AND A.jour <= %(end_date)s + """, + vars(), + ) + Abs = cursor.dictfetchall() + # log('ListeAbsDate: abs=%s' % Abs) + # remove duplicates + A = {} # { (jour, matin) : abs } + for a in Abs: + jour, matin = a["jour"], a["matin"] + if (jour, matin) in A: + # garde toujours la description + a["description"] = a["description"] or A[(jour, matin)]["description"] + # et la justif: + a["estjust"] = a["estjust"] or A[(jour, matin)]["estjust"] + a["estabs"] = a["estabs"] or A[(jour, matin)]["estabs"] + A[(jour, matin)] = a + else: + A[(jour, matin)] = a + if A[(jour, matin)]["description"] is None: + A[(jour, matin)]["description"] = "" + # add hours: matin = 8:00 - 12:00, apresmidi = 12:00 - 18:00 + dat = "%04d-%02d-%02d" % (a["jour"].year, a["jour"].month, a["jour"].day) + if a["matin"]: + A[(jour, matin)]["begin"] = dat + " 08:00:00" + A[(jour, matin)]["end"] = dat + " 11:59:59" + else: + A[(jour, matin)]["begin"] = dat + " 12:00:00" + A[(jour, matin)]["end"] = dat + " 17:59:59" + # sort + R = A.values() + R.sort(key=lambda x: (x["begin"])) + # log('R=%s' % R) + return R + + security.declareProtected(ScoView, "ListeAbsJust") + + def ListeAbsJust(self, etudid, datedebut): + "Liste des absences justifiees (par ordre chronologique)" + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + """SELECT DISTINCT A.ETUDID, A.JOUR, A.MATIN FROM ABSENCES A, ABSENCES B + WHERE A.ETUDID = %(etudid)s + AND A.ETUDID = B.ETUDID + AND A.JOUR = B.JOUR AND A.MATIN = B.MATIN AND A.JOUR >= %(datedebut)s + AND A.ESTABS AND (A.ESTJUST OR B.ESTJUST) + ORDER BY A.JOUR + """, + vars(), + ) + A = cursor.dictfetchall() + for a in A: + a["description"] = self._GetAbsDescription(a, cursor=cursor) + return A + + security.declareProtected(ScoView, "ListeAbsNonJust") + + def ListeAbsNonJust(self, etudid, datedebut): + "Liste des absences NON justifiees (par ordre chronologique)" + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + """SELECT ETUDID, JOUR, MATIN FROM ABSENCES A + WHERE A.ETUDID = %(etudid)s + AND A.estabs + AND A.jour >= %(datedebut)s + EXCEPT SELECT ETUDID, JOUR, MATIN FROM ABSENCES B + WHERE B.estjust + AND B.ETUDID = %(etudid)s + ORDER BY JOUR + """, + vars(), + ) + A = cursor.dictfetchall() + for a in A: + a["description"] = self._GetAbsDescription(a, cursor=cursor) + return A + + security.declareProtected(ScoView, "ListeAbsJust") + + def ListeJustifs(self, etudid, datedebut, datefin=None, only_no_abs=False): + """Liste des justificatifs (sans absence relevée) à partir d'une date, + ou, si datefin spécifié, entre deux dates. + Si only_no_abs: seulement les justificatifs correspondant aux jours sans absences relevées. + """ + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + req = """SELECT DISTINCT ETUDID, JOUR, MATIN FROM ABSENCES A + WHERE A.ETUDID = %(etudid)s + AND A.ESTJUST + AND A.JOUR >= %(datedebut)s""" + if datefin: + req += """AND A.JOUR <= %(datefin)s""" + if only_no_abs: + req += """ + EXCEPT SELECT ETUDID, JOUR, MATIN FROM ABSENCES B + WHERE B.estabs + AND B.ETUDID = %(etudid)s + """ + cursor.execute(req, vars()) + A = cursor.dictfetchall() + for a in A: + a["description"] = self._GetAbsDescription(a, cursor=cursor) + + return A + + def _GetAbsDescription(self, a, cursor=None): + "Description associee a l'absence" + if not cursor: + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + a = a.copy() + # a['jour'] = a['jour'].date() + if a["matin"]: # devrait etre booleen... :-( + a["matin"] = True + else: + a["matin"] = False + cursor.execute( + """select * from absences where etudid=%(etudid)s and jour=%(jour)s and matin=%(matin)s order by entry_date desc""", + a, + ) + A = cursor.dictfetchall() + desc = None + module = "" + for a in A: + if a["description"]: + desc = a["description"] + if a["moduleimpl_id"] and a["moduleimpl_id"] != "NULL": + # Trouver le nom du module + Mlist = self.Notes.do_moduleimpl_withmodule_list( + moduleimpl_id=a["moduleimpl_id"] + ) + if Mlist: + M = Mlist[0] + module += "%s " % M["module"]["code"] + + if desc: + return "(%s) %s" % (desc, module) + return desc + if module: + return module + return "" + + security.declareProtected(ScoView, "ListeAbsJour") + + def ListeAbsJour(self, date, am=True, pm=True, is_abs=True, is_just=None): + """Liste des absences et/ou justificatifs ce jour. + is_abs: None (peu importe), True, False + is_just: idem + """ + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + req = """SELECT DISTINCT etudid, jour, matin FROM ABSENCES A + WHERE A.jour = %(date)s + """ + if is_abs != None: + req += " AND A.estabs = %(is_abs)s" + if is_just != None: + req += " AND A.estjust = %(is_just)s" + if not am: + req += " AND NOT matin " + if not pm: + req += " AND matin" + + cursor.execute(req, {"date": date, "is_just": is_just, "is_abs": is_abs}) + A = cursor.dictfetchall() + for a in A: + a["description"] = self._GetAbsDescription(a, cursor=cursor) + return A + + security.declareProtected(ScoView, "ListeAbsNonJustJour") + + def ListeAbsNonJustJour(self, date, am=True, pm=True): + "Liste des absences non justifiees ce jour" + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + reqa = "" + if not am: + reqa += " AND NOT matin " + if not pm: + reqa += " AND matin " + req = ( + """SELECT etudid, jour, matin FROM ABSENCES A + WHERE A.estabs + AND A.jour = %(date)s + """ + + reqa + + """EXCEPT SELECT etudid, jour, matin FROM ABSENCES B + WHERE B.estjust AND B.jour = %(date)s""" + + reqa + ) + + cursor.execute(req, {"date": date}) + A = cursor.dictfetchall() + for a in A: + a["description"] = self._GetAbsDescription(a, cursor=cursor) + return A + + security.declareProtected(ScoAbsChange, "doSignaleAbsenceGrSemestre") + + def doSignaleAbsenceGrSemestre( + self, + moduleimpl_id=None, + abslist=[], + dates="", + etudids="", + destination=None, + REQUEST=None, + ): + """Enregistre absences aux dates indiquees (abslist et dates). + dates est une liste de dates ISO (séparées par des ','). + Efface les absences aux dates indiquées par dates, + ou bien ajoute celles de abslist. + """ + if etudids: + etudids = etudids.split(",") + else: + etudids = [] + if dates: + dates = dates.split(",") + else: + dates = [] + + # 1- Efface les absences + if dates: + for etudid in etudids: + self.AnnuleAbsencesDatesNoJust(etudid, dates, moduleimpl_id, REQUEST) + return "Absences effacées" + + # 2- Ajoute les absences + if abslist: + self._add_abslist(abslist, REQUEST, moduleimpl_id) + return "Absences ajoutées" + + return "" + + def _add_abslist(self, abslist, REQUEST, moduleimpl_id=None): + for a in abslist: + etudid, jour, ampm = a.split(":") + if ampm == "am": + matin = 1 + elif ampm == "pm": + matin = 0 + else: + raise ValueError("invalid ampm !") + # ajoute abs si pas deja absent + if self.CountAbs(etudid, jour, jour, matin, moduleimpl_id) == 0: + self._AddAbsence(etudid, jour, matin, 0, REQUEST, "", moduleimpl_id) + + # + security.declareProtected(ScoView, "CalSelectWeek") + + def CalSelectWeek(self, year=None, REQUEST=None): + "display calendar allowing week selection" + if not year: + year = AnneeScolaire(REQUEST) + sems = sco_formsemestre.do_formsemestre_list(self) + if not sems: + js = "" + else: + js = 'onmouseover="highlightweek(this);" onmouseout="deselectweeks();" onclick="wclick(this);"' + C = YearTable(self, int(year), dayattributes=js) + return C + + # --- Misc tools.... ------------------ + + def _isFarFutur(self, jour): + # check si jour est dans le futur "lointain" + # pour autoriser les saisies dans le futur mais pas a plus de 6 mois + y, m, d = [int(x) for x in jour.split("-")] + j = datetime.date(y, m, d) + # 6 mois ~ 182 jours: + return j - datetime.date.today() > datetime.timedelta(182) + + security.declareProtected(ScoView, "is_work_saturday") + + def is_work_saturday(self): + "Vrai si le samedi est travaillé" + return int(self.get_preference("work_saturday")) + + def day_names(self): + """Returns week day names. + If work_saturday property is set, include saturday + """ + if self.is_work_saturday(): + return ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"] + else: + return ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi"] + + security.declareProtected(ScoView, "ListMondays") + + def ListMondays(self, year=None, REQUEST=None): + """return list of mondays (ISO dates), from september to june + """ + if not year: + year = AnneeScolaire(REQUEST) + d = ddmmyyyy("1/9/%d" % year, work_saturday=self.is_work_saturday()) + while d.weekday != 0: + d = d.next() + end = ddmmyyyy("1/7/%d" % (year + 1), work_saturday=self.is_work_saturday()) + L = [d] + while d < end: + d = d.next(days=7) + L.append(d) + return map(lambda x: x.ISO(), L) + + security.declareProtected(ScoView, "NextISODay") + + def NextISODay(self, date): + "return date after date" + d = ddmmyyyy(date, fmt="iso", work_saturday=self.is_work_saturday()) + return d.next().ISO() + + security.declareProtected(ScoView, "DateRangeISO") + + def DateRangeISO(self, date_beg, date_end, workable=1): + """returns list of dates in [date_beg,date_end] + workable = 1 => keeps only workable days""" + if not date_beg: + raise ScoValueError("pas de date spécifiée !") + if not date_end: + date_end = date_beg + r = [] + cur = ddmmyyyy(date_beg, work_saturday=self.is_work_saturday()) + end = ddmmyyyy(date_end, work_saturday=self.is_work_saturday()) + while cur <= end: + if (not workable) or cur.iswork(): + r.append(cur) + cur = cur.next() + + return map(lambda x: x.ISO(), r) + + # ------------ HTML Interfaces + security.declareProtected(ScoAbsChange, "SignaleAbsenceGrHebdo") + + def SignaleAbsenceGrHebdo( + self, datelundi, group_ids=[], destination="", moduleimpl_id=None, REQUEST=None + ): + "Saisie hebdomadaire des absences" + if not moduleimpl_id: + moduleimp_id = None + + groups_infos = sco_groups_view.DisplayedGroupsInfos( + self, group_ids, REQUEST=REQUEST + ) + if not groups_infos.members: + return ( + self.sco_header(page_title="Saisie des absences", REQUEST=REQUEST) + + "

    Aucun étudiant !

    " + + self.sco_footer(REQUEST) + ) + + base_url = "SignaleAbsenceGrHebdo?datelundi=%s&%s&destination=%s" % ( + datelundi, + groups_infos.groups_query_args, + urllib.quote(destination), + ) + + formsemestre_id = groups_infos.formsemestre_id + etuds = [ + self.getEtudInfo(etudid=m["etudid"], filled=True)[0] + for m in groups_infos.members + ] + nt = self.Notes._getNotesCache().get_NotesTable(self.Notes, formsemestre_id) + sem = sco_formsemestre.do_formsemestre_list( + self, {"formsemestre_id": formsemestre_id} + )[0] + + # calcule dates jours de cette semaine + datessem = [DateDMYtoISO(datelundi)] + for jour in self.day_names()[1:]: + datessem.append(self.NextISODay(datessem[-1])) + + # + if groups_infos.tous_les_etuds_du_sem: + gr_tit = "en" + else: + if len(groups_infos.group_ids) > 1: + p = "des groupes" + else: + p = "du groupe" + gr_tit = ( + p + '' + groups_infos.groups_titles + "" + ) + + H = [ + self.sco_header( + page_title="Saisie hebdomadaire des absences", + init_qtip=True, + javascripts=["js/etud_info.js", "js/abs_ajax.js"], + no_side_bar=1, + REQUEST=REQUEST, + ), + """
    +

    Saisie des absences %s %s, + semaine du lundi %s

    + +

    Annuler

    + +

    +

    + """ + % (gr_tit, sem["titre_num"], datelundi, REQUEST.URL0), + ] + # + modimpls_list = [] + # Initialize with first student + ues = nt.get_ues(etudid=etuds[0]["etudid"]) + for ue in ues: + modimpls_list += nt.get_modimpls(ue_id=ue["ue_id"]) + + # Add modules other students are subscribed to + for etud in etuds[1:]: + modimpls_etud = [] + ues = nt.get_ues(etudid=etud["etudid"]) + for ue in ues: + modimpls_etud += nt.get_modimpls(ue_id=ue["ue_id"]) + modimpls_list += [m for m in modimpls_etud if m not in modimpls_list] + + menu_module = "" + for modimpl in modimpls_list: + if modimpl["moduleimpl_id"] == moduleimpl_id: + sel = "selected" + else: + sel = "" + menu_module += ( + """\n""" + % { + "modimpl_id": modimpl["moduleimpl_id"], + "modname": modimpl["module"]["code"] + + " " + + (modimpl["module"]["abbrev"] or modimpl["module"]["titre"]), + "sel": sel, + } + ) + if moduleimpl_id: + sel = "" + else: + sel = "selected" # aucun module specifie + + H.append( + """ + Module concerné par ces absences (optionnel): +

    """ + % {"menu_module": menu_module, "url": base_url, "sel": sel} + ) + + H += self._gen_form_saisie_groupe( + etuds, self.day_names(), datessem, destination, None, moduleimpl_id + ) + + H.append(self.sco_footer(REQUEST)) + return "\n".join(H) + + security.declareProtected(ScoAbsChange, "SignaleAbsenceGrSemestre") + + def SignaleAbsenceGrSemestre( + self, + datedebut, + datefin, + destination="", + group_ids=[], # list of groups to display + nbweeks=4, # ne montre que les nbweeks dernieres semaines + moduleimpl_id=None, + REQUEST=None, + ): + """Saisie des absences sur une journée sur un semestre (ou intervalle de dates) entier + """ + # log('SignaleAbsenceGrSemestre: moduleimpl_id=%s destination=%s' % (moduleimpl_id, destination)) + groups_infos = sco_groups_view.DisplayedGroupsInfos( + self, group_ids, REQUEST=REQUEST + ) + if not groups_infos.members: + return ( + self.sco_header(page_title="Saisie des absences", REQUEST=REQUEST) + + "

    Aucun étudiant !

    " + + self.sco_footer(REQUEST) + ) + + formsemestre_id = groups_infos.formsemestre_id + etuds = [ + self.getEtudInfo(etudid=m["etudid"], filled=True)[0] + for m in groups_infos.members + ] + + if not moduleimpl_id: + moduleimp_id = None + base_url_noweeks = ( + "SignaleAbsenceGrSemestre?datedebut=%s&datefin=%s&%s&destination=%s" + % ( + datedebut, + datefin, + groups_infos.groups_query_args, + urllib.quote(destination), + ) + ) + base_url = ( + base_url_noweeks + "&nbweeks=%s" % nbweeks + ) # sans le moduleimpl_id + + if etuds: + nt = self.Notes._getNotesCache().get_NotesTable(self.Notes, formsemestre_id) + sem = sco_formsemestre.do_formsemestre_list( + self, {"formsemestre_id": formsemestre_id} + )[0] + + jourdebut = ddmmyyyy(datedebut, work_saturday=self.is_work_saturday()) + jourfin = ddmmyyyy(datefin, work_saturday=self.is_work_saturday()) + today = ddmmyyyy( + time.strftime("%d/%m/%Y", time.localtime()), + work_saturday=self.is_work_saturday(), + ) + today.next() + if jourfin > today: # ne propose jamais les semaines dans le futur + jourfin = today + if jourdebut > today: + raise ScoValueError("date de début dans le futur (%s) !" % jourdebut) + # + if not jourdebut.iswork() or jourdebut > jourfin: + raise ValueError( + "date debut invalide (%s, ouvrable=%d)" + % (str(jourdebut), jourdebut.iswork()) + ) + # calcule dates + dates = [] # ddmmyyyy instances + d = ddmmyyyy(datedebut, work_saturday=self.is_work_saturday()) + while d <= jourfin: + dates.append(d) + d = d.next(7) # avance d'une semaine + # + msg = "Montrer seulement les 4 dernières semaines" + nwl = 4 + if nbweeks: + nbweeks = int(nbweeks) + if nbweeks > 0: + dates = dates[-nbweeks:] + msg = "Montrer toutes les semaines" + nwl = 0 + url_link_semaines = base_url_noweeks + "&nbweeks=%s" % nwl + if moduleimpl_id: + url_link_semaines += "&moduleimpl_id=" + moduleimpl_id + # + colnames = [str(x) for x in dates] + dates = [x.ISO() for x in dates] + dayname = self.day_names()[jourdebut.weekday] + + if groups_infos.tous_les_etuds_du_sem: + gr_tit = "en" + else: + if len(groups_infos.group_ids) > 1: + p = "des groupes" + else: + p = "du groupe" + gr_tit = ( + p + '' + groups_infos.groups_titles + "" + ) + + H = [ + self.sco_header( + page_title="Saisie des absences", + init_qtip=True, + javascripts=["js/etud_info.js", "js/abs_ajax.js"], + no_side_bar=1, + REQUEST=REQUEST, + ), + """
    +

    Saisie des absences %s %s, + les %s

    +

    + %s + + """ + % (gr_tit, sem["titre_num"], dayname, url_link_semaines, msg), + ] + # + if etuds: + modimpls_list = [] + # Initialize with first student + ues = nt.get_ues(etudid=etuds[0]["etudid"]) + for ue in ues: + modimpls_list += nt.get_modimpls(ue_id=ue["ue_id"]) + + # Add modules other students are subscribed to + for etud in etuds[1:]: + modimpls_etud = [] + ues = nt.get_ues(etudid=etud["etudid"]) + for ue in ues: + modimpls_etud += nt.get_modimpls(ue_id=ue["ue_id"]) + modimpls_list += [m for m in modimpls_etud if m not in modimpls_list] + + menu_module = "" + for modimpl in modimpls_list: + if modimpl["moduleimpl_id"] == moduleimpl_id: + sel = "selected" + else: + sel = "" + menu_module += ( + """\n""" + % { + "modimpl_id": modimpl["moduleimpl_id"], + "modname": modimpl["module"]["code"] + + " " + + (modimpl["module"]["abbrev"] or modimpl["module"]["titre"]), + "sel": sel, + } + ) + if moduleimpl_id: + sel = "" + else: + sel = "selected" # aucun module specifie + H.append( + """

    + Module concerné par ces absences (optionnel): +

    """ + % {"menu_module": menu_module, "url": base_url, "sel": sel} + ) + + H += self._gen_form_saisie_groupe( + etuds, colnames, dates, destination, dayname, moduleimpl_id + ) + H.append(self.sco_footer(REQUEST)) + return "\n".join(H) + + def _gen_form_saisie_groupe( + self, etuds, colnames, dates, destination="", dayname="", moduleimpl_id=None + ): + H = [ + """ + +
    +
    + + + """ + % len(etuds) + ] + # Titres colonnes + if dayname: + for jour in colnames: + H.append( + '" + ) + H.append("") + + for jour in colnames: + H.append( + '" + ) + + H.append("") + H.append("" * len(colnames)) + H.append("") + # + if not etuds: + H.append( + '' + ) + i = 1 + for etud in etuds: + i += 1 + etudid = etud["etudid"] + # UE capitalisee dans semestre courant ? + cap = [] + if etud["cursem"]: + nt = self.Notes._getNotesCache().get_NotesTable( + self.Notes, etud["cursem"]["formsemestre_id"] + ) # > get_ues, get_etud_ue_status + for ue in nt.get_ues(): + status = nt.get_etud_ue_status(etudid, ue["ue_id"]) + if status["is_capitalized"]: + cap.append(ue["acronyme"]) + if cap: + capstr = ' (%s cap.)' % ", ".join(cap) + else: + capstr = "" + + tr_class = ("row_1", "row_2", "row_3")[i % 3] + td_matin_class = ("matin_1", "matin_2", "matin_3")[i % 3] + + H.append( + '' + % (tr_class, etudid, etudid, etud["nomprenom"], capstr) + ) + for date in dates: + # matin + if self.CountAbs(etudid, date, date, True, moduleimpl_id=moduleimpl_id): + checked = "checked" + else: + checked = "" + H.append( + '' + % ( + td_matin_class, + etudid + ":" + date + ":" + "am", + checked, + etudid, + date + ":am", + ) + ) + # apres midi + if self.CountAbs( + etudid, date, date, False, moduleimpl_id=moduleimpl_id + ): + checked = "checked" + else: + checked = "" + H.append( + '' + % (etudid + ":" + date + ":" + "pm", checked, etudid, date + ":pm") + ) + H.append("") + H.append("
    %d étudiants' + + dayname + + "
     ' + + jour + + "
     AMPM
    Aucun étudiant inscrit !
    %s%s
    ") + # place la liste des etudiants et les dates pour pouvoir effacer les absences + H.append( + '' + % ",".join([etud["etudid"] for etud in etuds]) + ) + H.append('' % dates[0]) + H.append('' % dates[-1]) + H.append('' % ",".join(dates)) + H.append( + '' + % urllib.quote(destination) + ) + # + # version pour formulaire avec AJAX (Yann LB) + H.append( + """ +

    +

    + +

    +
    +

    Les cases cochées correspondent à des absences. + Les absences saisies ne sont pas justifiées (sauf si un justificatif a été entré + par ailleurs). +

    Si vous "décochez" une case, l'absence correspondante sera supprimée. + Attention, les modifications sont automatiquement entregistrées au fur et à mesure. +

    + """ + % destination + ) + return H + + def _TablesAbsEtud( + self, + etudid, + datedebut, + with_evals=True, + format="html", + absjust_only=0, + REQUEST=None, + ): + """Tables des absences justifiees et non justifiees d'un étudiant sur l'année en cours + """ + absjust = self.ListeAbsJust(etudid=etudid, datedebut=datedebut) + absnonjust = self.ListeAbsNonJust(etudid=etudid, datedebut=datedebut) + # examens ces jours là ? + if with_evals: + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + for a in absnonjust + absjust: + cursor.execute( + """select eval.* + from notes_evaluation eval, notes_moduleimpl_inscription mi, notes_moduleimpl m + where eval.jour = %(jour)s and eval.moduleimpl_id = m.moduleimpl_id + and mi.moduleimpl_id = m.moduleimpl_id and mi.etudid = %(etudid)s""", + {"jour": a["jour"].strftime("%Y-%m-%d"), "etudid": etudid}, + ) + a["evals"] = cursor.dictfetchall() + cursor.execute( + """SELECT mi.moduleimpl_id + from absences abs, notes_moduleimpl_inscription mi, notes_moduleimpl m + where abs.matin = %(matin)s and abs.jour = %(jour)s and abs.etudid=%(etudid)s and abs.moduleimpl_id=mi.moduleimpl_id and mi.moduleimpl_id=m.moduleimpl_id + and mi.etudid = %(etudid)s""", + { + "matin": bool(a["matin"]), + "jour": a["jour"].strftime("%Y-%m-%d"), + "etudid": etudid, + }, + ) + a["absent"] = cursor.dictfetchall() + + def matin(x): + if x: + return "matin" + else: + return "après midi" + + def descr_exams(a): + if not a.has_key("evals"): + return "" + ex = [] + for ev in a["evals"]: + mod = self.Notes.do_moduleimpl_withmodule_list( + moduleimpl_id=ev["moduleimpl_id"] + )[0] + if format == "html": + ex.append( + '%s' + % (mod["moduleimpl_id"], mod["module"]["code"]) + ) + else: + ex.append(mod["module"]["code"]) + if ex: + return ", ".join(ex) + return "" + + def descr_abs(a): + ex = [] + for ev in a.get("absent", []): + mod = self.Notes.do_moduleimpl_withmodule_list( + moduleimpl_id=ev["moduleimpl_id"] + )[0] + if format == "html": + ex.append( + '%s' + % (mod["moduleimpl_id"], mod["module"]["code"]) + ) + else: + ex.append(mod["module"]["code"]) + if ex: + return ", ".join(ex) + return "" + + # ajoute date formatée et évaluations + for L in (absnonjust, absjust): + for a in L: + if with_evals: + a["exams"] = descr_exams(a) + a["datedmy"] = a["jour"].strftime("%d/%m/%Y") + a["matin_o"] = int(a["matin"]) + a["matin"] = matin(a["matin"]) + index = a["description"].find(")") + if index != -1: + a["motif"] = a["description"][1:index] + else: + a["motif"] = "" + a["description"] = descr_abs(a) or "" + + # ajoute lien pour justifier + if format == "html": + for a in absnonjust: + a["justlink"] = "justifier" + a["_justlink_target"] = ( + "doJustifAbsence?etudid=%s&datedebut=%s&datefin=%s&demijournee=%s" + % (etudid, a["datedmy"], a["datedmy"], a["matin_o"]) + ) + # + titles = { + "datedmy": "Date", + "matin": "", + "exams": "Examens ce jour", + "justlink": "", + "description": "Modules", + "motif": "Motif", + } + columns_ids = ["datedmy", "matin"] + if with_evals: + columns_ids.append("exams") + + columns_ids.append("description") + columns_ids.append("motif") + if format == "html": + columns_ids.append("justlink") + + return titles, columns_ids, absnonjust, absjust + + security.declareProtected(ScoView, "EtatAbsencesGr") # ported from dtml + + def EtatAbsencesGr( + self, + group_ids=[], # list of groups to display + debut="", + fin="", + with_boursier=True, # colonne boursier + format="html", + REQUEST=None, + ): + """Liste les absences de groupes + """ + datedebut = DateDMYtoISO(debut) + datefin = DateDMYtoISO(fin) + # Informations sur les groupes à afficher: + groups_infos = sco_groups_view.DisplayedGroupsInfos( + self, group_ids, REQUEST=REQUEST + ) + formsemestre_id = groups_infos.formsemestre_id + sem = groups_infos.formsemestre + + # Construit tableau (etudid, statut, nomprenom, nbJust, nbNonJust, NbTotal) + T = [] + for m in groups_infos.members: + etud = self.getEtudInfo(etudid=m["etudid"], filled=True)[0] + nbabs = self.CountAbs(etudid=etud["etudid"], debut=datedebut, fin=datefin) + nbabsjust = self.CountAbsJust( + etudid=etud["etudid"], debut=datedebut, fin=datefin + ) + nbjustifs_noabs = len( + self.ListeJustifs( + etudid=etud["etudid"], datedebut=datedebut, only_no_abs=True + ) + ) + # retrouve sem dans etud['sems'] + s = None + for s in etud["sems"]: + if s["formsemestre_id"] == formsemestre_id: + break + if not s or s["formsemestre_id"] != formsemestre_id: + raise ValueError( + "EtatAbsencesGr: can't retreive sem" + ) # bug or malicious arg + T.append( + { + "etudid": etud["etudid"], + "etatincursem": s["ins"]["etat"], + "nomprenom": etud["nomprenom"], + "nbabsjust": nbabsjust, + "nbabsnonjust": nbabs - nbabsjust, + "nbabs": nbabs, + "nbjustifs_noabs": nbjustifs_noabs, + "_nomprenom_target": "CalAbs?etudid=%s" % etud["etudid"], + "_nomprenom_td_attrs": 'id="%s" class="etudinfo"' % etud["etudid"], + "boursier": etud["boursier"], + } + ) + if s["ins"]["etat"] == "D": + T[-1]["_css_row_class"] = "etuddem" + T[-1]["nomprenom"] += " (dem)" + columns_ids = [ + "nomprenom", + "nbjustifs_noabs", + "nbabsjust", + "nbabsnonjust", + "nbabs", + ] + if with_boursier: + columns_ids[1:1] = ["boursier"] + if groups_infos.tous_les_etuds_du_sem: + gr_tit = "" + else: + if len(groups_infos.group_ids) > 1: + p = "des groupes" + else: + p = "du groupe" + if format == "html": + h = ' ' + groups_infos.groups_titles + "" + else: + h = groups_infos.groups_titles + gr_tit = p + h + + title = "Etat des absences %s" % gr_tit + if format == "xls" or format == "xml": + columns_ids = ["etudid"] + columns_ids + tab = GenTable( + columns_ids=columns_ids, + rows=T, + preferences=self.get_preferences(formsemestre_id), + titles={ + "etatincursem": "Etat", + "nomprenom": "Nom", + "nbabsjust": "Justifiées", + "nbabsnonjust": "Non justifiées", + "nbabs": "Total", + "nbjustifs_noabs": "Justifs non utilisés", + "boursier": "Bourse", + }, + html_sortable=True, + html_class="table_leftalign", + html_header=self.sco_header( + REQUEST, + page_title=title, + init_qtip=True, + javascripts=["js/etud_info.js"], + ), + html_title=self.Notes.html_sem_header( + REQUEST, "%s" % title, sem, with_page_header=False + ) + + "

    Période du %s au %s (nombre de demi-journées)
    " + % (debut, fin), + base_url="%s&formsemestre_id=%s&debut=%s&fin=%s" + % (groups_infos.base_url, formsemestre_id, debut, fin), + filename="etat_abs_" + + make_filename( + "%s de %s" % (groups_infos.groups_filename, sem["titreannee"]) + ), + caption=title, + html_next_section="""

    +

    +Justifs non utilisés: nombre de demi-journées avec justificatif mais sans absences relevées. +

    +

    +Cliquez sur un nom pour afficher le calendrier des absences
    +ou entrez une date pour visualiser les absents un jour donné : +

    +
    +
    + +%s + + +
    + """ + % (REQUEST.URL0, formsemestre_id, groups_infos.get_form_elem()), + ) + return tab.make_page(self, format=format, REQUEST=REQUEST) + + security.declareProtected(ScoView, "EtatAbsencesDate") # ported from dtml + + def EtatAbsencesDate( + self, group_ids=[], date=None, REQUEST=None # list of groups to display + ): + """Etat des absences pour un groupe à une date donnée + """ + # Informations sur les groupes à afficher: + groups_infos = sco_groups_view.DisplayedGroupsInfos( + self, group_ids, REQUEST=REQUEST + ) + formsemestre_id = groups_infos.formsemestre_id + sem = sco_formsemestre.do_formsemestre_list( + self, {"formsemestre_id": formsemestre_id} + )[0] + H = [self.sco_header(page_title="Etat des absences", REQUEST=REQUEST)] + if date: + dateiso = DateDMYtoISO(date) + nbetud = 0 + t_nbabsjustam = 0 + t_nbabsam = 0 + t_nbabsjustpm = 0 + t_nbabspm = 0 + etuds = self.getEtudInfoGroupes(groups_infos.group_ids) + H.append("

    Etat des absences le %s

    " % date) + H.append( + """ + + + """ + ) + for etud in groups_infos.members: + nbabsam = self.CountAbs( + etudid=etud["etudid"], debut=dateiso, fin=dateiso, matin=1 + ) + nbabspm = self.CountAbs( + etudid=etud["etudid"], debut=dateiso, fin=dateiso, matin=0 + ) + if (nbabsam != 0) or (nbabspm != 0): + nbetud += 1 + nbabsjustam = self.CountAbsJust( + etudid=etud["etudid"], debut=dateiso, fin=dateiso, matin=1 + ) + nbabsjustpm = self.CountAbsJust( + etudid=etud["etudid"], debut=dateiso, fin=dateiso, matin=0 + ) + H.append( + """") + H.append( + """""" + % (t_nbabsam, t_nbabsjustam, t_nbabspm, t_nbabsjustpm) + ) + H.append("
     MatinAprès-midi
    + %(nomprenom)s""" + % etud + ) # """ + if nbabsam != 0: + if nbabsjustam: + H.append("Just.") + t_nbabsjustam += 1 + else: + H.append("Abs.") + t_nbabsam += 1 + else: + H.append("") + H.append('') + if nbabspm != 0: + if nbabsjustpm: + H.append("Just.") + t_nbabsjustam += 1 + else: + H.append("Abs.") + t_nbabspm += 1 + else: + H.append("") + H.append("
    %d abs, %d just.%d abs, %d just.
    ") + if nbetud == 0: + H.append("

    Aucune absence !

    ") + else: + H.append( + """

    Erreur: vous n'avez pas choisi de date !

    + Continuer""" + % REQUEST.HTTP_REFERER + ) + + return "\n".join(H) + self.sco_footer(REQUEST) + + # ----- Gestion des "billets d'absence": signalement par les etudiants eux mêmes (à travers le portail) + security.declareProtected(ScoAbsAddBillet, "AddBilletAbsence") + + def AddBilletAbsence( + self, + begin, + end, + description, + etudid=False, + code_nip=None, + code_ine=None, + justified=True, + REQUEST=None, + xml_reply=True, + ): + """Memorise un "billet" + begin et end sont au format ISO (eg "1999-01-08 04:05:06") + """ + t0 = time.time() + # check etudid + etuds = self.getEtudInfo( + etudid=etudid, code_nip=code_nip, REQUEST=REQUEST, filled=True + ) + if not etuds: + return log_unknown_etud(self, REQUEST=REQUEST) + etud = etuds[0] + # check dates + begin_date = ParseDateTimeUTC(begin) # may raises ValueError + end_date = ParseDateTimeUTC(end) + if begin_date > end_date: + raise ValueError("invalid dates") + # + justified = int(justified) + # + cnx = self.GetDBConnexion() + billet_id = billet_absence_create( + cnx, + { + "etudid": etud["etudid"], + "abs_begin": begin, + "abs_end": end, + "description": description, + "etat": 0, + "justified": justified, + }, + ) + if xml_reply: + # Renvoie le nouveau billet en XML + if REQUEST: + REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE) + + billets = billet_absence_list(cnx, {"billet_id": billet_id}) + tab = self._tableBillets(billets, etud=etud) + log( + "AddBilletAbsence: new billet_id=%s (%gs)" + % (billet_id, time.time() - t0) + ) + return tab.make_page(self, REQUEST=REQUEST, format="xml") + else: + return billet_id + + security.declareProtected(ScoAbsAddBillet, "AddBilletAbsenceForm") + + def AddBilletAbsenceForm(self, etudid, REQUEST=None): + """Formulaire ajout billet (pour tests seulement, le vrai formulaire accessible aux etudiants + étant sur le portail étudiant). + """ + etud = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0] + H = [ + self.sco_header( + REQUEST, page_title="Billet d'absence de %s" % etud["nomprenom"] + ) + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ("etudid", {"input_type": "hidden"}), + ("begin", {"input_type": "date"}), + ("end", {"input_type": "date"}), + ( + "justified", + {"input_type": "boolcheckbox", "default": 0, "title": "Justifiée"}, + ), + ("description", {"input_type": "textarea"}), + ), + ) + if tf[0] == 0: + return "\n".join(H) + tf[1] + self.sco_footer(REQUEST) + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + e = tf[2]["begin"].split("/") + begin = e[2] + "-" + e[1] + "-" + e[0] + " 00:00:00" + e = tf[2]["end"].split("/") + end = e[2] + "-" + e[1] + "-" + e[0] + " 00:00:00" + log( + self.AddBilletAbsence( + begin, + end, + tf[2]["description"], + etudid=etudid, + xml_reply=True, + justified=tf[2]["justified"], + ) + ) + return REQUEST.RESPONSE.redirect("listeBilletsEtud?etudid=" + etudid) + + def _tableBillets(self, billets, etud=None, title=""): + for b in billets: + if b["abs_begin"].hour < 12: + m = " matin" + else: + m = " après midi" + b["abs_begin_str"] = b["abs_begin"].strftime("%d/%m/%Y") + m + if b["abs_end"].hour < 12: + m = " matin" + else: + m = " après midi" + b["abs_end_str"] = b["abs_end"].strftime("%d/%m/%Y") + m + if b["etat"] == 0: + if b["justified"] == 0: + b["etat_str"] = "à traiter" + else: + b["etat_str"] = "à justifier" + b["_etat_str_target"] = ( + "ProcessBilletAbsenceForm?billet_id=%s" % b["billet_id"] + ) + if etud: + b["_etat_str_target"] += "&etudid=%s" % etud["etudid"] + b["_billet_id_target"] = b["_etat_str_target"] + else: + b["etat_str"] = "ok" + if not etud: + # ajoute info etudiant + e = self.getEtudInfo(etudid=b["etudid"], filled=1) + if not e: + b["nomprenom"] = "???" # should not occur + else: + b["nomprenom"] = e[0]["nomprenom"] + b["_nomprenom_target"] = "ficheEtud?etudid=%s" % b["etudid"] + if etud and not title: + title = "Billets d'absence déclarés par %(nomprenom)s" % etud + else: + title = title + columns_ids = ["billet_id"] + if not etud: + columns_ids += ["nomprenom"] + columns_ids += ["abs_begin_str", "abs_end_str", "description", "etat_str"] + + tab = GenTable( + titles={ + "billet_id": "Numéro", + "abs_begin_str": "Début", + "abs_end_str": "Fin", + "description": "Raison de l'absence", + "etat_str": "Etat", + }, + columns_ids=columns_ids, + page_title=title, + html_title="

    %s

    " % title, + preferences=self.get_preferences(), + rows=billets, + html_sortable=True, + ) + return tab + + security.declareProtected(ScoView, "listeBilletsEtud") + + def listeBilletsEtud(self, etudid=False, REQUEST=None, format="html"): + """Liste billets pour un etudiant + """ + etuds = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST) + if not etuds: + return log_unknown_etud(self, format=format, REQUEST=REQUEST) + + etud = etuds[0] + cnx = self.GetDBConnexion() + billets = billet_absence_list(cnx, {"etudid": etud["etudid"]}) + tab = self._tableBillets(billets, etud=etud) + return tab.make_page(self, REQUEST=REQUEST, format=format) + + security.declareProtected(ScoView, "XMLgetBilletsEtud") + + def XMLgetBilletsEtud(self, etudid=False, REQUEST=None): + """Liste billets pour un etudiant + """ + if not self.get_preference("handle_billets_abs"): + return "" + t0 = time.time() + r = self.listeBilletsEtud(etudid, REQUEST=REQUEST, format="xml") + log("XMLgetBilletsEtud (%gs)" % (time.time() - t0)) + return r + + security.declareProtected(ScoView, "listeBillets") + + def listeBillets(self, REQUEST=None): + """Page liste des billets non traités et formulaire recherche d'un billet""" + cnx = self.GetDBConnexion() + billets = billet_absence_list(cnx, {"etat": 0}) + tab = self._tableBillets(billets) + T = tab.html() + H = [ + self.sco_header(REQUEST, page_title="Billet d'absence non traités"), + "

    Billets d'absence en attente de traitement (%d)

    " % len(billets), + ] + + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + (("billet_id", {"input_type": "text", "title": "Numéro du billet"}),), + submitbutton=False, + ) + if tf[0] == 0: + return "\n".join(H) + tf[1] + T + self.sco_footer(REQUEST) + else: + return REQUEST.RESPONSE.redirect( + "ProcessBilletAbsenceForm?billet_id=" + tf[2]["billet_id"] + ) + + security.declareProtected(ScoAbsChange, "deleteBilletAbsence") + + def deleteBilletAbsence(self, billet_id, REQUEST=None, dialog_confirmed=False): + """Supprime un billet. + """ + cnx = self.GetDBConnexion() + billets = billet_absence_list(cnx, {"billet_id": billet_id}) + if not billets: + return REQUEST.RESPONSE.redirect( + "listeBillets?head_message=Billet%%20%s%%20inexistant !" % billet_id + ) + if not dialog_confirmed: + tab = self._tableBillets(billets) + return self.confirmDialog( + """

    Supprimer ce billet ?

    """ + tab.html(), + dest_url="", + REQUEST=REQUEST, + cancel_url="listeBillets", + parameters={"billet_id": billet_id}, + ) + + billet_absence_delete(cnx, billet_id) + + return REQUEST.RESPONSE.redirect("listeBillets?head_message=Billet%20supprimé") + + def _ProcessBilletAbsence(self, billet, estjust, description, REQUEST): + """Traite un billet: ajoute absence(s) et éventuellement justificatifs, + et change l'état du billet à 1. + NB: actuellement, les heures ne sont utilisées que pour déterminer si matin et/ou après midi. + """ + cnx = self.GetDBConnexion() + if billet["etat"] != 0: + log("billet=%s" % billet) + log("billet deja traité !") + return -1 + n = 0 # nombre de demi-journées d'absence ajoutées + # 1-- ajout des absences (et justifs) + datedebut = billet["abs_begin"].strftime("%d/%m/%Y") + datefin = billet["abs_end"].strftime("%d/%m/%Y") + dates = self.DateRangeISO(datedebut, datefin) + # commence apres midi ? + if dates and billet["abs_begin"].hour > 11: + self._AddAbsence( + billet["etudid"], dates[0], 0, estjust, REQUEST, description=description + ) + n += 1 + dates = dates[1:] + # termine matin ? + if dates and billet["abs_end"].hour < 12: + self._AddAbsence( + billet["etudid"], + dates[-1], + 1, + estjust, + REQUEST, + description=description, + ) + n += 1 + dates = dates[:-1] + + for jour in dates: + self._AddAbsence( + billet["etudid"], jour, 0, estjust, REQUEST, description=description + ) + self._AddAbsence( + billet["etudid"], jour, 1, estjust, REQUEST, description=description + ) + n += 2 + + # 2- change etat du billet + billet_absence_edit(cnx, {"billet_id": billet["billet_id"], "etat": 1}) + + return n + + security.declareProtected(ScoAbsChange, "ProcessBilletAbsenceForm") + + def ProcessBilletAbsenceForm(self, billet_id, REQUEST=None): + """Formulaire traitement d'un billet""" + cnx = self.GetDBConnexion() + billets = billet_absence_list(cnx, {"billet_id": billet_id}) + if not billets: + return REQUEST.RESPONSE.redirect( + "listeBillets?head_message=Billet%%20%s%%20inexistant !" % billet_id + ) + billet = billets[0] + etudid = billet["etudid"] + etud = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0] + + H = [ + self.sco_header( + REQUEST, + page_title="Traitement billet d'absence de %s" % etud["nomprenom"], + ), + '

    Traitement du billet %s : %s

    ' + % (billet_id, etudid, etud["nomprenom"]), + ] + + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ("billet_id", {"input_type": "hidden"}), + ( + "etudid", + {"input_type": "hidden"}, + ), # pour centrer l'UI sur l'étudiant + ( + "estjust", + {"input_type": "boolcheckbox", "title": "Absences justifiées"}, + ), + ("description", {"input_type": "text", "size": 42, "title": "Raison"}), + ), + initvalues={ + "description": billet["description"], + "estjust": billet["justified"], + "etudid": etudid, + }, + submitlabel="Enregistrer ces absences", + ) + if tf[0] == 0: + tab = self._tableBillets([billet], etud=etud) + H.append(tab.html()) + if billet["justified"] == 1: + H.append( + """

    L'étudiant pense pouvoir justifier cette absence.
    Vérifiez le justificatif avant d'enregistrer.

    """ + ) + F = ( + """

    Supprimer ce billet (utiliser en cas d'erreur, par ex. billet en double)

    """ + % billet_id + ) + F += '

    Liste de tous les billets en attente

    ' + + return "\n".join(H) + "
    " + tf[1] + F + self.sco_footer(REQUEST) + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + n = self._ProcessBilletAbsence( + billet, tf[2]["estjust"], tf[2]["description"], REQUEST + ) + if tf[2]["estjust"]: + j = "justifiées" + else: + j = "non justifiées" + H.append('
    ') + if n > 0: + H.append("%d absences (1/2 journées) %s ajoutées" % (n, j)) + elif n == 0: + H.append("Aucun jour d'absence dans les dates indiquées !") + elif n < 0: + H.append("Ce billet avait déjà été traité !") + H.append( + '

    Autre billets en attente

    Billets déclarés par %s

    ' + % (etud["nomprenom"]) + ) + billets = billet_absence_list(cnx, {"etudid": etud["etudid"]}) + tab = self._tableBillets(billets, etud=etud) + H.append(tab.html()) + return "\n".join(H) + self.sco_footer(REQUEST) + + security.declareProtected(ScoView, "XMLgetAbsEtud") + + def XMLgetAbsEtud(self, beg_date="", end_date="", REQUEST=None): + """returns list of absences in date interval""" + t0 = time.time() + etud = self.getEtudInfo(REQUEST=REQUEST)[0] + exp = re.compile(r"^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$") + if not exp.match(beg_date): + raise ScoValueError("invalid date: %s" % beg_date) + if not exp.match(end_date): + raise ScoValueError("invalid date: %s" % end_date) + + Abs = self._ListeAbsDate(etud["etudid"], beg_date, end_date) + + REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE) + doc = jaxml.XML_document(encoding=SCO_ENCODING) + doc.absences(etudid=etud["etudid"], beg_date=beg_date, end_date=end_date) + doc._push() + for a in Abs: + if a["estabs"]: # ne donne pas les justifications si pas d'absence + doc._push() + doc.abs( + begin=a["begin"], + end=a["end"], + description=a["description"], + justified=a["estjust"], + ) + doc._pop() + doc._pop() + log("XMLgetAbsEtud (%gs)" % (time.time() - t0)) + return repr(doc) + + +_billet_absenceEditor = EditableTable( + "billet_absence", + "billet_id", + ( + "billet_id", + "etudid", + "abs_begin", + "abs_end", + "description", + "etat", + "entry_date", + "justified", + ), + sortkey="entry_date desc", +) + +billet_absence_create = _billet_absenceEditor.create +billet_absence_delete = _billet_absenceEditor.delete +billet_absence_list = _billet_absenceEditor.list +billet_absence_edit = _billet_absenceEditor.edit + +# ------ HTML Calendar functions (see YearTable function) + +# MONTH/DAY NAMES: + +MONTHNAMES = ( + "Janvier", + "Février", + "Mars", + "Avril", + "Mai", + "Juin", + "Juillet", + "Aout", + "Septembre", + "Octobre", + "Novembre", + "Décembre", +) + +MONTHNAMES_ABREV = ( + "Jan.", + "Fév.", + "Mars", + "Avr.", + "Mai ", + "Juin", + "Juil", + "Aout", + "Sept", + "Oct.", + "Nov.", + "Déc.", +) + +DAYNAMES = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche") + +DAYNAMES_ABREV = ("L", "M", "M", "J", "V", "S", "D") + +# COLORS: + +WHITE = "#FFFFFF" +GRAY1 = "#EEEEEE" +GREEN3 = "#99CC99" +WEEKDAYCOLOR = GRAY1 +WEEKENDCOLOR = GREEN3 + + +def MonthTableHead(month): + color = WHITE + return """ + \n""" % ( + color, + MONTHNAMES_ABREV[month - 1], + ) + + +def MonthTableTail(): + return "
    %s
    \n" + + +def MonthTableBody( + month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8 +): + # log('XXX events=%s' % events) + firstday, nbdays = calendar.monthrange(year, month) + localtime = time.localtime() + current_weeknum = time.strftime("%U", localtime) + current_year = localtime[0] + T = [] + # cherche date du lundi de la 1ere semaine de ce mois + monday = ddmmyyyy("1/%d/%d" % (month, year)) + while monday.weekday != 0: + monday = monday.prev() + + if work_saturday: + weekend = ("D",) + else: + weekend = ("S", "D") + + if not halfday: + for d in range(1, nbdays + 1): + weeknum = time.strftime( + "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y") + ) + day = DAYNAMES_ABREV[(firstday + d - 1) % 7] + if day in weekend: + bgcolor = WEEKENDCOLOR + weekclass = "wkend" + attrs = "" + else: + bgcolor = WEEKDAYCOLOR + weekclass = "wk" + str(monday).replace("/", "_") + attrs = trattributes + color = None + legend = "" + href = "" + descr = "" + # event this day ? + # each event is a tuple (date, text, color, href) + # where date is a string in ISO format (yyyy-mm-dd) + for ev in events: + ev_year = int(ev[0][:4]) + ev_month = int(ev[0][5:7]) + ev_day = int(ev[0][8:10]) + if year == ev_year and month == ev_month and ev_day == d: + if ev[1]: + legend = ev[1] + if ev[2]: + color = ev[2] + if ev[3]: + href = ev[3] + if len(ev) > 4 and ev[4]: + descr = ev[4] + # + cc = [] + if color != None: + cc.append('' % color) + else: + cc.append('') + + if href: + href = 'href="%s"' % href + if descr: + descr = 'title="%s"' % descr + if href or descr: + cc.append("" % (href, descr)) + + if legend or d == 1: + if pad_width != None: + n = pad_width - len(legend) # pad to 8 cars + if n > 0: + legend = " " * (n / 2) + legend + " " * ((n + 1) / 2) + else: + legend = " " # empty cell + cc.append(legend) + if href or descr: + cc.append("") + cc.append("") + cell = string.join(cc, "") + if day == "D": + monday = monday.next(7) + if ( + weeknum == current_weeknum + and current_year == year + and weekclass != "wkend" + ): + weekclass += " currentweek" + T.append( + '%d%s%s' + % (bgcolor, weekclass, attrs, d, day, cell) + ) + else: + # Calendar with 2 cells / day + for d in range(1, nbdays + 1): + weeknum = time.strftime( + "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y") + ) + day = DAYNAMES_ABREV[(firstday + d - 1) % 7] + if day in weekend: + bgcolor = WEEKENDCOLOR + weekclass = "wkend" + attrs = "" + else: + bgcolor = WEEKDAYCOLOR + weekclass = "wk" + str(monday).replace("/", "_") + attrs = trattributes + if ( + weeknum == current_weeknum + and current_year == year + and weekclass != "wkend" + ): + weeknum += " currentweek" + + if day == "D": + monday = monday.next(7) + T.append( + '%d%s' + % (bgcolor, weekclass, attrs, d, day) + ) + cc = [] + for morning in (1, 0): + color = None + legend = "" + href = "" + descr = "" + for ev in events: + ev_year = int(ev[0][:4]) + ev_month = int(ev[0][5:7]) + ev_day = int(ev[0][8:10]) + if ev[4] != None: + ev_half = int(ev[4]) + else: + ev_half = 0 + if ( + year == ev_year + and month == ev_month + and ev_day == d + and morning == ev_half + ): + if ev[1]: + legend = ev[1] + if ev[2]: + color = ev[2] + if ev[3]: + href = ev[3] + if len(ev) > 5 and ev[5]: + descr = ev[5] + # + if color != None: + cc.append('' % (color)) + else: + cc.append('') + if href: + href = 'href="%s"' % href + if descr: + descr = 'title="%s"' % descr + if href or descr: + cc.append("" % (href, descr)) + if legend or d == 1: + n = 3 - len(legend) # pad to 3 cars + if n > 0: + legend = " " * (n / 2) + legend + " " * ((n + 1) / 2) + else: + legend = "   " # empty cell + cc.append(legend) + if href or descr: + cc.append("") + cc.append("\n") + T.append(string.join(cc, "") + "") + return string.join(T, "\n") + + +# -------------------------------------------------------------------- +# +# Zope Product Administration +# +# -------------------------------------------------------------------- +def manage_addZAbsences( + self, id="id_ZAbsences", title="The Title for ZAbsences Object", REQUEST=None +): + "Add a ZAbsences instance to a folder." + self._setObject(id, ZAbsences(id, title)) + if REQUEST is not None: + return self.manage_main(self, REQUEST) + # return self.manage_editForm(self, REQUEST) + + +# The form used to get the instance id from the user. +# manage_addZAbsencesForm = DTMLFile('dtml/manage_addZAbsencesForm', globals()) + + +# -------------------------------------------------------------------- +# +# Cache absences +# +# On cache simplement (à la demande) le nombre d'absences de chaque etudiant +# dans un semestre donné. +# Toute modification du semestre (invalidation) invalide le cache +# (simple mécanisme de "listener" sur le cache de semestres) +# Toute modification des absences d'un étudiant invalide les caches des semestres +# concernés à cette date (en général un seul semestre) +# +# On ne cache pas la liste des absences car elle est rarement utilisée (calendrier, +# absences à une date donnée). +# +# -------------------------------------------------------------------- +class CAbsSemEtud: + """Comptes d'absences d'un etudiant dans un semestre""" + + def __init__(self, context, sem, etudid): + self.context = context + self.sem = sem + self.etudid = etudid + self._loaded = False + formsemestre_id = sem["formsemestre_id"] + context.Notes._getNotesCache().add_listener( + self.invalidate, formsemestre_id, (etudid, formsemestre_id) + ) + + def CountAbs(self): + if not self._loaded: + self.load() + return self._CountAbs + + def CountAbsJust(self): + if not self._loaded: + self.load() + return self._CountAbsJust + + def load(self): + "Load state from DB" + # log('loading CAbsEtudSem(%s,%s)' % (self.etudid, self.sem['formsemestre_id'])) + # Reload sem, it may have changed + self.sem = sco_formsemestre.get_formsemestre( + self.context, self.sem["formsemestre_id"] + ) + debut_sem = DateDMYtoISO(self.sem["date_debut"]) + fin_sem = DateDMYtoISO(self.sem["date_fin"]) + + self._CountAbs = self.context.Absences.CountAbs( + etudid=self.etudid, debut=debut_sem, fin=fin_sem + ) + self._CountAbsJust = self.context.Absences.CountAbsJust( + etudid=self.etudid, debut=debut_sem, fin=fin_sem + ) + self._loaded = True + + def invalidate(self, args=None): + "Notify me that DB has been modified" + # log('invalidate CAbsEtudSem(%s,%s)' % (self.etudid, self.sem['formsemestre_id'])) + self._loaded = False + + +# Accès au cache des absences +ABS_CACHE_INST = {} # { DeptId : { formsemestre_id : { etudid : CAbsEtudSem } } } + + +def getAbsSemEtud(context, sem, etudid): + AbsSemEtuds = getAbsSemEtuds(context, sem) + if not etudid in AbsSemEtuds: + AbsSemEtuds[etudid] = CAbsSemEtud(context, sem, etudid) + return AbsSemEtuds[etudid] + + +def getAbsSemEtuds(context, sem): + u = context.GetDBConnexionString() # identifie le dept de facon fiable + if not u in ABS_CACHE_INST: + ABS_CACHE_INST[u] = {} + C = ABS_CACHE_INST[u] + if sem["formsemestre_id"] not in C: + C[sem["formsemestre_id"]] = {} + return C[sem["formsemestre_id"]] + + +def invalidateAbsEtudDate(context, etudid, date): + """Doit etre appelé à chaque modification des absences pour cet étudiant et cette date. + Invalide cache absence et PDF bulletins si nécessaire. + date: date au format ISO + """ + # Semestres a cette date: + etud = context.getEtudInfo(etudid=etudid, filled=True)[0] + sems = [ + sem + for sem in etud["sems"] + if sem["date_debut_iso"] <= date and sem["date_fin_iso"] >= date + ] + + # Invalide les PDF et les abscences: + for sem in sems: + # Inval cache bulletin et/ou note_table + if sco_compute_moy.formsemestre_expressions_use_abscounts( + context, sem["formsemestre_id"] + ): + pdfonly = False # seules certaines formules utilisent les absences + else: + pdfonly = ( + True # efface toujours le PDF car il affiche en général les absences + ) + + context.Notes._inval_cache( + pdfonly=pdfonly, formsemestre_id=sem["formsemestre_id"] + ) + + # Inval cache compteurs absences: + AbsSemEtuds = getAbsSemEtuds(context, sem) + if etudid in AbsSemEtuds: + AbsSemEtuds[etudid].invalidate() diff --git a/ZEntreprises.py b/ZEntreprises.py new file mode 100644 index 00000000..3ee42a84 --- /dev/null +++ b/ZEntreprises.py @@ -0,0 +1,898 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +""" Gestion des relations avec les entreprises +""" +import urllib + +from sco_zope import * + +# --------------- + +from notesdb import * +from notes_log import log +from scolog import logdb +from sco_utils import * +import html_sidebar + +from TrivialFormulator import TrivialFormulator, TF +import scolars +import string, re +import time, calendar + + +def _format_nom(nom): + "formatte nom (filtre en entree db) d'une entreprise" + if not nom: + return nom + nom = nom.decode(SCO_ENCODING) + return (nom[0].upper() + nom[1:]).encode(SCO_ENCODING) + + +class EntreprisesEditor(EditableTable): + def delete(self, cnx, oid): + "delete correspondants and contacts, then self" + # first, delete all correspondants and contacts + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "delete from entreprise_contact where entreprise_id=%(entreprise_id)s", + {"entreprise_id": oid}, + ) + cursor.execute( + "delete from entreprise_correspondant where entreprise_id=%(entreprise_id)s", + {"entreprise_id": oid}, + ) + cnx.commit() + EditableTable.delete(self, cnx, oid) + + def list( + self, + cnx, + args={}, + operator="and", + test="=", + sortkey=None, + sort_on_contact=False, + ZEntrepriseInstance=None, + ): + # list, then sort on date of last contact + R = EditableTable.list( + self, cnx, args=args, operator=operator, test=test, sortkey=sortkey + ) + if sort_on_contact: + for r in R: + c = ZEntrepriseInstance.do_entreprise_contact_list( + args={"entreprise_id": r["entreprise_id"]}, disable_formatting=True + ) + if c: + r["date"] = max([x["date"] or datetime.date.min for x in c]) + else: + r["date"] = datetime.date.min + # sort + R.sort(lambda r1, r2: cmp(r2["date"], r1["date"])) + for r in R: + r["date"] = DateISOtoDMY(r["date"]) + return R + + def list_by_etud( + self, cnx, args={}, sort_on_contact=False, disable_formatting=False + ): + "cherche rentreprise ayant eu contact avec etudiant" + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "select E.*, I.nom as etud_nom, I.prenom as etud_prenom, C.date from entreprises E, entreprise_contact C, identite I where C.entreprise_id = E.entreprise_id and C.etudid = I.etudid and I.nom ~* %(etud_nom)s ORDER BY E.nom", + args, + ) + titles, res = [x[0] for x in cursor.description], cursor.dictfetchall() + R = [] + for r in res: + r["etud_prenom"] = r["etud_prenom"] or "" + d = {} + for key in r: + v = r[key] + # format value + if not disable_formatting and self.output_formators.has_key(key): + v = self.output_formators[key](v) + d[key] = v + R.append(d) + # sort + if sort_on_contact: + R.sort( + lambda r1, r2: cmp( + r2["date"] or datetime.date.min, r1["date"] or datetime.date.min + ) + ) + for r in R: + r["date"] = DateISOtoDMY(r["date"] or datetime.date.min) + return R + + +_entreprisesEditor = EntreprisesEditor( + "entreprises", + "entreprise_id", + ( + "entreprise_id", + "nom", + "adresse", + "ville", + "codepostal", + "pays", + "contact_origine", + "secteur", + "privee", + "localisation", + "qualite_relation", + "plus10salaries", + "note", + "date_creation", + ), + sortkey="nom", + input_formators={"nom": _format_nom}, +) + +# ----------- Correspondants +_entreprise_correspEditor = EditableTable( + "entreprise_correspondant", + "entreprise_corresp_id", + ( + "entreprise_corresp_id", + "entreprise_id", + "civilite", + "nom", + "prenom", + "fonction", + "phone1", + "phone2", + "mobile", + "fax", + "mail1", + "mail2", + "note", + ), + sortkey="nom", +) + + +# ----------- Contacts +_entreprise_contactEditor = EditableTable( + "entreprise_contact", + "entreprise_contact_id", + ( + "entreprise_contact_id", + "date", + "type_contact", + "entreprise_id", + "entreprise_corresp_id", + "etudid", + "description", + "enseignant", + ), + sortkey="date", + output_formators={"date": DateISOtoDMY}, + input_formators={"date": DateDMYtoISO}, +) + +# --------------- + + +class ZEntreprises( + ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit +): + + "ZEntreprises object" + + meta_type = "ZEntreprises" + security = ClassSecurityInfo() + + # This is the list of the methods associated to 'tabs' in the ZMI + # Be aware that The first in the list is the one shown by default, so if + # the 'View' tab is the first, you will never see your tabs by cliquing + # on the object. + manage_options = ( + ({"label": "Contents", "action": "manage_main"},) + + PropertyManager.manage_options # add the 'Properties' tab + + ( + # this line is kept as an example with the files : + # dtml/manage_editZScolarForm.dtml + # html/ZScolar-edit.stx + # {'label': 'Properties', 'action': 'manage_editForm',}, + {"label": "View", "action": "index_html"}, + ) + + Item.manage_options # add the 'Undo' & 'Owner' tab + + RoleManager.manage_options # add the 'Security' tab + ) + + # no permissions, only called from python + def __init__(self, id, title): + "initialise a new instance" + self.id = id + self.title = title + + # The form used to edit this object + def manage_editZEntreprises(self, title, RESPONSE=None): + "Changes the instance values" + self.title = title + self._p_changed = 1 + RESPONSE.redirect("manage_editForm") + + # Ajout (dans l'instance) d'un dtml modifiable par Zope + def defaultDocFile(self, id, title, file): + f = open(file_path + "/dtml-editable/" + file + ".dtml") + file = f.read() + f.close() + self.manage_addDTMLMethod(id, title, file) + + security.declareProtected(ScoEntrepriseView, "entreprise_header") + + def entreprise_header(self, REQUEST=None, page_title=""): + "common header for all Entreprises pages" + authuser = REQUEST.AUTHENTICATED_USER + # _read_only is used to modify pages properties (links, buttons) + # Python methods (do_xxx in this class) are also protected individualy) + if authuser.has_permission(ScoEntrepriseChange, self): + REQUEST.set("_read_only", False) + else: + REQUEST.set("_read_only", True) + return self.sco_header(REQUEST, container=self, page_title=page_title) + + security.declareProtected(ScoEntrepriseView, "entreprise_footer") + + def entreprise_footer(self, REQUEST): + "common entreprise footer" + return self.sco_footer(REQUEST) + + security.declareProtected(ScoEntrepriseView, "sidebar") + + def sidebar(self, REQUEST): + "barre gauche (overide std sco sidebar)" + # rewritten from legacy DTML code + context = self + params = {"ScoURL": context.ScoURL()} + + H = [ + """ """) + return "".join(H) + + # -------------------------------------------------------------------- + # + # Entreprises : Methodes en DTML + # + # -------------------------------------------------------------------- + # used to view content of the object + security.declareProtected(ScoEntrepriseView, "index_html") + index_html = DTMLFile("dtml/entreprises/index_html", globals()) + + security.declareProtected(ScoEntrepriseView, "entreprise_contact_list") + entreprise_contact_list = DTMLFile( + "dtml/entreprises/entreprise_contact_list", globals() + ) + security.declareProtected(ScoEntrepriseView, "entreprise_correspondant_list") + entreprise_correspondant_list = DTMLFile( + "dtml/entreprises/entreprise_correspondant_list", globals() + ) + # les methodes "edit" sont aussi en ScoEntrepriseView car elles permettent + # la visualisation (via variable _read_only positionnee dans entreprise_header) + security.declareProtected(ScoEntrepriseView, "entreprise_contact_edit") + entreprise_contact_edit = DTMLFile( + "dtml/entreprises/entreprise_contact_edit", globals() + ) + security.declareProtected(ScoEntrepriseView, "entreprise_correspondant_edit") + entreprise_correspondant_edit = DTMLFile( + "dtml/entreprises/entreprise_correspondant_edit", globals() + ) + + # Acces en modification: + security.declareProtected(ScoEntrepriseChange, "entreprise_contact_create") + entreprise_contact_create = DTMLFile( + "dtml/entreprises/entreprise_contact_create", globals() + ) + security.declareProtected(ScoEntrepriseChange, "entreprise_contact_delete") + entreprise_contact_delete = DTMLFile( + "dtml/entreprises/entreprise_contact_delete", globals() + ) + security.declareProtected(ScoEntrepriseChange, "entreprise_correspondant_create") + entreprise_correspondant_create = DTMLFile( + "dtml/entreprises/entreprise_correspondant_create", globals() + ) + security.declareProtected(ScoEntrepriseChange, "entreprise_correspondant_delete") + entreprise_correspondant_delete = DTMLFile( + "dtml/entreprises/entreprise_correspondant_delete", globals() + ) + security.declareProtected(ScoEntrepriseChange, "entreprise_delete") + entreprise_delete = DTMLFile("dtml/entreprises/entreprise_delete", globals()) + + # -------------------------------------------------------------------- + # + # Entreprises : Methodes en Python + # + # -------------------------------------------------------------------- + security.declareProtected(ScoEntrepriseChange, "do_entreprise_create") + + def do_entreprise_create(self, args): + "entreprise_create" + cnx = self.GetDBConnexion() + r = _entreprisesEditor.create(cnx, args) + return r + + security.declareProtected(ScoEntrepriseChange, "do_entreprise_delete") + + def do_entreprise_delete(self, oid): + "entreprise_delete" + cnx = self.GetDBConnexion() + _entreprisesEditor.delete(cnx, oid) + + security.declareProtected(ScoEntrepriseView, "do_entreprise_list") + + def do_entreprise_list(self, **kw): + "entreprise_list" + cnx = self.GetDBConnexion() + kw["ZEntrepriseInstance"] = self + return _entreprisesEditor.list(cnx, **kw) + + security.declareProtected(ScoEntrepriseView, "do_entreprise_list_by_etud") + + def do_entreprise_list_by_etud(self, **kw): + "entreprise_list_by_etud" + cnx = self.GetDBConnexion() + return _entreprisesEditor.list_by_etud(cnx, **kw) + + security.declareProtected(ScoEntrepriseView, "do_entreprise_edit") + + def do_entreprise_edit(self, *args, **kw): + "entreprise_edit" + cnx = self.GetDBConnexion() + _entreprisesEditor.edit(cnx, *args, **kw) + + security.declareProtected(ScoEntrepriseChange, "do_entreprise_correspondant_create") + + def do_entreprise_correspondant_create(self, args): + "entreprise_correspondant_create" + cnx = self.GetDBConnexion() + r = _entreprise_correspEditor.create(cnx, args) + return r + + security.declareProtected(ScoEntrepriseChange, "do_entreprise_correspondant_delete") + + def do_entreprise_correspondant_delete(self, oid): + "entreprise_correspondant_delete" + cnx = self.GetDBConnexion() + _entreprise_correspEditor.delete(cnx, oid) + + security.declareProtected(ScoEntrepriseView, "do_entreprise_correspondant_list") + + def do_entreprise_correspondant_list(self, **kw): + "entreprise_correspondant_list" + cnx = self.GetDBConnexion() + return _entreprise_correspEditor.list(cnx, **kw) + + security.declareProtected(ScoEntrepriseView, "do_entreprise_correspondant_edit") + + def do_entreprise_correspondant_edit(self, *args, **kw): + "entreprise_correspondant_edit" + cnx = self.GetDBConnexion() + _entreprise_correspEditor.edit(cnx, *args, **kw) + + def do_entreprise_correspondant_listnames(self, args={}): + "-> liste des noms des correspondants (pour affichage menu)" + cnx = self.GetDBConnexion() + C = self.do_entreprise_correspondant_list(args=args) + return [ + (x["prenom"] + " " + x["nom"], str(x["entreprise_corresp_id"])) for x in C + ] + + security.declareProtected(ScoEntrepriseChange, "do_entreprise_contact_create") + + def do_entreprise_contact_create(self, args): + "entreprise_contact_create" + cnx = self.GetDBConnexion() + r = _entreprise_contactEditor.create(cnx, args) + return r + + security.declareProtected(ScoEntrepriseChange, "do_entreprise_contact_delete") + + def do_entreprise_contact_delete(self, oid): + "entreprise_contact_delete" + cnx = self.GetDBConnexion() + _entreprise_contactEditor.delete(cnx, oid) + + security.declareProtected(ScoEntrepriseView, "do_entreprise_contact_list") + + def do_entreprise_contact_list(self, **kw): + "entreprise_contact_list" + cnx = self.GetDBConnexion() + return _entreprise_contactEditor.list(cnx, **kw) + + security.declareProtected(ScoEntrepriseView, "do_entreprise_contact_edit") + + def do_entreprise_contact_edit(self, *args, **kw): + "entreprise_contact_edit" + cnx = self.GetDBConnexion() + _entreprise_contactEditor.edit(cnx, *args, **kw) + + # + security.declareProtected(ScoEntrepriseView, "do_entreprise_check_etudiant") + + def do_entreprise_check_etudiant(self, etudiant): + """Si etudiant est vide, ou un ETUDID valide, ou un nom unique, + retourne (1, ETUDID). + Sinon, retourne (0, 'message explicatif') + """ + etudiant = etudiant.strip().translate( + None, "'()" + ) # suppress parens and quote from name + if not etudiant: + return 1, None + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "select etudid, nom, prenom from identite where upper(nom) ~ upper(%(etudiant)s) or etudid=%(etudiant)s", + {"etudiant": etudiant}, + ) + r = cursor.fetchall() + if len(r) < 1: + return 0, 'Aucun etudiant ne correspond à "%s"' % etudiant + elif len(r) > 10: + return ( + 0, + "%d etudiants correspondent à ce nom (utilisez le code)" + % len(r), + ) + elif len(r) > 1: + e = ['
      '] + for x in r: + e.append( + "
    • %s %s (code %s)
    • " + % (strupper(x[1]), x[2] or "", x[0].strip()) + ) + e.append("
    ") + return ( + 0, + "Les étudiants suivants correspondent: préciser le nom complet ou le code\n" + + "\n".join(e), + ) + else: # une seule reponse ! + return 1, r[0][0].strip() + + security.declareProtected(ScoEntrepriseView, "do_entreprise_list_by_contact") + + def do_entreprise_list_by_contact( + self, args={}, operator="and", test="=", sortkey=None + ): + """Recherche dans entreprises, avec date de contact""" + # (fonction ad-hoc car requete sur plusieurs tables) + raise NotImplementedError + # XXXXX fonction non achevee , non testee... + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + vals = dictfilter(args, self.dbfields) + # DBSelect + what = ["*"] + operator = " " + operator + " " + cond = " E.entreprise_id = C.entreprise_id " + if vals: + cond += " where " + operator.join( + ["%s%s%%(%s)s" % (x, test, x) for x in vals.keys() if vals[x] != None] + ) + cnuls = " and ".join( + ["%s is NULL" % x for x in vals.keys() if vals[x] is None] + ) + if cnuls: + cond = cond + " and " + cnuls + else: + cond += "" + cursor.execute( + "select distinct" + + ", ".join(what) + + " from entreprises E, entreprise_contact C " + + cond + + orderby, + vals, + ) + titles, res = [x[0] for x in cursor.description], cursor.fetchall() + # + R = [] + for r in res: + d = {} + for i in range(len(titles)): + v = r[i] + # value not formatted ! (see EditableTable.list()) + d[titles[i]] = v + R.append(d) + return R + + # -------- Formulaires: traductions du DTML + security.declareProtected(ScoEntrepriseChange, "entreprise_create") + + def entreprise_create(self, REQUEST=None): + """Form. création entreprise""" + context = self + H = [ + self.entreprise_header(REQUEST, page_title="Création d'une entreprise"), + """

    Création d'une entreprise

    """, + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ("nom", {"size": 25, "title": "Nom de l'entreprise"}), + ( + "adresse", + {"size": 30, "title": "Adresse", "explanation": "(numéro, rue)"}, + ), + ("codepostal", {"size": 8, "title": "Code Postal"}), + ("ville", {"size": 30, "title": "Ville"}), + ("pays", {"size": 30, "title": "Pays", "default": "France"}), + ( + "localisation", + { + "input_type": "menu", + "labels": ["Ile de France", "Province", "Etranger"], + "allowed_values": ["IDF", "Province", "Etranger"], + }, + ), + ("secteur", {"size": 30, "title": "Secteur d'activités"}), + ( + "privee", + { + "input_type": "menu", + "title": "Statut", + "labels": [ + "Entreprise privee", + "Entreprise Publique", + "Association", + ], + "allowed_values": ["privee", "publique", "association"], + }, + ), + ( + "plus10salaries", + { + "title": "Masse salariale", + "type": "integer", + "input_type": "menu", + "labels": [ + "10 salariés ou plus", + "Moins de 10 salariés", + "Inconnue", + ], + "allowed_values": [1, 0, -1], + }, + ), + ( + "qualite_relation", + { + "title": "Qualité relation IUT/Entreprise", + "input_type": "menu", + "default": "-1", + "labels": [ + "Très bonne", + "Bonne", + "Moyenne", + "Mauvaise", + "Inconnue", + ], + "allowed_values": ["100", "75", "50", "25", "-1"], + }, + ), + ("contact_origine", {"size": 30, "title": "Origine du contact"}), + ( + "note", + {"input_type": "textarea", "rows": 3, "cols": 40, "title": "Note"}, + ), + ), + cancelbutton="Annuler", + submitlabel="Ajouter cette entreprise", + readonly=REQUEST["_read_only"], + ) + if tf[0] == 0: + return "\n".join(H) + tf[1] + context.entreprise_footer(REQUEST) + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + self.do_entreprise_create(tf[2]) + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + + security.declareProtected(ScoEntrepriseView, "entreprise_edit") + + def entreprise_edit(self, entreprise_id, REQUEST=None, start=1): + """Form. edit entreprise""" + context = self + authuser = REQUEST.AUTHENTICATED_USER + readonly = not authuser.has_permission(ScoEntrepriseChange, self) + F = self.do_entreprise_list(args={"entreprise_id": entreprise_id})[0] + H = [ + self.entreprise_header(REQUEST, page_title="Entreprise"), + """

    %(nom)s

    """ % F, + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ("entreprise_id", {"default": entreprise_id, "input_type": "hidden"}), + ("start", {"default": 1, "input_type": "hidden"}), + ( + "date_creation", + {"default": time.strftime("%Y-%m-%d"), "input_type": "hidden"}, + ), + ("nom", {"size": 25, "title": "Nom de l'entreprise"}), + ( + "adresse", + {"size": 30, "title": "Adresse", "explanation": "(numéro, rue)"}, + ), + ("codepostal", {"size": 8, "title": "Code Postal"}), + ("ville", {"size": 30, "title": "Ville"}), + ("pays", {"size": 30, "title": "Pays", "default": "France"}), + ( + "localisation", + { + "input_type": "menu", + "labels": ["Ile de France", "Province", "Etranger"], + "allowed_values": ["IDF", "Province", "Etranger"], + }, + ), + ("secteur", {"size": 30, "title": "Secteur d'activités"}), + ( + "privee", + { + "input_type": "menu", + "title": "Statut", + "labels": [ + "Entreprise privee", + "Entreprise Publique", + "Association", + ], + "allowed_values": ["privee", "publique", "association"], + }, + ), + ( + "plus10salaries", + { + "title": "Masse salariale", + "input_type": "menu", + "labels": [ + "10 salariés ou plus", + "Moins de 10 salariés", + "Inconnue", + ], + "allowed_values": ["1", "0", "-1"], + }, + ), + ( + "qualite_relation", + { + "title": "Qualité relation IUT/Entreprise", + "input_type": "menu", + "labels": [ + "Très bonne", + "Bonne", + "Moyenne", + "Mauvaise", + "Inconnue", + ], + "allowed_values": ["100", "75", "50", "25", "-1"], + }, + ), + ("contact_origine", {"size": 30, "title": "Origine du contact"}), + ( + "note", + {"input_type": "textarea", "rows": 3, "cols": 40, "title": "Note"}, + ), + ), + cancelbutton="Annuler", + initvalues=F, + submitlabel="Modifier les valeurs", + readonly=readonly, + ) + + if tf[0] == 0: + H.append(tf[1]) + Cl = self.do_entreprise_correspondant_list( + args={"entreprise_id": F["entreprise_id"]} + ) + Cts = self.do_entreprise_contact_list( + args={"entreprise_id": F["entreprise_id"]} + ) + if not readonly: + H.append( + """

    %s Supprimer cette entreprise

    """ + % ( + icontag("delete_img", title="delete", border="0"), + F["entreprise_id"], + ) + ) + if len(Cl): + H.append( + """

    %d correspondants dans l'entreprise %s (liste complète) :

    +") + if len(Cts): + H.append( + """

    %d contacts avec l'entreprise %s (liste complète) :

      """ + % (len(Cts), F["nom"], F["entreprise_id"]) + ) + for c in Cts: + H.append( + """
    • %s   """ + % (c["entreprise_contact_id"], c["date"]) + ) + if c["type_contact"]: + H.append(c["type_contact"]) + if c["etudid"]: + etud = self.getEtudInfo(etudid=c["etudid"], filled=1) + if etud: + etud = etud[0] + H.append( + """%s""" + % (self.ScoURL(), c["etudid"], etud["nomprenom"]) + ) + if c["description"]: + H.append("(%s)" % c["description"]) + H.append("
    • ") + H.append("
    ") + return "\n".join(H) + context.entreprise_footer(REQUEST) + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect(REQUEST.URL1 + "?start=" + start) + else: + self.do_entreprise_edit(tf[2]) + return REQUEST.RESPONSE.redirect(REQUEST.URL1 + "?start=" + start) + + # --- Misc tools.... ------------------ + security.declareProtected(ScoEntrepriseView, "str_abbrev") + + def str_abbrev(self, s, maxlen): + "abreviation" + if s == None: + return "?" + if len(s) < maxlen: + return s + return s[: maxlen - 3] + "..." + + security.declareProtected(ScoEntrepriseView, "setPageSizeCookie") + + def setPageSizeCookie(self, REQUEST=None): + "set page size cookie" + RESPONSE = REQUEST.RESPONSE + # + if REQUEST.form.has_key("entreprise_page_size"): + RESPONSE.setCookie( + "entreprise_page_size", + REQUEST.form["entreprise_page_size"], + path="/", + expires="Wed, 31-Dec-2025 23:55:00 GMT", + ) + RESPONSE.redirect(REQUEST.form["target_url"]) + + security.declareProtected(ScoEntrepriseView, "make_link_create_corr") + + def make_link_create_corr(self, entreprise_id): + "yet another stupid code snippet" + return ( + 'créer un nouveau correspondant' + ) + + +# -------------------------------------------------------------------- +# +# Zope Product Administration +# +# -------------------------------------------------------------------- +def manage_addZEntreprises( + self, id="id_ZEntreprises", title="The Title for ZEntreprises Object", REQUEST=None +): + "Add a ZEntreprises instance to a folder." + self._setObject(id, ZEntreprises(id, title)) + if REQUEST is not None: + return self.manage_main(self, REQUEST) + # return self.manage_editForm(self, REQUEST) + + +# The form used to get the instance id from the user. +# manage_addZAbsencesForm = DTMLFile('dtml/manage_addZAbsencesForm', globals()) diff --git a/ZNotes.py b/ZNotes.py new file mode 100644 index 00000000..b9946c86 --- /dev/null +++ b/ZNotes.py @@ -0,0 +1,3647 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +"""Interface Zope <-> Notes +""" + +from sets import Set + +from sco_zope import * + +# --------------- + +from notesdb import * +from notes_log import log, sendAlarm +import scolog +from scolog import logdb +from sco_utils import * +import htmlutils +import sco_excel + +# import notes_users +from gen_tables import GenTable +import sco_cache +import scolars +import sco_news +from sco_news import NEWS_INSCR, NEWS_NOTE, NEWS_FORM, NEWS_SEM, NEWS_MISC + +import sco_formsemestre +import sco_formsemestre_edit +import sco_formsemestre_status +import sco_formsemestre_inscriptions +import sco_formsemestre_custommenu +import sco_moduleimpl_status +import sco_moduleimpl_inscriptions +import sco_evaluations +import sco_groups +import sco_edit_ue +import sco_edit_formation +import sco_edit_matiere +import sco_edit_module +import sco_tag_module +import sco_bulletins +import sco_bulletins_pdf +import sco_recapcomplet +import sco_liste_notes +import sco_saisie_notes +import sco_placement +import sco_undo_notes +import sco_formations +import sco_report +import sco_lycee +import sco_poursuite_dut +import pe_view +import sco_debouche +import sco_ue_external +import sco_cost_formation +import sco_formsemestre_validation +import sco_parcours_dut +import sco_codes_parcours +import sco_pvjury +import sco_pvpdf +import sco_prepajury +import sco_inscr_passage +import sco_synchro_etuds +import sco_archives +import sco_apogee_csv +import sco_etape_apogee_view +import sco_apogee_compare +import sco_semset +import sco_export_results +import sco_formsemestre_exterieurs + +from sco_pdf import PDFLOCK +from notes_table import * +import VERSION + +# +# Cache global: chaque instance, repérée par sa connexion db, a un cache +# qui est recréé à la demande +# +CACHE_formsemestre_inscription = {} +CACHE_evaluations = {} + +# --------------- + + +class ZNotes(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit): + + "ZNotes object" + + meta_type = "ZNotes" + security = ClassSecurityInfo() + + # This is the list of the methods associated to 'tabs' in the ZMI + # Be aware that The first in the list is the one shown by default, so if + # the 'View' tab is the first, you will never see your tabs by cliquing + # on the object. + manage_options = ( + ({"label": "Contents", "action": "manage_main"},) + + PropertyManager.manage_options # add the 'Properties' tab + + ( + # this line is kept as an example with the files : + # dtml/manage_editZScolarForm.dtml + # html/ZScolar-edit.stx + # {'label': 'Properties', 'action': 'manage_editForm',}, + {"label": "View", "action": "index_html"}, + ) + + Item.manage_options # add the 'Undo' & 'Owner' tab + + RoleManager.manage_options # add the 'Security' tab + ) + + # no permissions, only called from python + def __init__(self, id, title): + "initialise a new instance of ZNotes" + self.id = id + self.title = title + + # The form used to edit this object + def manage_editZNotes(self, title, RESPONSE=None): + "Changes the instance values" + self.title = title + self._p_changed = 1 + RESPONSE.redirect("manage_editForm") + + def _getNotesCache(self): + "returns CacheNotesTable instance for us" + u = self.GetDBConnexionString() # identifie le dept de facon fiable + if not NOTES_CACHE_INST.has_key(u): + log("getNotesCache: creating cache for %s" % u) + NOTES_CACHE_INST[u] = CacheNotesTable() + return NOTES_CACHE_INST[u] + + def _inval_cache( + self, formsemestre_id=None, pdfonly=False, formsemestre_id_list=None + ): # > + "expire cache pour un semestre (ou tous si pas d'argument)" + if formsemestre_id_list: + for formsemestre_id in formsemestre_id_list: + self._getNotesCache().inval_cache( + self, formsemestre_id=formsemestre_id, pdfonly=pdfonly + ) + # Affecte aussi cache inscriptions + self.get_formsemestre_inscription_cache().inval_cache( + key=formsemestre_id + ) # > + else: + self._getNotesCache().inval_cache( + self, formsemestre_id=formsemestre_id, pdfonly=pdfonly + ) # > + # Affecte aussi cache inscriptions + self.get_formsemestre_inscription_cache().inval_cache( + key=formsemestre_id + ) # > + + security.declareProtected(ScoView, "clearcache") + + def clearcache(self, REQUEST=None): + "Efface les caches de notes (utile pendant developpement slt)" + log("*** clearcache request") + # Debugging code: compare results before and after cache reconstruction + # (_should_ be identicals !) + # Compare XML representation + cache = self._getNotesCache() + formsemestre_ids = cache.get_cached_formsemestre_ids() + docs_before = [] + for formsemestre_id in formsemestre_ids: + docs_before.append( + sco_recapcomplet.do_formsemestre_recapcomplet( + self, REQUEST, formsemestre_id, format="xml", xml_nodate=True + ) + ) + # + cache.inval_cache(self) # > + # Rebuild cache (useful only to debug) + docs_after = [] + for formsemestre_id in formsemestre_ids: + docs_after.append( + sco_recapcomplet.do_formsemestre_recapcomplet( + self, REQUEST, formsemestre_id, format="xml", xml_nodate=True + ) + ) + if docs_before != docs_after: + log("clearcache: inconsistency !") + txt = "before=" + repr(docs_before) + "\n\nafter=" + repr(docs_after) + "\n" + log(txt) + sendAlarm(self, "clearcache: inconsistency !", txt) + + # -------------------------------------------------------------------- + # + # NOTES (top level) + # + # -------------------------------------------------------------------- + # XXX essai + security.declareProtected(ScoView, "gloups") + + def gloups(self, REQUEST): + "essai gloups" + return "" + # return pdfbulletins.essaipdf(REQUEST) + # return sendPDFFile(REQUEST, pdfbulletins.pdftrombino(0,0), 'toto.pdf' ) + + # Python methods: + security.declareProtected(ScoView, "formsemestre_status") + formsemestre_status = sco_formsemestre_status.formsemestre_status + + security.declareProtected(ScoImplement, "formsemestre_createwithmodules") + formsemestre_createwithmodules = ( + sco_formsemestre_edit.formsemestre_createwithmodules + ) + + security.declareProtected(ScoView, "formsemestre_editwithmodules") + formsemestre_editwithmodules = sco_formsemestre_edit.formsemestre_editwithmodules + + security.declareProtected(ScoView, "formsemestre_clone") + formsemestre_clone = sco_formsemestre_edit.formsemestre_clone + + security.declareProtected(ScoChangeFormation, "formsemestre_associate_new_version") + formsemestre_associate_new_version = ( + sco_formsemestre_edit.formsemestre_associate_new_version + ) + + security.declareProtected(ScoImplement, "formsemestre_delete") + formsemestre_delete = sco_formsemestre_edit.formsemestre_delete + security.declareProtected(ScoImplement, "formsemestre_delete2") + formsemestre_delete2 = sco_formsemestre_edit.formsemestre_delete2 + + security.declareProtected(ScoView, "formsemestre_recapcomplet") + formsemestre_recapcomplet = sco_recapcomplet.formsemestre_recapcomplet + + security.declareProtected(ScoView, "moduleimpl_status") + moduleimpl_status = sco_moduleimpl_status.moduleimpl_status + + security.declareProtected(ScoView, "formsemestre_description") + formsemestre_description = sco_formsemestre_status.formsemestre_description + + security.declareProtected(ScoView, "formsemestre_lists") + formsemestre_lists = sco_formsemestre_status.formsemestre_lists + + security.declareProtected(ScoView, "formsemestre_status_menubar") + formsemestre_status_menubar = sco_formsemestre_status.formsemestre_status_menubar + security.declareProtected(ScoChangeFormation, "formation_create") + formation_create = sco_edit_formation.formation_create + security.declareProtected(ScoChangeFormation, "formation_delete") + formation_delete = sco_edit_formation.formation_delete + security.declareProtected(ScoChangeFormation, "formation_edit") + formation_edit = sco_edit_formation.formation_edit + + security.declareProtected(ScoView, "formsemestre_bulletinetud") + formsemestre_bulletinetud = sco_bulletins.formsemestre_bulletinetud + + security.declareProtected(ScoView, "formsemestre_evaluations_cal") + formsemestre_evaluations_cal = sco_evaluations.formsemestre_evaluations_cal + security.declareProtected(ScoView, "formsemestre_evaluations_delai_correction") + formsemestre_evaluations_delai_correction = ( + sco_evaluations.formsemestre_evaluations_delai_correction + ) + + security.declareProtected(ScoView, "module_evaluation_renumber") + module_evaluation_renumber = sco_evaluations.module_evaluation_renumber + security.declareProtected(ScoView, "module_evaluation_move") + module_evaluation_move = sco_evaluations.module_evaluation_move + + security.declareProtected(ScoView, "formsemestre_list_saisies_notes") + formsemestre_list_saisies_notes = sco_undo_notes.formsemestre_list_saisies_notes + + security.declareProtected(ScoChangeFormation, "ue_create") + ue_create = sco_edit_ue.ue_create + security.declareProtected(ScoChangeFormation, "ue_delete") + ue_delete = sco_edit_ue.ue_delete + security.declareProtected(ScoChangeFormation, "ue_edit") + ue_edit = sco_edit_ue.ue_edit + security.declareProtected(ScoView, "ue_list") + ue_list = sco_edit_ue.ue_list + security.declareProtected(ScoView, "ue_sharing_code") + ue_sharing_code = sco_edit_ue.ue_sharing_code + security.declareProtected(ScoChangeFormation, "edit_ue_set_code_apogee") + edit_ue_set_code_apogee = sco_edit_ue.edit_ue_set_code_apogee + security.declareProtected(ScoView, "formation_table_recap") + formation_table_recap = sco_edit_ue.formation_table_recap + security.declareProtected(ScoChangeFormation, "formation_add_malus_modules") + formation_add_malus_modules = sco_edit_module.formation_add_malus_modules + + security.declareProtected(ScoChangeFormation, "matiere_create") + matiere_create = sco_edit_matiere.matiere_create + security.declareProtected(ScoChangeFormation, "matiere_delete") + matiere_delete = sco_edit_matiere.matiere_delete + security.declareProtected(ScoChangeFormation, "matiere_edit") + matiere_edit = sco_edit_matiere.matiere_edit + + security.declareProtected(ScoChangeFormation, "module_create") + module_create = sco_edit_module.module_create + security.declareProtected(ScoChangeFormation, "module_delete") + module_delete = sco_edit_module.module_delete + security.declareProtected(ScoChangeFormation, "module_edit") + module_edit = sco_edit_module.module_edit + security.declareProtected(ScoChangeFormation, "edit_module_set_code_apogee") + edit_module_set_code_apogee = sco_edit_module.edit_module_set_code_apogee + security.declareProtected(ScoView, "module_list") + module_list = sco_edit_module.module_list + # Tags + security.declareProtected(ScoView, "module_tag_search") + module_tag_search = sco_tag_module.module_tag_search + security.declareProtected( + ScoView, "module_tag_set" + ) # should be ScoEditFormationTags, but not present in old installs => check in method + module_tag_set = sco_tag_module.module_tag_set + + # + security.declareProtected(ScoView, "index_html") + + def index_html(self, REQUEST=None): + "Page accueil formations" + lockicon = icontag( + "lock32_img", title="Comporte des semestres verrouillés", border="0" + ) + suppricon = icontag( + "delete_small_img", border="0", alt="supprimer", title="Supprimer" + ) + editicon = icontag( + "edit_img", border="0", alt="modifier", title="Modifier titres et code" + ) + + editable = REQUEST.AUTHENTICATED_USER.has_permission(ScoChangeFormation, self) + + H = [ + self.sco_header(REQUEST, page_title="Programmes formations"), + """

    Programmes pédagogiques

    + """, + ] + T = sco_formations.formation_list_table(self, REQUEST=REQUEST) + + H.append(T.html()) + + if editable: + H.append( + """

    Créer une formation

    +

    Importer une formation (xml)

    +

    Une "formation" est un programme pédagogique structuré en UE, matières et modules. Chaque semestre se réfère à une formation. La modification d'une formation affecte tous les semestres qui s'y réfèrent.

    + """ + ) + + H.append(self.sco_footer(REQUEST)) + return "\n".join(H) + + # -------------------------------------------------------------------- + # + # Notes Methods + # + # -------------------------------------------------------------------- + + # --- Formations + _formationEditor = EditableTable( + "notes_formations", + "formation_id", + ( + "formation_id", + "acronyme", + "titre", + "titre_officiel", + "version", + "formation_code", + "type_parcours", + "code_specialite", + ), + sortkey="acronyme", + ) + + security.declareProtected(ScoChangeFormation, "do_formation_create") + + def do_formation_create(self, args, REQUEST): + "create a formation" + cnx = self.GetDBConnexion() + # check unique acronyme/titre/version + a = args.copy() + if a.has_key("formation_id"): + del a["formation_id"] + F = self.formation_list(args=a) + if len(F) > 0: + log( + "do_formation_create: error: %d formations matching args=%s" + % (len(F), a) + ) + raise ScoValueError("Formation non unique (%s) !" % str(a)) + # Si pas de formation_code, l'enleve (default SQL) + if args.has_key("formation_code") and not args["formation_code"]: + del args["formation_code"] + # + r = self._formationEditor.create(cnx, args) + + sco_news.add( + self, + REQUEST, + typ=NEWS_FORM, + text="Création de la formation %(titre)s (%(acronyme)s)" % args, + ) + return r + + security.declareProtected(ScoChangeFormation, "do_formation_delete") + + def do_formation_delete(self, oid, REQUEST): + """delete a formation (and all its UE, matieres, modules) + XXX delete all ues, will break if there are validations ! USE WITH CARE ! + """ + F = self.formation_list(args={"formation_id": oid})[0] + if self.formation_has_locked_sems(oid): + raise ScoLockedFormError() + cnx = self.GetDBConnexion() + # delete all UE in this formation + ues = self.do_ue_list({"formation_id": oid}) + for ue in ues: + self._do_ue_delete(ue["ue_id"], REQUEST=REQUEST, force=True) + + self._formationEditor.delete(cnx, oid) + + # news + sco_news.add( + self, + REQUEST, + typ=NEWS_FORM, + object=oid, + text="Suppression de la formation %(acronyme)s" % F, + ) + + security.declareProtected(ScoView, "formation_list") + + def formation_list(self, format=None, REQUEST=None, formation_id=None, args={}): + """List formation(s) with given id, or matching args + (when args is given, formation_id is ignored). + """ + # logCallStack() + if not args: + if formation_id is None: + args = {} + else: + args = {"formation_id": formation_id} + cnx = self.GetDBConnexion() + r = self._formationEditor.list(cnx, args=args) + # log('%d formations found' % len(r)) + return sendResult(REQUEST, r, name="formation", format=format) + + security.declareProtected(ScoView, "formation_export") + + def formation_export( + self, formation_id, export_ids=False, format=None, REQUEST=None + ): + "Export de la formation au format indiqué (xml ou json)" + return sco_formations.formation_export( + self, formation_id, export_ids=export_ids, format=format, REQUEST=REQUEST + ) + + security.declareProtected(ScoChangeFormation, "formation_import_xml") + + def formation_import_xml(self, file, REQUEST): + "import d'une formation en XML" + log("formation_import_xml") + doc = file.read() + return sco_formations.formation_import_xml(self, REQUEST, doc) + + security.declareProtected(ScoChangeFormation, "formation_import_xml_form") + + def formation_import_xml_form(self, REQUEST): + "form import d'une formation en XML" + H = [ + self.sco_header(page_title="Import d'une formation", REQUEST=REQUEST), + """

    Import d'une formation

    +

    Création d'une formation (avec UE, matières, modules) + à partir un fichier XML (réservé aux utilisateurs avertis)

    + """, + ] + footer = self.sco_footer(REQUEST) + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + (("xmlfile", {"input_type": "file", "title": "Fichier XML", "size": 30}),), + submitlabel="Importer", + cancelbutton="Annuler", + ) + if tf[0] == 0: + return "\n".join(H) + tf[1] + footer + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + formation_id, junk, junk = self.formation_import_xml( + tf[2]["xmlfile"], REQUEST + ) + + return ( + "\n".join(H) + + """

    Import effectué !

    +

    Voir la formation

    """ + % formation_id + + footer + ) + + security.declareProtected(ScoChangeFormation, "formation_create_new_version") + + def formation_create_new_version(self, formation_id, redirect=True, REQUEST=None): + "duplicate formation, with new version number" + xml = sco_formations.formation_export( + self, formation_id, export_ids=True, format="xml" + ) + new_id, modules_old2new, ues_old2new = sco_formations.formation_import_xml( + self, REQUEST, xml + ) + # news + F = self.formation_list(args={"formation_id": new_id})[0] + sco_news.add( + self, + REQUEST, + typ=NEWS_FORM, + object=new_id, + text="Nouvelle version de la formation %(acronyme)s" % F, + ) + if redirect: + return REQUEST.RESPONSE.redirect( + "ue_list?formation_id=" + new_id + "&msg=Nouvelle version !" + ) + else: + return new_id, modules_old2new, ues_old2new + + # --- UE + _ueEditor = EditableTable( + "notes_ue", + "ue_id", + ( + "ue_id", + "formation_id", + "acronyme", + "numero", + "titre", + "type", + "ue_code", + "ects", + "is_external", + "code_apogee", + "coefficient", + ), + sortkey="numero", + input_formators={"type": int_null_is_zero}, + output_formators={ + "numero": int_null_is_zero, + "ects": float_null_is_null, + "coefficient": float_null_is_zero, + }, + ) + + security.declareProtected(ScoChangeFormation, "do_ue_create") + + def do_ue_create(self, args, REQUEST): + "create an ue" + cnx = self.GetDBConnexion() + # check duplicates + ues = self.do_ue_list( + {"formation_id": args["formation_id"], "acronyme": args["acronyme"]} + ) + if ues: + raise ScoValueError('Acronyme d\'UE "%s" déjà utilisé !' % args["acronyme"]) + # create + r = self._ueEditor.create(cnx, args) + + # news + F = self.formation_list(args={"formation_id": args["formation_id"]})[0] + sco_news.add( + self, + REQUEST, + typ=NEWS_FORM, + object=args["formation_id"], + text="Modification de la formation %(acronyme)s" % F, + ) + return r + + def _do_ue_delete(self, ue_id, delete_validations=False, REQUEST=None, force=False): + "delete UE and attached matieres (but not modules (it should ?))" + cnx = self.GetDBConnexion() + log( + "do_ue_delete: ue_id=%s, delete_validations=%s" + % (ue_id, delete_validations) + ) + # check + ue = self.do_ue_list({"ue_id": ue_id}) + if not ue: + raise ScoValueError("UE inexistante !") + ue = ue[0] + if self.ue_is_locked(ue["ue_id"]): + raise ScoLockedFormError() + # Il y a-t-il des etudiants ayant validé cette UE ? + # si oui, propose de supprimer les validations + validations = sco_parcours_dut.scolar_formsemestre_validation_list( + cnx, args={"ue_id": ue_id} + ) + if validations and not delete_validations and not force: + return self.confirmDialog( + "

    %d étudiants ont validé l'UE %s (%s)

    Si vous supprimez cette UE, ces validations vont être supprimées !

    " + % (len(validations), ue["acronyme"], ue["titre"]), + dest_url="", + REQUEST=REQUEST, + target_variable="delete_validations", + cancel_url="ue_list?formation_id=%s" % ue["formation_id"], + parameters={"ue_id": ue_id, "dialog_confirmed": 1}, + ) + if delete_validations: + log("deleting all validations of UE %s" % ue_id) + SimpleQuery( + self, + "DELETE FROM scolar_formsemestre_validation WHERE ue_id=%(ue_id)s", + {"ue_id": ue_id}, + ) + + # delete all matiere in this UE + mats = self.do_matiere_list({"ue_id": ue_id}) + for mat in mats: + self.do_matiere_delete(mat["matiere_id"], REQUEST) + # delete uecoef and events + SimpleQuery( + self, + "DELETE FROM notes_formsemestre_uecoef WHERE ue_id=%(ue_id)s", + {"ue_id": ue_id}, + ) + SimpleQuery( + self, "DELETE FROM scolar_events WHERE ue_id=%(ue_id)s", {"ue_id": ue_id} + ) + cnx = self.GetDBConnexion() + self._ueEditor.delete(cnx, ue_id) + self._inval_cache() # > UE delete + supr. validations associées etudiants (cas compliqué, mais rarement utilisé: acceptable de tout invalider ?) + # news + F = self.formation_list(args={"formation_id": ue["formation_id"]})[0] + sco_news.add( + self, + REQUEST, + typ=NEWS_FORM, + object=ue["formation_id"], + text="Modification de la formation %(acronyme)s" % F, + ) + # + if not force: + return REQUEST.RESPONSE.redirect( + REQUEST.URL1 + "/ue_list?formation_id=" + str(ue["formation_id"]) + ) + else: + return None + + security.declareProtected(ScoView, "do_ue_list") + + def do_ue_list(self, *args, **kw): + "list UEs" + cnx = self.GetDBConnexion() + return self._ueEditor.list(cnx, *args, **kw) + + # --- Matieres + _matiereEditor = EditableTable( + "notes_matieres", + "matiere_id", + ("matiere_id", "ue_id", "numero", "titre"), + sortkey="numero", + output_formators={"numero": int_null_is_zero}, + ) + + security.declareProtected(ScoChangeFormation, "do_matiere_create") + + def do_matiere_create(self, args, REQUEST): + "create a matiere" + cnx = self.GetDBConnexion() + # check + ue = self.do_ue_list({"ue_id": args["ue_id"]})[0] + # create matiere + r = self._matiereEditor.create(cnx, args) + + # news + F = self.formation_list(args={"formation_id": ue["formation_id"]})[0] + sco_news.add( + self, + REQUEST, + typ=NEWS_FORM, + object=ue["formation_id"], + text="Modification de la formation %(acronyme)s" % F, + ) + return r + + security.declareProtected(ScoChangeFormation, "do_matiere_delete") + + def do_matiere_delete(self, oid, REQUEST): + "delete matiere and attached modules" + cnx = self.GetDBConnexion() + # check + mat = self.do_matiere_list({"matiere_id": oid})[0] + ue = self.do_ue_list({"ue_id": mat["ue_id"]})[0] + locked = self.matiere_is_locked(mat["matiere_id"]) + if locked: + log("do_matiere_delete: mat=%s" % mat) + log("do_matiere_delete: ue=%s" % ue) + log("do_matiere_delete: locked sems: %s" % locked) + raise ScoLockedFormError() + log("do_matiere_delete: matiere_id=%s" % oid) + # delete all modules in this matiere + mods = self.do_module_list({"matiere_id": oid}) + for mod in mods: + self.do_module_delete(mod["module_id"], REQUEST) + self._matiereEditor.delete(cnx, oid) + + # news + F = self.formation_list(args={"formation_id": ue["formation_id"]})[0] + sco_news.add( + self, + REQUEST, + typ=NEWS_FORM, + object=ue["formation_id"], + text="Modification de la formation %(acronyme)s" % F, + ) + + security.declareProtected(ScoView, "do_matiere_list") + + def do_matiere_list(self, *args, **kw): + "list matieres" + cnx = self.GetDBConnexion() + return self._matiereEditor.list(cnx, *args, **kw) + + security.declareProtected(ScoChangeFormation, "do_matiere_edit") + + def do_matiere_edit(self, *args, **kw): + "edit a matiere" + cnx = self.GetDBConnexion() + # check + mat = self.do_matiere_list({"matiere_id": args[0]["matiere_id"]})[0] + ue = self.do_ue_list({"ue_id": mat["ue_id"]})[0] + if self.matiere_is_locked(mat["matiere_id"]): + raise ScoLockedFormError() + # edit + self._matiereEditor.edit(cnx, *args, **kw) + self._inval_cache() # > modif matiere + + security.declareProtected(ScoView, "do_matiere_formation_id") + + def do_matiere_formation_id(self, matiere_id): + "get formation_id from matiere" + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "select UE.formation_id from notes_matieres M, notes_ue UE where M.matiere_id = %(matiere_id)s and M.ue_id = UE.ue_id", + {"matiere_id": matiere_id}, + ) + res = cursor.fetchall() + return res[0][0] + + # --- Modules + _moduleEditor = EditableTable( + "notes_modules", + "module_id", + ( + "module_id", + "titre", + "code", + "abbrev", + "heures_cours", + "heures_td", + "heures_tp", + "coefficient", + "ue_id", + "matiere_id", + "formation_id", + "semestre_id", + "numero", + "code_apogee", + "module_type" + #'ects' + ), + sortkey="numero, code, titre", + output_formators={ + "heures_cours": float_null_is_zero, + "heures_td": float_null_is_zero, + "heures_tp": float_null_is_zero, + "numero": int_null_is_zero, + "coefficient": float_null_is_zero, + "module_type": int_null_is_zero + #'ects' : float_null_is_null + }, + ) + + security.declareProtected(ScoChangeFormation, "do_module_create") + + def do_module_create(self, args, REQUEST): + "create a module" + # create + cnx = self.GetDBConnexion() + r = self._moduleEditor.create(cnx, args) + + # news + F = self.formation_list(args={"formation_id": args["formation_id"]})[0] + sco_news.add( + self, + REQUEST, + typ=NEWS_FORM, + object=args["formation_id"], + text="Modification de la formation %(acronyme)s" % F, + ) + return r + + security.declareProtected(ScoChangeFormation, "do_module_delete") + + def do_module_delete(self, oid, REQUEST): + "delete module" + mod = self.do_module_list({"module_id": oid})[0] + if self.module_is_locked(mod["module_id"]): + raise ScoLockedFormError() + + # S'il y a des moduleimpls, on ne peut pas detruire le module ! + mods = self.do_moduleimpl_list(module_id=oid) + if mods: + err_page = self.confirmDialog( + message="""

    Destruction du module impossible car il est utilisé dans des semestres existants !

    """, + helpmsg="""Il faut d'abord supprimer le semestre. Mais il est peut être préférable de laisser ce programme intact et d'en créer une nouvelle version pour la modifier.""", + dest_url="ue_list", + parameters={"formation_id": mod["formation_id"]}, + REQUEST=REQUEST, + ) + raise ScoGenError(err_page) + # delete + cnx = self.GetDBConnexion() + self._moduleEditor.delete(cnx, oid) + + # news + F = self.formation_list(args={"formation_id": mod["formation_id"]})[0] + sco_news.add( + self, + REQUEST, + typ=NEWS_FORM, + object=mod["formation_id"], + text="Modification de la formation %(acronyme)s" % F, + ) + + security.declareProtected(ScoView, "do_module_list") + + def do_module_list(self, *args, **kw): + "list modules" + cnx = self.GetDBConnexion() + return self._moduleEditor.list(cnx, *args, **kw) + + security.declareProtected(ScoChangeFormation, "do_module_edit") + + def do_module_edit(self, val): + "edit a module" + # check + mod = self.do_module_list({"module_id": val["module_id"]})[0] + if self.module_is_locked(mod["module_id"]): + # formation verrouillée: empeche de modifier certains champs: + protected_fields = ("coefficient", "ue_id", "matiere_id", "semestre_id") + for f in protected_fields: + if f in val: + del val[f] + # edit + cnx = self.GetDBConnexion() + self._moduleEditor.edit(cnx, val) + + sems = sco_formsemestre.do_formsemestre_list( + self, args={"formation_id": mod["formation_id"]} + ) + if sems: + self._inval_cache( + formsemestre_id_list=[s["formsemestre_id"] for s in sems] + ) # > modif module + + # + security.declareProtected(ScoView, "formation_has_locked_sems") + + def formation_has_locked_sems(self, formation_id): + "True if there is a locked formsemestre in this formation" + sems = sco_formsemestre.do_formsemestre_list( + self, args={"formation_id": formation_id, "etat": "0"} + ) + return sems + + security.declareProtected(ScoView, "formation_count_sems") + + def formation_count_sems(self, formation_id): + "Number of formsemestre in this formation (locked or not)" + sems = sco_formsemestre.do_formsemestre_list( + self, args={"formation_id": formation_id} + ) + return len(sems) + + security.declareProtected(ScoView, "module_count_moduleimpls") + + def module_count_moduleimpls(self, module_id): + "Number of moduleimpls using this module" + mods = self.do_moduleimpl_list(module_id=module_id) + return len(mods) + + security.declareProtected(ScoView, "module_is_locked") + + def module_is_locked(self, module_id): + """True if UE should not be modified + (used in a locked formsemestre) + """ + r = SimpleDictFetch( + self, + """SELECT mi.* from notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi + WHERE mi.module_id = mod.module_id AND mi.formsemestre_id = sem.formsemestre_id + AND mi.module_id = %(module_id)s AND sem.etat = 0 + """, + {"module_id": module_id}, + ) + return len(r) > 0 + + security.declareProtected(ScoView, "matiere_is_locked") + + def matiere_is_locked(self, matiere_id): + """True if matiere should not be modified + (contains modules used in a locked formsemestre) + """ + r = SimpleDictFetch( + self, + """SELECT ma.* from notes_matieres ma, notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi + WHERE ma.matiere_id = mod.matiere_id AND mi.module_id = mod.module_id AND mi.formsemestre_id = sem.formsemestre_id + AND ma.matiere_id = %(matiere_id)s AND sem.etat = 0 + """, + {"matiere_id": matiere_id}, + ) + return len(r) > 0 + + security.declareProtected(ScoView, "ue_is_locked") + + def ue_is_locked(self, ue_id): + """True if module should not be modified + (contains modules used in a locked formsemestre) + """ + r = SimpleDictFetch( + self, + """SELECT ue.* FROM notes_ue ue, notes_modules mod, notes_formsemestre sem, notes_moduleimpl mi + WHERE ue.ue_id = mod.ue_id + AND mi.module_id = mod.module_id AND mi.formsemestre_id = sem.formsemestre_id + AND ue.ue_id = %(ue_id)s AND sem.etat = 0 + """, + {"ue_id": ue_id}, + ) + return len(r) > 0 + + security.declareProtected(ScoChangeFormation, "module_move") + + def module_move(self, module_id, after=0, REQUEST=None, redirect=1): + """Move before/after previous one (decrement/increment numero)""" + module = self.do_module_list({"module_id": module_id})[0] + redirect = int(redirect) + after = int(after) # 0: deplace avant, 1 deplace apres + if after not in (0, 1): + raise ValueError('invalid value for "after"') + formation_id = module["formation_id"] + others = self.do_module_list({"matiere_id": module["matiere_id"]}) + # log('others=%s' % others) + if len(others) > 1: + idx = [p["module_id"] for p in others].index(module_id) + # log('module_move: after=%s idx=%s' % (after, idx)) + neigh = None # object to swap with + if after == 0 and idx > 0: + neigh = others[idx - 1] + elif after == 1 and idx < len(others) - 1: + neigh = others[idx + 1] + if neigh: # + # swap numero between partition and its neighbor + # log('moving module %s' % module_id) + cnx = self.GetDBConnexion() + module["numero"], neigh["numero"] = neigh["numero"], module["numero"] + if module["numero"] == neigh["numero"]: + neigh["numero"] -= 2 * after - 1 + self._moduleEditor.edit(cnx, module) + self._moduleEditor.edit(cnx, neigh) + + # redirect to ue_list page: + if redirect: + return REQUEST.RESPONSE.redirect("ue_list?formation_id=" + formation_id) + + def ue_move(self, ue_id, after=0, REQUEST=None, redirect=1): + """Move UE before/after previous one (decrement/increment numero)""" + o = self.do_ue_list({"ue_id": ue_id})[0] + # log('ue_move %s (#%s) after=%s' % (ue_id, o['numero'], after)) + redirect = int(redirect) + after = int(after) # 0: deplace avant, 1 deplace apres + if after not in (0, 1): + raise ValueError('invalid value for "after"') + formation_id = o["formation_id"] + others = self.do_ue_list({"formation_id": formation_id}) + if len(others) > 1: + idx = [p["ue_id"] for p in others].index(ue_id) + neigh = None # object to swap with + if after == 0 and idx > 0: + neigh = others[idx - 1] + elif after == 1 and idx < len(others) - 1: + neigh = others[idx + 1] + if neigh: # + # swap numero between partition and its neighbor + # log('moving ue %s (neigh #%s)' % (ue_id, neigh['numero'])) + cnx = self.GetDBConnexion() + o["numero"], neigh["numero"] = neigh["numero"], o["numero"] + if o["numero"] == neigh["numero"]: + neigh["numero"] -= 2 * after - 1 + self._ueEditor.edit(cnx, o) + self._ueEditor.edit(cnx, neigh) + # redirect to ue_list page + if redirect: + return REQUEST.RESPONSE.redirect( + "ue_list?formation_id=" + o["formation_id"] + ) + + # --- Semestres de formation + + security.declareProtected(ScoImplement, "do_formsemestre_create") + + def do_formsemestre_create(self, args, REQUEST, silent=False): + "create a formsemestre" + cnx = self.GetDBConnexion() + formsemestre_id = sco_formsemestre._formsemestreEditor.create(cnx, args) + if args["etapes"]: + args["formsemestre_id"] = formsemestre_id + sco_formsemestre.write_formsemestre_etapes(self, args) + if args["responsables"]: + args["formsemestre_id"] = formsemestre_id + sco_formsemestre.write_formsemestre_responsables(self, args) + + # create default partition + partition_id = sco_groups.partition_create( + self, formsemestre_id, default=True, redirect=0, REQUEST=REQUEST + ) + _group_id = sco_groups.createGroup( + self, partition_id, default=True, REQUEST=REQUEST + ) + + # news + if not args.has_key("titre"): + args["titre"] = "sans titre" + args["formsemestre_id"] = formsemestre_id + args["url"] = ( + "Notes/formsemestre_status?formsemestre_id=%(formsemestre_id)s" % args + ) + if not silent: + sco_news.add( + self, + REQUEST, + typ=NEWS_SEM, + text='Création du semestre %(titre)s' % args, + url=args["url"], + ) + return formsemestre_id + + def formsemestre_list( + self, + format=None, + REQUEST=None, + formsemestre_id=None, + formation_id=None, + etape_apo=None, + ): + """List formsemestres in given format. + kw can specify some conditions: examples: + formsemestre_list( format='json', formation_id='F777', REQUEST=REQUEST) + """ + # XAPI: new json api + args = {} + L = locals() + for argname in ("formsemestre_id", "formation_id", "etape_apo"): + if L[argname] is not None: + args[argname] = L[argname] + sems = sco_formsemestre.do_formsemestre_list(self, args=args) + # log('formsemestre_list: format="%s", %s semestres found' % (format,len(sems))) + return sendResult(REQUEST, sems, name="formsemestre", format=format) + + security.declareProtected(ScoView, "XMLgetFormsemestres") + + def XMLgetFormsemestres(self, etape_apo=None, formsemestre_id=None, REQUEST=None): + """List all formsemestres matching etape, XML format + DEPRECATED: use formsemestre_list() + """ + log("Warning: calling deprecated XMLgetFormsemestres") + args = {} + if etape_apo: + args["etape_apo"] = etape_apo + if formsemestre_id: + args["formsemestre_id"] = formsemestre_id + if REQUEST: + REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE) + doc = jaxml.XML_document(encoding=SCO_ENCODING) + doc.formsemestrelist() + for sem in sco_formsemestre.do_formsemestre_list(self, args=args): + doc._push() + doc.formsemestre(sem) + doc._pop() + return repr(doc) + + security.declareProtected(ScoImplement, "do_formsemestre_edit") + do_formsemestre_edit = sco_formsemestre.do_formsemestre_edit + + security.declareProtected(ScoView, "formsemestre_edit_options") + formsemestre_edit_options = sco_formsemestre_edit.formsemestre_edit_options + + security.declareProtected(ScoView, "formsemestre_change_lock") + formsemestre_change_lock = sco_formsemestre_edit.formsemestre_change_lock + + security.declareProtected(ScoView, "formsemestre_change_publication_bul") + formsemestre_change_publication_bul = ( + sco_formsemestre_edit.formsemestre_change_publication_bul + ) + + security.declareProtected(ScoView, "view_formsemestre_by_etape") + view_formsemestre_by_etape = sco_formsemestre.view_formsemestre_by_etape + + def _check_access_diretud( + self, formsemestre_id, REQUEST, required_permission=ScoImplement + ): + """Check if access granted: responsable or ScoImplement + Return True|False, HTML_error_page + """ + authuser = REQUEST.AUTHENTICATED_USER + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + header = self.sco_header(page_title="Accès interdit", REQUEST=REQUEST) + footer = self.sco_footer(REQUEST) + if (str(authuser) not in sem["responsables"]) and not authuser.has_permission( + required_permission, self + ): + return ( + False, + "\n".join( + [ + header, + "

    Opération non autorisée pour %s

    " % authuser, + "

    Responsable de ce semestre : %s

    " + % ", ".join(sem["responsables"]), + footer, + ] + ), + ) + else: + return True, "" + + security.declareProtected(ScoView, "formsemestre_custommenu_edit") + + def formsemestre_custommenu_edit(self, REQUEST, formsemestre_id): + "Dialogue modif menu" + # accessible à tous ! + return sco_formsemestre_custommenu.formsemestre_custommenu_edit( + self, formsemestre_id, REQUEST=REQUEST + ) + + security.declareProtected(ScoView, "formsemestre_custommenu_html") + formsemestre_custommenu_html = ( + sco_formsemestre_custommenu.formsemestre_custommenu_html + ) + + security.declareProtected(ScoView, "html_sem_header") + + def html_sem_header( + self, + REQUEST, + title, + sem=None, + with_page_header=True, + with_h2=True, + page_title=None, + **args + ): + "Titre d'une page semestre avec lien vers tableau de bord" + # sem now unused and thus optional... + if with_page_header: + h = self.sco_header( + REQUEST, page_title="%s" % (page_title or title), **args + ) + else: + h = "" + if with_h2: + return h + """

    %s

    """ % (title) + else: + return h + + # --- Gestion des "Implémentations de Modules" + # Un "moduleimpl" correspond a la mise en oeuvre d'un module + # dans une formation spécifique, à une date spécifique. + _moduleimplEditor = EditableTable( + "notes_moduleimpl", + "moduleimpl_id", + ( + "moduleimpl_id", + "module_id", + "formsemestre_id", + "responsable_id", + "computation_expr", + ), + ) + + _modules_enseignantsEditor = EditableTable( + "notes_modules_enseignants", + "modules_enseignants_id", + ("modules_enseignants_id", "moduleimpl_id", "ens_id"), + ) + + security.declareProtected(ScoImplement, "do_moduleimpl_create") + + def do_moduleimpl_create(self, args): + "create a moduleimpl" + cnx = self.GetDBConnexion() + r = self._moduleimplEditor.create(cnx, args) + self._inval_cache( + formsemestre_id=args["formsemestre_id"] + ) # > creation moduleimpl + return r + + security.declareProtected(ScoImplement, "do_moduleimpl_delete") + + def do_moduleimpl_delete(self, oid, formsemestre_id=None): + "delete moduleimpl (desinscrit tous les etudiants)" + cnx = self.GetDBConnexion() + # --- desinscription des etudiants + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + req = "DELETE FROM notes_moduleimpl_inscription WHERE moduleimpl_id=%(moduleimpl_id)s" + cursor.execute(req, {"moduleimpl_id": oid}) + # --- suppression des enseignants + cursor.execute( + "DELETE FROM notes_modules_enseignants WHERE moduleimpl_id=%(moduleimpl_id)s", + {"moduleimpl_id": oid}, + ) + # --- suppression des references dans les absences + cursor.execute( + "UPDATE absences SET moduleimpl_id=NULL WHERE moduleimpl_id=%(moduleimpl_id)s", + {"moduleimpl_id": oid}, + ) + # --- destruction du moduleimpl + self._moduleimplEditor.delete(cnx, oid) + self._inval_cache(formsemestre_id=formsemestre_id) # > moduleimpl_delete + + security.declareProtected(ScoView, "do_moduleimpl_list") + + def do_moduleimpl_list( + self, moduleimpl_id=None, formsemestre_id=None, module_id=None, REQUEST=None + ): + "list moduleimpls" + args = locals() + cnx = self.GetDBConnexion() + modimpls = self._moduleimplEditor.list(cnx, args) # *args, **kw) + # Ajoute la liste des enseignants + for mo in modimpls: + mo["ens"] = self.do_ens_list(args={"moduleimpl_id": mo["moduleimpl_id"]}) + return return_text_if_published(modimpls, REQUEST) + + security.declareProtected(ScoImplement, "do_moduleimpl_edit") + + def do_moduleimpl_edit(self, args, formsemestre_id=None, cnx=None): + "edit a moduleimpl" + if not cnx: + cnx = self.GetDBConnexion() + self._moduleimplEditor.edit(cnx, args) + + self._inval_cache(formsemestre_id=formsemestre_id) # > modif moduleimpl + + security.declareProtected(ScoView, "do_moduleimpl_withmodule_list") + + def do_moduleimpl_withmodule_list( + self, moduleimpl_id=None, formsemestre_id=None, module_id=None, REQUEST=None + ): + """Liste les moduleimpls et ajoute dans chacun le module correspondant + Tri la liste par semestre/UE/numero_matiere/numero_module + """ + args = locals() + del args["self"] + del args["REQUEST"] + modimpls = self.do_moduleimpl_list(**args) + for mo in modimpls: + mo["module"] = self.do_module_list(args={"module_id": mo["module_id"]})[0] + mo["ue"] = self.do_ue_list(args={"ue_id": mo["module"]["ue_id"]})[0] + mo["matiere"] = self.do_matiere_list( + args={"matiere_id": mo["module"]["matiere_id"]} + )[0] + + # tri par semestre/UE/numero_matiere/numero_module + + extr = lambda x: ( + x["ue"]["numero"], + x["ue"]["ue_id"], + x["matiere"]["numero"], + x["matiere"]["matiere_id"], + x["module"]["numero"], + x["module"]["code"], + ) + + modimpls.sort(lambda x, y: cmp(extr(x), extr(y))) + # log('after sort args=%s' % args) + # log( ',\n'.join( [ str(extr(m)) for m in modimpls ] )) + # log('after sort: Mlist=\n' + ',\n'.join( [ str(m) for m in modimpls ] ) + '\n') + return return_text_if_published(modimpls, REQUEST) + + security.declareProtected(ScoView, "do_ens_list") + + def do_ens_list(self, *args, **kw): + "liste les enseignants d'un moduleimpl (pas le responsable)" + cnx = self.GetDBConnexion() + ens = self._modules_enseignantsEditor.list(cnx, *args, **kw) + return ens + + security.declareProtected(ScoImplement, "do_ens_edit") + + def do_ens_edit(self, *args, **kw): + "edit ens" + cnx = self.GetDBConnexion() + self._modules_enseignantsEditor.edit(cnx, *args, **kw) + + security.declareProtected(ScoImplement, "do_ens_create") + + def do_ens_create(self, args): + "create ens" + cnx = self.GetDBConnexion() + r = self._modules_enseignantsEditor.create(cnx, args) + return r + + security.declareProtected(ScoImplement, "do_ens_delete") + + def do_ens_delete(self, oid): + "delete ens" + cnx = self.GetDBConnexion() + r = self._modules_enseignantsEditor.delete(cnx, oid) + return r + + # --- dialogue modif enseignants/moduleimpl + security.declareProtected(ScoView, "edit_enseignants_form") + + def edit_enseignants_form(self, REQUEST, moduleimpl_id): + "modif liste enseignants/moduleimpl" + M, sem = self.can_change_ens(REQUEST, moduleimpl_id) + # -- + header = self.html_sem_header( + REQUEST, + 'Enseignants du module %s' + % (moduleimpl_id, M["module"]["titre"]), + page_title="Enseignants du module %s" % M["module"]["titre"], + javascripts=["libjs/AutoSuggest.js"], + cssstyles=["css/autosuggest_inquisitor.css"], + bodyOnLoad="init_tf_form('')", + ) + footer = self.sco_footer(REQUEST) + + # Liste des enseignants avec forme pour affichage / saisie avec suggestion + userlist = self.Users.get_userlist() + login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)" + for u in userlist: + login2display[u["user_name"]] = u["nomplogin"] + allowed_user_names = login2display.values() + + H = [ + "
    • %s (responsable)
    • " + % login2display.get(M["responsable_id"], M["responsable_id"]) + ] + for ens in M["ens"]: + H.append( + '
    • %s (supprimer)
    • ' + % ( + login2display.get(ens["ens_id"], ens["ens_id"]), + moduleimpl_id, + ens["ens_id"], + ) + ) + H.append("
    ") + F = """

    Les enseignants d'un module ont le droit de + saisir et modifier toutes les notes des évaluations de ce module. +

    +

    Pour changer le responsable du module, passez par la + page "Modification du semestre", accessible uniquement au responsable de la formation (chef de département) +

    + """ % ( + sem["formation_id"], + M["formsemestre_id"], + ) + + modform = [ + ("moduleimpl_id", {"input_type": "hidden"}), + ( + "ens_id", + { + "input_type": "text_suggest", + "size": 50, + "title": "Ajouter un enseignant", + "allowed_values": allowed_user_names, + "allow_null": False, + "text_suggest_options": { + "script": "Users/get_userlist_xml?", + "varname": "start", + "json": False, + "noresults": "Valeur invalide !", + "timeout": 60000, + }, + }, + ), + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + modform, + submitlabel="Ajouter enseignant", + cancelbutton="Annuler", + ) + if tf[0] == 0: + return header + "\n".join(H) + tf[1] + F + footer + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect( + "moduleimpl_status?moduleimpl_id=" + moduleimpl_id + ) + else: + ens_id = self.Users.get_user_name_from_nomplogin(tf[2]["ens_id"]) + if not ens_id: + H.append( + '

    Pour ajouter un enseignant, choisissez un nom dans le menu

    ' + ) + else: + # et qu'il n'est pas deja: + if ( + ens_id in [x["ens_id"] for x in M["ens"]] + or ens_id == M["responsable_id"] + ): + H.append( + '

    Enseignant %s déjà dans la liste !

    ' + % ens_id + ) + else: + self.do_ens_create( + {"moduleimpl_id": moduleimpl_id, "ens_id": ens_id} + ) + return REQUEST.RESPONSE.redirect( + "edit_enseignants_form?moduleimpl_id=%s" % moduleimpl_id + ) + return header + "\n".join(H) + tf[1] + F + footer + + security.declareProtected(ScoView, "edit_moduleimpl_resp") + + def edit_moduleimpl_resp(self, REQUEST, moduleimpl_id): + """Changement d'un enseignant responsable de module + Accessible par Admin et dir des etud si flag resp_can_change_ens + """ + M, sem = self.can_change_module_resp(REQUEST, moduleimpl_id) + H = [ + self.html_sem_header( + REQUEST, + 'Modification du responsable du module %s' + % (moduleimpl_id, M["module"]["titre"]), + sem, + javascripts=["libjs/AutoSuggest.js"], + cssstyles=["css/autosuggest_inquisitor.css"], + bodyOnLoad="init_tf_form('')", + ) + ] + help = """

    Taper le début du nom de l'enseignant.

    """ + # Liste des enseignants avec forme pour affichage / saisie avec suggestion + userlist = self.Users.get_userlist() + login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)" + for u in userlist: + login2display[u["user_name"]] = u["nomplogin"] + allowed_user_names = login2display.values() + + initvalues = M + initvalues["responsable_id"] = login2display.get( + M["responsable_id"], M["responsable_id"] + ) + form = [ + ("moduleimpl_id", {"input_type": "hidden"}), + ( + "responsable_id", + { + "input_type": "text_suggest", + "size": 50, + "title": "Responsable du module", + "allowed_values": allowed_user_names, + "allow_null": False, + "text_suggest_options": { + "script": "Users/get_userlist_xml?", + "varname": "start", + "json": False, + "noresults": "Valeur invalide !", + "timeout": 60000, + }, + }, + ), + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + form, + submitlabel="Changer responsable", + cancelbutton="Annuler", + initvalues=initvalues, + ) + if tf[0] == 0: + return "\n".join(H) + tf[1] + help + self.sco_footer(REQUEST) + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect( + "moduleimpl_status?moduleimpl_id=" + moduleimpl_id + ) + else: + responsable_id = self.Users.get_user_name_from_nomplogin( + tf[2]["responsable_id"] + ) + if ( + not responsable_id + ): # presque impossible: tf verifie les valeurs (mais qui peuvent changer entre temps) + return REQUEST.RESPONSE.redirect( + "moduleimpl_status?moduleimpl_id=" + moduleimpl_id + ) + self.do_moduleimpl_edit( + {"moduleimpl_id": moduleimpl_id, "responsable_id": responsable_id}, + formsemestre_id=sem["formsemestre_id"], + ) + return REQUEST.RESPONSE.redirect( + "moduleimpl_status?moduleimpl_id=" + + moduleimpl_id + + "&head_message=responsable%20modifié" + ) + + _expr_help = """

    Expérimental: formule de calcul de la moyenne %(target)s

    +

    Dans la formule, les variables suivantes sont définies:

    +
      +
    • moy la moyenne, calculée selon la règle standard (moyenne pondérée)
    • +
    • moy_is_valid vrai si la moyenne est valide (numérique)
    • +
    • moy_val la valeur de la moyenne (nombre, valant 0 si invalide)
    • +
    • notes vecteur des notes (/20) aux %(objs)s
    • +
    • coefs vecteur des coefficients des %(objs)s, les coefs des %(objs)s sans notes (ATT, EXC) étant mis à zéro
    • +
    • cmask vecteur de 0/1, 0 si le coef correspondant a été annulé
    • +
    • Nombre d'absences: nb_abs, nb_abs_just, nb_abs_nojust (en demi-journées)
    • +
    +

    Les éléments des vecteurs sont ordonnés dans l'ordre des %(objs)s%(ordre)s.

    +

    Les fonctions suivantes sont utilisables: abs, cmp, dot, len, map, max, min, pow, reduce, round, sum, ifelse

    +

    La notation V(1,2,3) représente un vecteur (1,2,3)

    +

    Vous pouvez désactiver la formule (et revenir au mode de calcul "classique") + en supprimant le texte ou en faisant précéder la première ligne par #

    + """ + + security.declareProtected(ScoView, "edit_moduleimpl_expr") + + def edit_moduleimpl_expr(self, REQUEST, moduleimpl_id): + """Edition formule calcul moyenne module + Accessible par Admin, dir des etud et responsable module + """ + M, sem = self.can_change_ens(REQUEST, moduleimpl_id) + H = [ + self.html_sem_header( + REQUEST, + 'Modification règle de calcul du module %s' + % (moduleimpl_id, M["module"]["titre"]), + sem, + ), + self._expr_help + % { + "target": "du module", + "objs": "évaluations", + "ordre": " (le premier élément est la plus ancienne évaluation)", + }, + ] + initvalues = M + form = [ + ("moduleimpl_id", {"input_type": "hidden"}), + ( + "computation_expr", + { + "title": "Formule de calcul", + "input_type": "textarea", + "rows": 4, + "cols": 60, + "explanation": "formule de calcul (expérimental)", + }, + ), + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + form, + submitlabel="Modifier formule de calcul", + cancelbutton="Annuler", + initvalues=initvalues, + ) + if tf[0] == 0: + return "\n".join(H) + tf[1] + self.sco_footer(REQUEST) + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect( + "moduleimpl_status?moduleimpl_id=" + moduleimpl_id + ) + else: + self.do_moduleimpl_edit( + { + "moduleimpl_id": moduleimpl_id, + "computation_expr": tf[2]["computation_expr"], + }, + formsemestre_id=sem["formsemestre_id"], + ) + self._inval_cache( + formsemestre_id=sem["formsemestre_id"] + ) # > modif regle calcul + return REQUEST.RESPONSE.redirect( + "moduleimpl_status?moduleimpl_id=" + + moduleimpl_id + + "&head_message=règle%20de%20calcul%20modifiée" + ) + + security.declareProtected(ScoView, "view_module_abs") + + def view_module_abs(self, REQUEST, moduleimpl_id, format="html"): + """Visulalisation des absences a un module + """ + M = self.do_moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0] + sem = sco_formsemestre.get_formsemestre(self, M["formsemestre_id"]) + debut_sem = DateDMYtoISO(sem["date_debut"]) + fin_sem = DateDMYtoISO(sem["date_fin"]) + list_insc = self.do_moduleimpl_listeetuds(moduleimpl_id) + + T = [] + for etudid in list_insc: + nb_abs = self.Absences.CountAbs( + etudid=etudid, debut=debut_sem, fin=fin_sem, moduleimpl_id=moduleimpl_id + ) + if nb_abs: + nb_abs_just = self.Absences.CountAbsJust( + etudid=etudid, + debut=debut_sem, + fin=fin_sem, + moduleimpl_id=moduleimpl_id, + ) + etud = self.getEtudInfo(etudid=etudid, filled=True)[0] + T.append( + { + "nomprenom": etud["nomprenom"], + "just": nb_abs_just, + "nojust": nb_abs - nb_abs_just, + "total": nb_abs, + "_nomprenom_target": "ficheEtud?etudid=%s" % etudid, + } + ) + + H = [ + self.html_sem_header( + REQUEST, + 'Absences du module %s' + % (moduleimpl_id, M["module"]["titre"]), + page_title="Absences du module %s" % (M["module"]["titre"]), + sem=sem, + ) + ] + if not T and format == "html": + return ( + "\n".join(H) + + "

    Aucune absence signalée

    " + + self.sco_footer(REQUEST) + ) + + tab = GenTable( + titles={ + "nomprenom": "Nom", + "just": "Just.", + "nojust": "Non Just.", + "total": "Total", + }, + columns_ids=("nomprenom", "just", "nojust", "total"), + rows=T, + html_class="table_leftalign", + base_url="%s?moduleimpl_id=%s" % (REQUEST.URL0, moduleimpl_id), + filename="absmodule_" + make_filename(M["module"]["titre"]), + caption="Absences dans le module %s" % M["module"]["titre"], + preferences=self.get_preferences(), + ) + + if format != "html": + return tab.make_page(self, format=format, REQUEST=REQUEST) + + return "\n".join(H) + tab.html() + self.sco_footer(REQUEST) + + security.declareProtected(ScoView, "edit_ue_expr") + + def edit_ue_expr(self, REQUEST, formsemestre_id, ue_id): + """Edition formule calcul moyenne UE""" + # Check access + sem = sco_formsemestre_edit.can_edit_sem(self, REQUEST, formsemestre_id) + if not sem: + raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération") + cnx = self.GetDBConnexion() + # + ue = self.do_ue_list({"ue_id": ue_id})[0] + H = [ + self.html_sem_header( + REQUEST, + "Modification règle de calcul de l'UE %s (%s)" + % (ue["acronyme"], ue["titre"]), + sem, + ), + self._expr_help % {"target": "de l'UE", "objs": "modules", "ordre": ""}, + ] + el = sco_compute_moy.formsemestre_ue_computation_expr_list( + cnx, {"formsemestre_id": formsemestre_id, "ue_id": ue_id} + ) + if el: + initvalues = el[0] + else: + initvalues = {} + form = [ + ("ue_id", {"input_type": "hidden"}), + ("formsemestre_id", {"input_type": "hidden"}), + ( + "computation_expr", + { + "title": "Formule de calcul", + "input_type": "textarea", + "rows": 4, + "cols": 60, + "explanation": "formule de calcul (expérimental)", + }, + ), + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + form, + submitlabel="Modifier formule de calcul", + cancelbutton="Annuler", + initvalues=initvalues, + ) + if tf[0] == 0: + return "\n".join(H) + tf[1] + self.sco_footer(REQUEST) + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect( + "formsemestre_status?formsemestre_id=" + formsemestre_id + ) + else: + if el: + el[0]["computation_expr"] = tf[2]["computation_expr"] + sco_compute_moy.formsemestre_ue_computation_expr_edit(cnx, el[0]) + else: + sco_compute_moy.formsemestre_ue_computation_expr_create(cnx, tf[2]) + + self._inval_cache(formsemestre_id=formsemestre_id) # > modif regle calcul + return REQUEST.RESPONSE.redirect( + "formsemestre_status?formsemestre_id=" + + formsemestre_id + + "&head_message=règle%20de%20calcul%20modifiée" + ) + + security.declareProtected(ScoView, "formsemestre_enseignants_list") + + def formsemestre_enseignants_list(self, REQUEST, formsemestre_id, format="html"): + """Liste les enseignants intervenants dans le semestre (resp. modules et chargés de TD) + et indique les absences saisies par chacun. + """ + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + # resp. de modules: + mods = self.do_moduleimpl_withmodule_list(formsemestre_id=formsemestre_id) + sem_ens = {} + for mod in mods: + if not mod["responsable_id"] in sem_ens: + sem_ens[mod["responsable_id"]] = {"mods": [mod]} + else: + sem_ens[mod["responsable_id"]]["mods"].append(mod) + # charges de TD: + for mod in mods: + for ensd in mod["ens"]: + if not ensd["ens_id"] in sem_ens: + sem_ens[ensd["ens_id"]] = {"mods": [mod]} + else: + sem_ens[ensd["ens_id"]]["mods"].append(mod) + # compte les absences ajoutées par chacun dans tout le semestre + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + for ens in sem_ens: + cursor.execute( + "select * from scolog L, notes_formsemestre_inscription I where method='AddAbsence' and authenticated_user=%(authenticated_user)s and L.etudid = I.etudid and I.formsemestre_id=%(formsemestre_id)s and date > %(date_debut)s and date < %(date_fin)s", + { + "authenticated_user": ens, + "formsemestre_id": formsemestre_id, + "date_debut": DateDMYtoISO(sem["date_debut"]), + "date_fin": DateDMYtoISO(sem["date_fin"]), + }, + ) + + events = cursor.dictfetchall() + sem_ens[ens]["nbabsadded"] = len(events) + + # description textuelle des modules + for ens in sem_ens: + sem_ens[ens]["descr_mods"] = ", ".join( + [x["module"]["code"] for x in sem_ens[ens]["mods"]] + ) + + # ajoute infos sur enseignant: + for ens in sem_ens: + sem_ens[ens].update(self.Users.user_info(ens)) + if sem_ens[ens]["email"]: + sem_ens[ens]["_email_target"] = "mailto:%s" % sem_ens[ens]["email"] + + sem_ens_list = sem_ens.values() + sem_ens_list.sort(lambda x, y: cmp(x["nomprenom"], y["nomprenom"])) + + # --- Generate page with table + title = "Enseignants de " + sem["titremois"] + T = GenTable( + columns_ids=["nom_fmt", "prenom_fmt", "descr_mods", "nbabsadded", "email"], + titles={ + "nom_fmt": "Nom", + "prenom_fmt": "Prénom", + "email": "Mail", + "descr_mods": "Modules", + "nbabsadded": "Saisies Abs.", + }, + rows=sem_ens_list, + html_sortable=True, + html_class="table_leftalign", + filename=make_filename("Enseignants-" + sem["titreannee"]), + html_title=self.html_sem_header( + REQUEST, "Enseignants du semestre", sem, with_page_header=False + ), + base_url="%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id), + caption="Tous les enseignants (responsables ou associés aux modules de ce semestre) apparaissent. Le nombre de saisies d'absences est le nombre d'opérations d'ajout effectuées sur ce semestre, sans tenir compte des annulations ou double saisies.", + preferences=self.get_preferences(formsemestre_id), + ) + return T.make_page( + self, page_title=title, title=title, REQUEST=REQUEST, format=format + ) + + security.declareProtected(ScoView, "edit_enseignants_form_delete") + + def edit_enseignants_form_delete(self, REQUEST, moduleimpl_id, ens_id): + "remove ens" + M, sem = self.can_change_ens(REQUEST, moduleimpl_id) + # search ens_id + ok = False + for ens in M["ens"]: + if ens["ens_id"] == ens_id: + ok = True + break + if not ok: + raise ScoValueError("invalid ens_id (%s)" % ens_id) + self.do_ens_delete(ens["modules_enseignants_id"]) + return REQUEST.RESPONSE.redirect( + "edit_enseignants_form?moduleimpl_id=%s" % moduleimpl_id + ) + + security.declareProtected(ScoView, "can_change_ens") + + def can_change_ens(self, REQUEST, moduleimpl_id, raise_exc=True): + "check if current user can modify ens list (raise exception if not)" + M = self.do_moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0] + # -- check lock + sem = sco_formsemestre.get_formsemestre(self, M["formsemestre_id"]) + if sem["etat"] != "1": + if raise_exc: + raise ScoValueError("Modification impossible: semestre verrouille") + else: + return False + # -- check access + authuser = REQUEST.AUTHENTICATED_USER + uid = str(authuser) + # admin, resp. module ou resp. semestre + if ( + uid != M["responsable_id"] + and not authuser.has_permission(ScoImplement, self) + and (uid not in sem["responsables"]) + ): + if raise_exc: + raise AccessDenied("Modification impossible pour %s" % uid) + else: + return False + return M, sem + + security.declareProtected(ScoView, "can_change_module_resp") + + def can_change_module_resp(self, REQUEST, moduleimpl_id): + """Check if current user can modify module resp. (raise exception if not). + = Admin, et dir des etud. (si option l'y autorise) + """ + M = self.do_moduleimpl_withmodule_list(moduleimpl_id=moduleimpl_id)[0] + # -- check lock + sem = sco_formsemestre.get_formsemestre(self, M["formsemestre_id"]) + if sem["etat"] != "1": + raise ScoValueError("Modification impossible: semestre verrouille") + # -- check access + authuser = REQUEST.AUTHENTICATED_USER + uid = str(authuser) + # admin ou resp. semestre avec flag resp_can_change_resp + if not authuser.has_permission(ScoImplement, self) and ( + (uid not in sem["responsables"]) or (not sem["resp_can_change_ens"]) + ): + raise AccessDenied("Modification impossible pour %s" % uid) + return M, sem + + # --- Gestion des inscriptions aux modules + _formsemestre_inscriptionEditor = EditableTable( + "notes_formsemestre_inscription", + "formsemestre_inscription_id", + ("formsemestre_inscription_id", "etudid", "formsemestre_id", "etat"), + sortkey="formsemestre_id", + ) + + security.declareProtected(ScoEtudInscrit, "do_formsemestre_inscription_create") + + def do_formsemestre_inscription_create(self, args, REQUEST, method=None): + "create a formsemestre_inscription (and sco event)" + cnx = self.GetDBConnexion() + log("do_formsemestre_inscription_create: args=%s" % str(args)) + sems = sco_formsemestre.do_formsemestre_list( + self, {"formsemestre_id": args["formsemestre_id"]} + ) + if len(sems) != 1: + raise ScoValueError( + "code de semestre invalide: %s" % args["formsemestre_id"] + ) + sem = sems[0] + # check lock + if sem["etat"] != "1": + raise ScoValueError("inscription: semestre verrouille") + # + r = self._formsemestre_inscriptionEditor.create(cnx, args) + # Evenement + scolars.scolar_events_create( + cnx, + args={ + "etudid": args["etudid"], + "event_date": time.strftime("%d/%m/%Y"), + "formsemestre_id": args["formsemestre_id"], + "event_type": "INSCRIPTION", + }, + ) + # Log etudiant + logdb( + REQUEST, + cnx, + method=method, + etudid=args["etudid"], + msg="inscription en semestre %s" % args["formsemestre_id"], + commit=False, + ) + # + self._inval_cache( + formsemestre_id=args["formsemestre_id"] + ) # > inscription au semestre + return r + + security.declareProtected(ScoImplement, "do_formsemestre_inscription_delete") + + def do_formsemestre_inscription_delete(self, oid, formsemestre_id=None): + "delete formsemestre_inscription" + cnx = self.GetDBConnexion() + self._formsemestre_inscriptionEditor.delete(cnx, oid) + + self._inval_cache( + formsemestre_id=formsemestre_id + ) # > desinscription du semestre + + security.declareProtected(ScoView, "do_formsemestre_inscription_list") + + def do_formsemestre_inscription_list(self, *args, **kw): + "list formsemestre_inscriptions" + cnx = self.GetDBConnexion() + return self._formsemestre_inscriptionEditor.list(cnx, *args, **kw) + + security.declareProtected(ScoView, "do_formsemestre_inscription_listinscrits") + + def do_formsemestre_inscription_listinscrits(self, formsemestre_id): + """Liste les inscrits (état I) à ce semestre et cache le résultat""" + cache = self.get_formsemestre_inscription_cache() + r = cache.get(formsemestre_id) + if r != None: + return r + # retreive list + r = self.do_formsemestre_inscription_list( + args={"formsemestre_id": formsemestre_id, "etat": "I"} + ) + cache.set(formsemestre_id, r) + return r + + security.declareProtected(ScoImplement, "do_formsemestre_inscription_edit") + + def do_formsemestre_inscription_edit(self, args=None, formsemestre_id=None): + "edit a formsemestre_inscription" + cnx = self.GetDBConnexion() + self._formsemestre_inscriptionEditor.edit(cnx, args) + self._inval_cache( + formsemestre_id=formsemestre_id + ) # > modif inscription semestre (demission ?) + + # Cache inscriptions semestres + def get_formsemestre_inscription_cache(self): + u = self.GetDBConnexionString() + if CACHE_formsemestre_inscription.has_key(u): + return CACHE_formsemestre_inscription[u] + else: + log("get_formsemestre_inscription_cache: new simpleCache") + CACHE_formsemestre_inscription[u] = sco_cache.simpleCache() + return CACHE_formsemestre_inscription[u] + + security.declareProtected(ScoImplement, "formsemestre_desinscription") + + def formsemestre_desinscription( + self, etudid, formsemestre_id, REQUEST=None, dialog_confirmed=False + ): + """desinscrit l'etudiant de ce semestre (et donc de tous les modules). + A n'utiliser qu'en cas d'erreur de saisie. + S'il s'agit d'un semestre extérieur et qu'il n'y a plus d'inscrit, + le semestre sera supprimé. + """ + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + # -- check lock + if sem["etat"] != "1": + raise ScoValueError("desinscription impossible: semestre verrouille") + + # -- Si décisions de jury, désinscription interdite + nt = self._getNotesCache().get_NotesTable(self, formsemestre_id) + if nt.etud_has_decision(etudid): + raise ScoValueError( + """Désinscription impossible: l'étudiant a une décision de jury + (la supprimer avant si nécessaire: + + supprimer décision jury + ) + """ + % (etudid, formsemestre_id) + ) + if not dialog_confirmed: + etud = self.getEtudInfo(etudid=etudid, filled=1)[0] + if sem["modalite"] != "EXT": + msg_ext = """ +

    %s sera désinscrit de tous les modules du semestre %s (%s - %s).

    +

    Cette opération ne doit être utilisée que pour corriger une erreur ! + Un étudiant réellement inscrit doit le rester, le faire éventuellement démissionner. +

    + """ % ( + etud["nomprenom"], + sem["titre_num"], + sem["date_debut"], + sem["date_fin"], + ) + else: # semestre extérieur + msg_ext = """ +

    %s sera désinscrit du semestre extérieur %s (%s - %s).

    + """ % ( + etud["nomprenom"], + sem["titre_num"], + sem["date_debut"], + sem["date_fin"], + ) + inscrits = self.do_formsemestre_inscription_list( + args={"formsemestre_id": formsemestre_id} + ) + nbinscrits = len(inscrits) + if nbinscrits <= 1: + msg_ext = """

    Attention: le semestre extérieur sera supprimé + car il n'a pas d'autre étudiant inscrit. +

    + """ + return self.confirmDialog( + """

    Confirmer la demande de desinscription ?

    """ + msg_ext, + dest_url="", + REQUEST=REQUEST, + cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id, + parameters={"etudid": etudid, "formsemestre_id": formsemestre_id}, + ) + + self.do_formsemestre_desinscription(etudid, formsemestre_id, REQUEST=REQUEST) + + return ( + self.sco_header(REQUEST) + + '

    Etudiant désinscrit !

    retour à la fiche' + % (self.ScoURL(), etudid) + + self.sco_footer(REQUEST) + ) + + def do_formsemestre_desinscription(self, etudid, formsemestre_id, REQUEST=None): + """Désinscription d'un étudiant. + Si semestre extérieur et dernier inscrit, suppression de ce semestre. + """ + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + # -- check lock + if sem["etat"] != "1": + raise ScoValueError("desinscription impossible: semestre verrouille") + + # -- Si decisions de jury, desinscription interdite + nt = self._getNotesCache().get_NotesTable(self, formsemestre_id) + if nt.etud_has_decision(etudid): + raise ScoValueError( + "desinscription impossible: l'étudiant a une décision de jury (la supprimer avant si nécessaire)" + ) + + insem = self.do_formsemestre_inscription_list( + args={"formsemestre_id": formsemestre_id, "etudid": etudid} + ) + if not insem: + raise ScoValueError("%s n'est pas inscrit au semestre !" % etudid) + insem = insem[0] + # -- desinscription de tous les modules + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "select moduleimpl_inscription_id from notes_moduleimpl_inscription Im, notes_moduleimpl M where Im.etudid=%(etudid)s and Im.moduleimpl_id = M.moduleimpl_id and M.formsemestre_id = %(formsemestre_id)s", + {"etudid": etudid, "formsemestre_id": formsemestre_id}, + ) + res = cursor.fetchall() + moduleimpl_inscription_ids = [x[0] for x in res] + for moduleimpl_inscription_id in moduleimpl_inscription_ids: + self.do_moduleimpl_inscription_delete( + moduleimpl_inscription_id, formsemestre_id=formsemestre_id + ) + # -- desincription du semestre + self.do_formsemestre_inscription_delete( + insem["formsemestre_inscription_id"], formsemestre_id=formsemestre_id + ) + # --- Semestre extérieur + if sem["modalite"] == "EXT": + inscrits = self.do_formsemestre_inscription_list( + args={"formsemestre_id": formsemestre_id} + ) + nbinscrits = len(inscrits) + if nbinscrits == 0: + log( + "do_formsemestre_desinscription: suppression du semestre extérieur %s" + % formsemestre_id + ) + sco_formsemestre_edit.do_formsemestre_delete( + self, formsemestre_id, REQUEST=REQUEST + ) + + if REQUEST: + logdb( + REQUEST, + cnx, + method="formsemestre_desinscription", + etudid=etudid, + msg="desinscription semestre %s" % formsemestre_id, + commit=False, + ) + + # --- Inscriptions aux modules + _moduleimpl_inscriptionEditor = EditableTable( + "notes_moduleimpl_inscription", + "moduleimpl_inscription_id", + ("moduleimpl_inscription_id", "etudid", "moduleimpl_id"), + ) + + security.declareProtected(ScoEtudInscrit, "do_moduleimpl_inscription_create") + + def do_moduleimpl_inscription_create( + self, args, REQUEST=None, formsemestre_id=None + ): + "create a moduleimpl_inscription" + cnx = self.GetDBConnexion() + log("do_moduleimpl_inscription_create: " + str(args)) + r = self._moduleimpl_inscriptionEditor.create(cnx, args) + self._inval_cache(formsemestre_id=formsemestre_id) # > moduleimpl_inscription + if REQUEST: + logdb( + REQUEST, + cnx, + method="moduleimpl_inscription", + etudid=args["etudid"], + msg="inscription module %s" % args["moduleimpl_id"], + commit=False, + ) + return r + + security.declareProtected(ScoImplement, "do_moduleimpl_inscription_delete") + + def do_moduleimpl_inscription_delete(self, oid, formsemestre_id=None): + "delete moduleimpl_inscription" + cnx = self.GetDBConnexion() + self._moduleimpl_inscriptionEditor.delete(cnx, oid) + self._inval_cache(formsemestre_id=formsemestre_id) # > moduleimpl_inscription + + security.declareProtected(ScoView, "do_moduleimpl_inscription_list") + + def do_moduleimpl_inscription_list( + self, moduleimpl_id=None, etudid=None, REQUEST=None + ): + "list moduleimpl_inscriptions" + args = locals() + cnx = self.GetDBConnexion() + return return_text_if_published( + self._moduleimpl_inscriptionEditor.list(cnx, args), REQUEST + ) + + security.declareProtected(ScoView, "do_moduleimpl_listeetuds") + + def do_moduleimpl_listeetuds(self, moduleimpl_id): + "retourne liste des etudids inscrits a ce module" + req = "select distinct Im.etudid from notes_moduleimpl_inscription Im, notes_formsemestre_inscription Isem, notes_moduleimpl M where Isem.etudid=Im.etudid and Im.moduleimpl_id=M.moduleimpl_id and M.moduleimpl_id = %(moduleimpl_id)s" + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute(req, {"moduleimpl_id": moduleimpl_id}) + res = cursor.fetchall() + return [x[0] for x in res] + + security.declareProtected(ScoEtudInscrit, "do_moduleimpl_inscrit_tout_semestre") + + def do_moduleimpl_inscrit_tout_semestre(self, moduleimpl_id, formsemestre_id): + "inscrit tous les etudiants inscrit au semestre a ce module" + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + req = """INSERT INTO notes_moduleimpl_inscription + (moduleimpl_id, etudid) + SELECT %(moduleimpl_id)s, I.etudid + FROM notes_formsemestre_inscription I + WHERE I.formsemestre_id=%(formsemestre_id)s""" + args = {"moduleimpl_id": moduleimpl_id, "formsemestre_id": formsemestre_id} + cursor.execute(req, args) + + security.declareProtected(ScoEtudInscrit, "do_moduleimpl_inscrit_etuds") + + def do_moduleimpl_inscrit_etuds( + self, moduleimpl_id, formsemestre_id, etudids, reset=False, REQUEST=None + ): + """Inscrit les etudiants (liste d'etudids) a ce module. + Si reset, desinscrit tous les autres. + """ + # Verifie qu'ils sont tous bien inscrits au semestre + for etudid in etudids: + insem = self.do_formsemestre_inscription_list( + args={"formsemestre_id": formsemestre_id, "etudid": etudid} + ) + if not insem: + raise ScoValueError("%s n'est pas inscrit au semestre !" % etudid) + + # Desinscriptions + if reset: + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "delete from notes_moduleimpl_inscription where moduleimpl_id = %(moduleimpl_id)s", + {"moduleimpl_id": moduleimpl_id}, + ) + # Inscriptions au module: + inmod_set = Set( + [ + x["etudid"] + for x in self.do_moduleimpl_inscription_list( + moduleimpl_id=moduleimpl_id + ) + ] + ) + for etudid in etudids: + # deja inscrit ? + if not etudid in inmod_set: + self.do_moduleimpl_inscription_create( + {"moduleimpl_id": moduleimpl_id, "etudid": etudid}, + REQUEST=REQUEST, + formsemestre_id=formsemestre_id, + ) + + self._inval_cache(formsemestre_id=formsemestre_id) # > moduleimpl_inscrit_etuds + + security.declareProtected(ScoEtudInscrit, "etud_desinscrit_ue") + + def etud_desinscrit_ue(self, etudid, formsemestre_id, ue_id, REQUEST=None): + """Desinscrit l'etudiant de tous les modules de cette UE dans ce semestre. + """ + sco_moduleimpl_inscriptions.do_etud_desinscrit_ue( + self, etudid, formsemestre_id, ue_id, REQUEST=REQUEST + ) + return REQUEST.RESPONSE.redirect( + self.ScoURL() + + "/Notes/moduleimpl_inscriptions_stats?formsemestre_id=" + + formsemestre_id + ) + + security.declareProtected(ScoEtudInscrit, "etud_inscrit_ue") + + def etud_inscrit_ue(self, etudid, formsemestre_id, ue_id, REQUEST=None): + """Inscrit l'etudiant de tous les modules de cette UE dans ce semestre. + """ + sco_moduleimpl_inscriptions.do_etud_inscrit_ue( + self, etudid, formsemestre_id, ue_id, REQUEST=REQUEST + ) + return REQUEST.RESPONSE.redirect( + self.ScoURL() + + "/Notes/moduleimpl_inscriptions_stats?formsemestre_id=" + + formsemestre_id + ) + + # --- Inscriptions + security.declareProtected( + ScoEtudInscrit, "formsemestre_inscription_with_modules_form" + ) + formsemestre_inscription_with_modules_form = ( + sco_formsemestre_inscriptions.formsemestre_inscription_with_modules_form + ) + + security.declareProtected( + ScoEtudInscrit, "formsemestre_inscription_with_modules_etud" + ) + formsemestre_inscription_with_modules_etud = ( + sco_formsemestre_inscriptions.formsemestre_inscription_with_modules_etud + ) + + security.declareProtected(ScoEtudInscrit, "formsemestre_inscription_with_modules") + formsemestre_inscription_with_modules = ( + sco_formsemestre_inscriptions.formsemestre_inscription_with_modules + ) + + security.declareProtected(ScoEtudInscrit, "formsemestre_inscription_option") + formsemestre_inscription_option = ( + sco_formsemestre_inscriptions.formsemestre_inscription_option + ) + + security.declareProtected(ScoEtudInscrit, "do_moduleimpl_incription_options") + do_moduleimpl_incription_options = ( + sco_formsemestre_inscriptions.do_moduleimpl_incription_options + ) + + security.declareProtected(ScoView, "formsemestre_inscrits_ailleurs") + formsemestre_inscrits_ailleurs = ( + sco_formsemestre_inscriptions.formsemestre_inscrits_ailleurs + ) + + security.declareProtected(ScoEtudInscrit, "moduleimpl_inscriptions_edit") + moduleimpl_inscriptions_edit = ( + sco_moduleimpl_inscriptions.moduleimpl_inscriptions_edit + ) + + security.declareProtected(ScoView, "moduleimpl_inscriptions_stats") + moduleimpl_inscriptions_stats = ( + sco_moduleimpl_inscriptions.moduleimpl_inscriptions_stats + ) + + # --- Evaluations + _evaluationEditor = EditableTable( + "notes_evaluation", + "evaluation_id", + ( + "evaluation_id", + "moduleimpl_id", + "jour", + "heure_debut", + "heure_fin", + "description", + "note_max", + "coefficient", + "visibulletin", + "publish_incomplete", + "evaluation_type", + "numero", + ), + sortkey="numero desc, jour desc, heure_debut desc", # plus recente d'abord + output_formators={ + "jour": DateISOtoDMY, + "visibulletin": str, + "publish_incomplete": str, + "numero": int_null_is_zero, + }, + input_formators={ + "jour": DateDMYtoISO, + "heure_debut": TimetoISO8601, # converti par do_evaluation_list + "heure_fin": TimetoISO8601, # converti par do_evaluation_list + "visibulletin": int, + "publish_incomplete": int, + }, + ) + + def _evaluation_check_write_access(self, REQUEST, moduleimpl_id=None): + """Vérifie que l'on a le droit de modifier, créer ou détruire une + évaluation dans ce module. + Sinon, lance une exception. + (nb: n'implique pas le droit de saisir ou modifier des notes) + """ + # acces pour resp. moduleimpl et resp. form semestre (dir etud) + if moduleimpl_id is None: + raise ValueError("no moduleimpl specified") # bug + authuser = REQUEST.AUTHENTICATED_USER + uid = str(authuser) + M = self.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] + sem = sco_formsemestre.get_formsemestre(self, M["formsemestre_id"]) + + if ( + (not authuser.has_permission(ScoEditAllEvals, self)) + and uid != M["responsable_id"] + and uid not in sem["responsables"] + ): + if sem["ens_can_edit_eval"]: + for ens in M["ens"]: + if ens["ens_id"] == uid: + return # ok + raise AccessDenied("Modification évaluation impossible pour %s" % (uid,)) + + security.declareProtected(ScoEnsView, "do_evaluation_create") + + def do_evaluation_create( + self, + moduleimpl_id=None, + jour=None, + heure_debut=None, + heure_fin=None, + description=None, + note_max=None, + coefficient=None, + visibulletin=None, + publish_incomplete=None, + evaluation_type=None, + numero=None, + REQUEST=None, + **kw + ): + """Create an evaluation + """ + args = locals() + log("do_evaluation_create: args=" + str(args)) + self._evaluation_check_write_access(REQUEST, moduleimpl_id=moduleimpl_id) + self._check_evaluation_args(args) + # Check numeros + sco_evaluations.module_evaluation_renumber( + self, moduleimpl_id, REQUEST=REQUEST, only_if_unumbered=True + ) + if not "numero" in args or args["numero"] is None: + n = None + # determine le numero avec la date + # Liste des eval existantes triees par date, la plus ancienne en tete + ModEvals = self.do_evaluation_list( + args={"moduleimpl_id": moduleimpl_id}, + sortkey="jour asc, heure_debut asc", + ) + if args["jour"]: + next_eval = None + t = (DateDMYtoISO(args["jour"]), TimetoISO8601(args["heure_debut"])) + for e in ModEvals: + if (DateDMYtoISO(e["jour"]), TimetoISO8601(e["heure_debut"])) > t: + next_eval = e + break + if next_eval: + n = sco_evaluations.module_evaluation_insert_before( + self, ModEvals, next_eval, REQUEST + ) + else: + n = None # a placer en fin + if n is None: # pas de date ou en fin: + if ModEvals: + log(pprint.pformat(ModEvals[-1])) + n = ModEvals[-1]["numero"] + 1 + else: + n = 0 # the only one + # log("creating with numero n=%d" % n) + args["numero"] = n + + # + cnx = self.GetDBConnexion() + r = self._evaluationEditor.create(cnx, args) + + # news + M = self.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] + mod = self.do_module_list(args={"module_id": M["module_id"]})[0] + mod["moduleimpl_id"] = M["moduleimpl_id"] + mod["url"] = "Notes/moduleimpl_status?moduleimpl_id=%(moduleimpl_id)s" % mod + sco_news.add( + self, + REQUEST, + typ=NEWS_NOTE, + object=moduleimpl_id, + text='Création d\'une évaluation dans %(titre)s' + % mod, + url=mod["url"], + ) + + return r + + def _check_evaluation_args(self, args): + "Check coefficient, dates and duration, raises exception if invalid" + moduleimpl_id = args["moduleimpl_id"] + # check bareme + note_max = args.get("note_max", None) + if note_max is None: + raise ScoValueError("missing note_max") + try: + note_max = float(note_max) + except ValueError: + raise ScoValueError("Invalid note_max value") + if note_max < 0: + raise ScoValueError("Invalid note_max value (must be positive or null)") + # check coefficient + coef = args.get("coefficient", None) + if coef is None: + raise ScoValueError("missing coefficient") + try: + coef = float(coef) + except ValueError: + raise ScoValueError("Invalid coefficient value") + if coef < 0: + raise ScoValueError("Invalid coefficient value (must be positive or null)") + # check date + jour = args.get("jour", None) + args["jour"] = jour + if jour: + M = self.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] + sem = sco_formsemestre.get_formsemestre(self, M["formsemestre_id"]) + d, m, y = [int(x) for x in sem["date_debut"].split("/")] + date_debut = datetime.date(y, m, d) + d, m, y = [int(x) for x in sem["date_fin"].split("/")] + date_fin = datetime.date(y, m, d) + # passe par DateDMYtoISO pour avoir date pivot + y, m, d = [int(x) for x in DateDMYtoISO(jour).split("-")] + jour = datetime.date(y, m, d) + if (jour > date_fin) or (jour < date_debut): + raise ScoValueError( + "La date de l'évaluation (%s/%s/%s) n'est pas dans le semestre !" + % (d, m, y) + ) + heure_debut = args.get("heure_debut", None) + args["heure_debut"] = heure_debut + heure_fin = args.get("heure_fin", None) + args["heure_fin"] = heure_fin + if jour and ((not heure_debut) or (not heure_fin)): + raise ScoValueError("Les heures doivent être précisées") + d = TimeDuration(heure_debut, heure_fin) + if d and ((d < 0) or (d > 60 * 12)): + raise ScoValueError("Heures de l'évaluation incohérentes !") + + security.declareProtected(ScoEnsView, "evaluation_delete") + + def evaluation_delete(self, REQUEST, evaluation_id): + """Form delete evaluation""" + El = self.do_evaluation_list(args={"evaluation_id": evaluation_id}) + if not El: + raise ValueError("Evalution inexistante ! (%s)" % evaluation_id) + E = El[0] + M = self.do_moduleimpl_list(moduleimpl_id=E["moduleimpl_id"])[0] + Mod = self.do_module_list(args={"module_id": M["module_id"]})[0] + tit = "Suppression de l'évaluation %(description)s (%(jour)s)" % E + etat = sco_evaluations.do_evaluation_etat(self, evaluation_id) + H = [ + self.html_sem_header(REQUEST, tit, with_h2=False), + """

    Module %(code)s %(titre)s

    """ + % Mod, + """

    %s

    """ % tit, + """

    Opération irréversible. Si vous supprimez l'évaluation, vous ne pourrez pas retrouver les notes associées.

    """, + ] + warning = False + if etat["nb_notes_total"]: + warning = True + nb_desinscrits = etat["nb_notes_total"] - etat["nb_notes"] + H.append( + """
    Il y a %s notes""" + % etat["nb_notes_total"] + ) + if nb_desinscrits: + H.append( + """ (dont %s d'étudiants qui ne sont plus inscrits)""" + % nb_desinscrits + ) + H.append(""" dans l'évaluation""") + if etat["nb_notes"] == 0: + H.append( + """

    Vous pouvez quand même supprimer l'évaluation, les notes des étudiants désincrits seront effacées.

    """ + ) + + if etat["nb_notes"]: + H.append( + """

    Suppression impossible (effacer les notes d'abord)

    retour au tableau de bord du module

    """ + % E["moduleimpl_id"] + ) + return "\n".join(H) + self.sco_footer(REQUEST) + if warning: + H.append("""""") + + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + (("evaluation_id", {"input_type": "hidden"}),), + initvalues=E, + submitlabel="Confirmer la suppression", + cancelbutton="Annuler", + ) + if tf[0] == 0: + return "\n".join(H) + tf[1] + self.sco_footer(REQUEST) + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect( + self.ScoURL() + + "/Notes/moduleimpl_status?moduleimpl_id=" + + E["moduleimpl_id"] + ) + else: + sco_evaluations.do_evaluation_delete(self, REQUEST, E["evaluation_id"]) + return ( + "\n".join(H) + + """

    OK, évaluation supprimée.

    +

    Continuer

    """ + % ( + self.ScoURL() + + "/Notes/moduleimpl_status?moduleimpl_id=" + + E["moduleimpl_id"] + ) + + self.sco_footer(REQUEST) + ) + + security.declareProtected(ScoView, "do_evaluation_list") + + def do_evaluation_list(self, args, sortkey=None): + """List evaluations, sorted by numero (or most recent date first). + + Ajoute les champs: + 'duree' : '2h30' + 'matin' : 1 (commence avant 12:00) ou 0 + 'apresmidi' : 1 (termine après 12:00) ou 0 + 'descrheure' : ' de 15h00 à 16h30' + """ + cnx = self.GetDBConnexion() + evals = self._evaluationEditor.list(cnx, args, sortkey=sortkey) + # calcule duree (chaine de car.) de chaque evaluation et ajoute jouriso, matin, apresmidi + for e in evals: + heure_debut_dt = e["heure_debut"] or datetime.time( + 8, 00 + ) # au cas ou pas d'heure (note externe?) + heure_fin_dt = e["heure_fin"] or datetime.time(8, 00) + e["heure_debut"] = TimefromISO8601(e["heure_debut"]) + e["heure_fin"] = TimefromISO8601(e["heure_fin"]) + e["jouriso"] = DateDMYtoISO(e["jour"]) + heure_debut, heure_fin = e["heure_debut"], e["heure_fin"] + d = TimeDuration(heure_debut, heure_fin) + if d is not None: + m = d % 60 + e["duree"] = "%dh" % (d / 60) + if m != 0: + e["duree"] += "%02d" % m + else: + e["duree"] = "" + if heure_debut and (not heure_fin or heure_fin == heure_debut): + e["descrheure"] = " à " + heure_debut + elif heure_debut and heure_fin: + e["descrheure"] = " de %s à %s" % (heure_debut, heure_fin) + else: + e["descrheure"] = "" + # matin, apresmidi: utile pour se referer aux absences: + if heure_debut_dt < datetime.time(12, 00): + e["matin"] = 1 + else: + e["matin"] = 0 + if heure_fin_dt > datetime.time(12, 00): + e["apresmidi"] = 1 + else: + e["apresmidi"] = 0 + + return evals + + security.declareProtected(ScoView, "do_evaluation_list_in_formsemestre") + + def do_evaluation_list_in_formsemestre(self, formsemestre_id): + "list evaluations in this formsemestre" + cnx = self.GetDBConnexion() + mods = self.do_moduleimpl_list(formsemestre_id=formsemestre_id) + evals = [] + for mod in mods: + evals += self.do_evaluation_list( + args={"moduleimpl_id": mod["moduleimpl_id"]} + ) + return evals + + security.declareProtected(ScoEnsView, "do_evaluation_edit") + + def do_evaluation_edit(self, REQUEST, args): + "edit a evaluation" + evaluation_id = args["evaluation_id"] + the_evals = self.do_evaluation_list({"evaluation_id": evaluation_id}) + if not the_evals: + raise ValueError("evaluation inexistante !") + moduleimpl_id = the_evals[0]["moduleimpl_id"] + args["moduleimpl_id"] = moduleimpl_id + self._check_evaluation_args(args) + self._evaluation_check_write_access(REQUEST, moduleimpl_id=moduleimpl_id) + cnx = self.GetDBConnexion() + self._evaluationEditor.edit(cnx, args) + # inval cache pour ce semestre + M = self.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] + self._inval_cache( + formsemestre_id=M["formsemestre_id"] + ) # > evaluation_edit (coef, ...) + + security.declareProtected(ScoEnsView, "evaluation_edit") + + def evaluation_edit(self, evaluation_id, REQUEST): + "form edit evaluation" + return sco_evaluations.evaluation_create_form( + self, evaluation_id=evaluation_id, REQUEST=REQUEST, edit=True + ) + + security.declareProtected(ScoEnsView, "evaluation_create") + + def evaluation_create(self, moduleimpl_id, REQUEST): + "form create evaluation" + return sco_evaluations.evaluation_create_form( + self, moduleimpl_id=moduleimpl_id, REQUEST=REQUEST, edit=False + ) + + security.declareProtected(ScoView, "evaluation_listenotes") + + def evaluation_listenotes(self, REQUEST=None): + """Affichage des notes d'une évaluation""" + if REQUEST.form.get("format", "html") == "html": + H = self.sco_header( + REQUEST, + cssstyles=["css/verticalhisto.css"], + javascripts=["js/etud_info.js"], + init_qtip=True, + ) + F = self.sco_footer(REQUEST) + else: + H, F = "", "" + B = self.do_evaluation_listenotes(REQUEST) + return H + B + F + + security.declareProtected(ScoView, "do_evaluation_listenotes") + do_evaluation_listenotes = sco_liste_notes.do_evaluation_listenotes + + security.declareProtected(ScoView, "evaluation_list_operations") + evaluation_list_operations = sco_undo_notes.evaluation_list_operations + + security.declareProtected(ScoView, "evaluation_check_absences_html") + evaluation_check_absences_html = sco_liste_notes.evaluation_check_absences_html + + security.declareProtected(ScoView, "formsemestre_check_absences_html") + formsemestre_check_absences_html = sco_liste_notes.formsemestre_check_absences_html + + # --- Placement des étudiants pour l'évaluation + security.declareProtected(ScoEnsView, "placement_eval_selectetuds") + placement_eval_selectetuds = sco_placement.placement_eval_selectetuds + + security.declareProtected(ScoEnsView, "do_placement") + do_placement = sco_placement.do_placement + + # --- Saisie des notes + security.declareProtected(ScoEnsView, "saisie_notes_tableur") + saisie_notes_tableur = sco_saisie_notes.saisie_notes_tableur + + security.declareProtected(ScoEnsView, "feuille_saisie_notes") + feuille_saisie_notes = sco_saisie_notes.feuille_saisie_notes + + security.declareProtected(ScoEnsView, "saisie_notes") + saisie_notes = sco_saisie_notes.saisie_notes + + security.declareProtected(ScoEnsView, "save_note") + save_note = sco_saisie_notes.save_note + + security.declareProtected(ScoEnsView, "do_evaluation_set_missing") + do_evaluation_set_missing = sco_saisie_notes.do_evaluation_set_missing + + security.declareProtected(ScoView, "evaluation_suppress_alln") + evaluation_suppress_alln = sco_saisie_notes.evaluation_suppress_alln + + security.declareProtected(ScoView, "can_edit_notes") + + def can_edit_notes(self, authuser, moduleimpl_id, allow_ens=True): + """True if authuser can enter or edit notes in this module. + If allow_ens, grant access to all ens in this module + + Si des décisions de jury ont déjà été saisies dans ce semestre, + seul le directeur des études peut saisir des notes (et il ne devrait pas). + """ + uid = str(authuser) + M = self.do_moduleimpl_list(moduleimpl_id=moduleimpl_id)[0] + sem = sco_formsemestre.get_formsemestre(self, M["formsemestre_id"]) + if sem["etat"] != "1": + return False # semestre verrouillé + + if sco_parcours_dut.formsemestre_has_decisions(self, sem["formsemestre_id"]): + # il y a des décisions de jury dans ce semestre ! + return ( + authuser.has_permission(ScoEditAllNotes, self) + or uid in sem["responsables"] + ) + else: + if ( + (not authuser.has_permission(ScoEditAllNotes, self)) + and uid != M["responsable_id"] + and uid not in sem["responsables"] + ): + # enseignant (chargé de TD) ? + if allow_ens: + for ens in M["ens"]: + if ens["ens_id"] == uid: + return True + return False + else: + return True + + security.declareProtected(ScoEditAllNotes, "dummy_ScoEditAllNotes") + + def dummy_ScoEditAllNotes(self): + "dummy method, necessary to declare permission ScoEditAllNotes" + return True + + security.declareProtected(ScoEditAllEvals, "dummy_ScoEditAllEvals") + + def dummy_ScoEditAllEvals(self): + "dummy method, necessary to declare permission ScoEditAllEvals" + return True + + security.declareProtected(ScoSuperAdmin, "dummy_ScoSuperAdmin") + + def dummy_ScoSuperAdmin(self): + "dummy method, necessary to declare permission ScoSuperAdmin" + return True + + security.declareProtected(ScoEtudChangeGroups, "dummy_ScoEtudChangeGroups") + + def dummy_ScoEtudChangeGroups(self): + "dummy method, necessary to declare permission ScoEtudChangeGroups" + return True + + security.declareProtected(ScoEtudSupprAnnotations, "dummy_ScoEtudSupprAnnotations") + + def dummy_ScoEtudSupprAnnotations(self): + "dummy method, necessary to declare permission ScoEtudSupprAnnotations" + return True + + security.declareProtected(ScoEditFormationTags, "dummy_ScoEditFormationTags") + + def dummy_ScoEditFormationTags(self): + "dummy method, necessary to declare permission ScoEditFormationTags" + return True + + # cache notes evaluations + def get_evaluations_cache(self): + u = self.GetDBConnexionString() + if CACHE_evaluations.has_key(u): + return CACHE_evaluations[u] + else: + log("get_evaluations_cache: new simpleCache") + CACHE_evaluations[u] = sco_cache.simpleCache() + return CACHE_evaluations[u] + + def _notes_getall( + self, evaluation_id, table="notes_notes", filter_suppressed=True, by_uid=None + ): + """get tt les notes pour une evaluation: { etudid : { 'value' : value, 'date' : date ... }} + Attention: inclue aussi les notes des étudiants qui ne sont plus inscrits au module. + """ + # log('_notes_getall( e=%s fs=%s )' % (evaluation_id, filter_suppressed)) + do_cache = ( + filter_suppressed and table == "notes_notes" and (by_uid is None) + ) # pas de cache pour (rares) appels via undo_notes ou specifiant un enseignant + if do_cache: + cache = self.get_evaluations_cache() + r = cache.get(evaluation_id) + if r != None: + return r + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cond = " where evaluation_id=%(evaluation_id)s" + if by_uid: + cond += " and uid=%(by_uid)s" + + cursor.execute( + "select * from " + table + cond, + {"evaluation_id": evaluation_id, "by_uid": by_uid}, + ) + res = cursor.dictfetchall() + d = {} + if filter_suppressed: + for x in res: + if x["value"] != NOTES_SUPPRESS: + d[x["etudid"]] = x + else: + for x in res: + d[x["etudid"]] = x + if do_cache: + cache.set(evaluation_id, d) + return d + + # --- Bulletins + security.declareProtected(ScoView, "formsemestre_bulletins_pdf") + + def formsemestre_bulletins_pdf( + self, formsemestre_id, REQUEST, version="selectedevals" + ): + "Publie les bulletins dans un classeur PDF" + pdfdoc, filename = sco_bulletins_pdf.get_formsemestre_bulletins_pdf( + self, formsemestre_id, REQUEST, version=version + ) + return sendPDFFile(REQUEST, pdfdoc, filename) + + security.declareProtected(ScoView, "etud_bulletins_pdf") + + def etud_bulletins_pdf(self, etudid, REQUEST, version="selectedevals"): + "Publie tous les bulletins d'un etudiants dans un classeur PDF" + pdfdoc, filename = sco_bulletins_pdf.get_etud_bulletins_pdf( + self, etudid, REQUEST, version=version + ) + return sendPDFFile(REQUEST, pdfdoc, filename) + + security.declareProtected(ScoView, "formsemestre_bulletins_pdf_choice") + formsemestre_bulletins_pdf_choice = sco_bulletins.formsemestre_bulletins_pdf_choice + + security.declareProtected(ScoView, "formsemestre_bulletins_mailetuds_choice") + formsemestre_bulletins_mailetuds_choice = ( + sco_bulletins.formsemestre_bulletins_mailetuds_choice + ) + + security.declareProtected(ScoView, "formsemestre_bulletins_mailetuds") + + def formsemestre_bulletins_mailetuds( + self, + formsemestre_id, + REQUEST, + version="long", + dialog_confirmed=False, + prefer_mail_perso=0, + ): + "envoi a chaque etudiant (inscrit et ayant un mail) son bulletin" + prefer_mail_perso = int(prefer_mail_perso) + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + nt = self._getNotesCache().get_NotesTable( + self, formsemestre_id + ) # > get_etudids + etudids = nt.get_etudids() + # + if not sco_bulletins.can_send_bulletin_by_mail(self, formsemestre_id, REQUEST): + raise AccessDenied("vous n'avez pas le droit d'envoyer les bulletins") + # Confirmation dialog + if not dialog_confirmed: + return self.confirmDialog( + "

    Envoyer les %d bulletins par e-mail aux étudiants ?" + % len(etudids), + dest_url="", + REQUEST=REQUEST, + cancel_url="formsemestre_status?formsemestre_id=%s" % formsemestre_id, + parameters={ + "version": version, + "formsemestre_id": formsemestre_id, + "prefer_mail_perso": prefer_mail_perso, + }, + ) + + # Make each bulletin + nb_send = 0 + for etudid in etudids: + h, i = sco_bulletins.do_formsemestre_bulletinetud( + self, + formsemestre_id, + etudid, + version=version, + prefer_mail_perso=prefer_mail_perso, + format="pdfmail", + nohtml=True, + REQUEST=REQUEST, + ) + if h: + nb_send += 1 + # + return ( + self.sco_header(REQUEST) + + '

    %d bulletins sur %d envoyés par mail !

    continuer

    ' + % (nb_send, len(etudids), formsemestre_id) + + self.sco_footer(REQUEST) + ) + + security.declareProtected(ScoView, "formsemestre_bulletins_mailetuds") + external_ue_create_form = sco_ue_external.external_ue_create_form + + security.declareProtected(ScoEnsView, "appreciation_add_form") + + def appreciation_add_form( + self, + etudid=None, + formsemestre_id=None, + id=None, # si id, edit + suppress=False, # si true, supress id + REQUEST=None, + ): + "form ajout ou edition d'une appreciation" + cnx = self.GetDBConnexion() + authuser = REQUEST.AUTHENTICATED_USER + if id: # edit mode + apps = scolars.appreciations_list(cnx, args={"id": id}) + if not apps: + raise ScoValueError("id d'appreciation invalide !") + app = apps[0] + formsemestre_id = app["formsemestre_id"] + etudid = app["etudid"] + if REQUEST.form.has_key("edit"): + edit = int(REQUEST.form["edit"]) + elif id: + edit = 1 + else: + edit = 0 + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + # check custom access permission + can_edit_app = (str(authuser) in sem["responsables"]) or ( + authuser.has_permission(ScoEtudInscrit, self) + ) + if not can_edit_app: + raise AccessDenied("vous n'avez pas le droit d'ajouter une appreciation") + # + bull_url = "formsemestre_bulletinetud?formsemestre_id=%s&etudid=%s" % ( + formsemestre_id, + etudid, + ) + if suppress: + scolars.appreciations_delete(cnx, id) + logdb(REQUEST, cnx, method="appreciation_suppress", etudid=etudid, msg="") + return REQUEST.RESPONSE.redirect(bull_url) + # + etud = self.getEtudInfo(etudid=etudid, filled=1)[0] + if id: + a = "Edition" + else: + a = "Ajout" + H = [ + self.sco_header(REQUEST) + + "

    %s d'une appréciation sur %s

    " % (a, etud["nomprenom"]) + ] + F = self.sco_footer(REQUEST) + descr = [ + ("edit", {"input_type": "hidden", "default": edit}), + ("etudid", {"input_type": "hidden"}), + ("formsemestre_id", {"input_type": "hidden"}), + ("id", {"input_type": "hidden"}), + ("comment", {"title": "", "input_type": "textarea", "rows": 4, "cols": 60}), + ] + if id: + initvalues = { + "etudid": etudid, + "formsemestre_id": formsemestre_id, + "comment": app["comment"], + } + else: + initvalues = {} + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + descr, + initvalues=initvalues, + cancelbutton="Annuler", + submitlabel="Ajouter appréciation", + ) + if tf[0] == 0: + return "\n".join(H) + "\n" + tf[1] + F + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect(bull_url) + else: + args = { + "etudid": etudid, + "formsemestre_id": formsemestre_id, + "author": str(authuser), + "comment": tf[2]["comment"], + "zope_authenticated_user": str(authuser), + "zope_remote_addr": REQUEST.REMOTE_ADDR, + } + if edit: + args["id"] = id + scolars.appreciations_edit(cnx, args) + else: # nouvelle + scolars.appreciations_create(cnx, args, has_uniq_values=False) + # log + logdb( + REQUEST, + cnx, + method="appreciation_add", + etudid=etudid, + msg=tf[2]["comment"], + ) + # ennuyeux mais necessaire (pour le PDF seulement) + self._inval_cache( + pdfonly=True, formsemestre_id=formsemestre_id + ) # > appreciation_add + return REQUEST.RESPONSE.redirect(bull_url) + + security.declareProtected(ScoView, "can_change_groups") + + def can_change_groups(self, REQUEST, formsemestre_id): + "Vrai si utilisateur peut changer les groupes dans ce semestre" + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + if sem["etat"] != "1": + return False # semestre verrouillé + authuser = REQUEST.AUTHENTICATED_USER + if authuser.has_permission(ScoEtudChangeGroups, self): + return True # admin, chef dept + uid = str(authuser) + if uid in sem["responsables"]: + return True + return False + + def _can_edit_pv(self, REQUEST, formsemestre_id): + "Vrai si utilisateur peut editer un PV de jury de ce semestre" + + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + if self._is_chef_or_diretud(REQUEST, sem): + return True + # Autorise les secrétariats, repérés via la permission ScoEtudChangeAdr + # (ceci nous évite d'ajouter une permission Zope aux installations existantes) + authuser = REQUEST.AUTHENTICATED_USER + return authuser.has_permission(ScoEtudChangeAdr, self) + + # --- FORMULAIRE POUR VALIDATION DES UE ET SEMESTRES + def _can_validate_sem(self, REQUEST, formsemestre_id): + "Vrai si utilisateur peut saisir decision de jury dans ce semestre" + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + if sem["etat"] != "1": + return False # semestre verrouillé + + return self._is_chef_or_diretud(REQUEST, sem) + + def _is_chef_or_diretud(self, REQUEST, sem): + "Vrai si utilisateur est admin, chef dept ou responsable du semestre" + authuser = REQUEST.AUTHENTICATED_USER + if authuser.has_permission(ScoImplement, self): + return True # admin, chef dept + uid = str(authuser) + if uid in sem["responsables"]: + return True + + return False + + security.declareProtected(ScoView, "formsemestre_validation_etud_form") + + def formsemestre_validation_etud_form( + self, + formsemestre_id, + etudid=None, + etud_index=None, + check=0, + desturl="", + sortcol=None, + REQUEST=None, + ): + "Formulaire choix jury pour un étudiant" + readonly = not self._can_validate_sem(REQUEST, formsemestre_id) + return sco_formsemestre_validation.formsemestre_validation_etud_form( + self, + formsemestre_id, + etudid=etudid, + etud_index=etud_index, + check=check, + readonly=readonly, + desturl=desturl, + sortcol=sortcol, + REQUEST=REQUEST, + ) + + security.declareProtected(ScoView, "formsemestre_validation_etud") + + def formsemestre_validation_etud( + self, + formsemestre_id, + etudid=None, + codechoice=None, + desturl="", + sortcol=None, + REQUEST=None, + ): + "Enregistre choix jury pour un étudiant" + if not self._can_validate_sem(REQUEST, formsemestre_id): + return self.confirmDialog( + message="

    Opération non autorisée pour %s" + % REQUEST.AUTHENTICATED_USER, + dest_url=self.ScoURL(), + REQUEST=REQUEST, + ) + + return sco_formsemestre_validation.formsemestre_validation_etud( + self, + formsemestre_id, + etudid=etudid, + codechoice=codechoice, + desturl=desturl, + sortcol=sortcol, + REQUEST=REQUEST, + ) + + security.declareProtected(ScoView, "formsemestre_validation_etud_manu") + + def formsemestre_validation_etud_manu( + self, + formsemestre_id, + etudid=None, + code_etat="", + new_code_prev="", + devenir="", + assidu=False, + desturl="", + sortcol=None, + REQUEST=None, + ): + "Enregistre choix jury pour un étudiant" + if not self._can_validate_sem(REQUEST, formsemestre_id): + return self.confirmDialog( + message="

    Opération non autorisée pour %s" + % REQUEST.AUTHENTICATED_USER, + dest_url=self.ScoURL(), + REQUEST=REQUEST, + ) + + return sco_formsemestre_validation.formsemestre_validation_etud_manu( + self, + formsemestre_id, + etudid=etudid, + code_etat=code_etat, + new_code_prev=new_code_prev, + devenir=devenir, + assidu=assidu, + desturl=desturl, + sortcol=sortcol, + REQUEST=REQUEST, + ) + + security.declareProtected(ScoView, "formsemestre_validate_previous_ue") + + def formsemestre_validate_previous_ue( + self, formsemestre_id, etudid=None, REQUEST=None + ): + "Form. saisie UE validée hors ScoDoc " + if not self._can_validate_sem(REQUEST, formsemestre_id): + return self.confirmDialog( + message="

    Opération non autorisée pour %s" + % REQUEST.AUTHENTICATED_USER, + dest_url=self.ScoURL(), + REQUEST=REQUEST, + ) + return sco_formsemestre_validation.formsemestre_validate_previous_ue( + self, formsemestre_id, etudid, REQUEST=REQUEST + ) + + security.declareProtected(ScoView, "formsemestre_ext_create_form") + formsemestre_ext_create_form = ( + sco_formsemestre_exterieurs.formsemestre_ext_create_form + ) + + security.declareProtected(ScoView, "formsemestre_ext_edit_ue_validations") + + def formsemestre_ext_edit_ue_validations( + self, formsemestre_id, etudid=None, REQUEST=None + ): + "Form. edition UE semestre extérieur" + if not self._can_validate_sem(REQUEST, formsemestre_id): + return self.confirmDialog( + message="

    Opération non autorisée pour %s" + % REQUEST.AUTHENTICATED_USER, + dest_url=self.ScoURL(), + REQUEST=REQUEST, + ) + return sco_formsemestre_exterieurs.formsemestre_ext_edit_ue_validations( + self, formsemestre_id, etudid, REQUEST=REQUEST + ) + + security.declareProtected(ScoView, "get_etud_ue_cap_html") + get_etud_ue_cap_html = sco_formsemestre_validation.get_etud_ue_cap_html + + security.declareProtected(ScoView, "etud_ue_suppress_validation") + + def etud_ue_suppress_validation(self, etudid, formsemestre_id, ue_id, REQUEST=None): + """Suppress a validation (ue_id, etudid) and redirect to formsemestre""" + if not self._can_validate_sem(REQUEST, formsemestre_id): + return self.confirmDialog( + message="

    Opération non autorisée pour %s" + % REQUEST.AUTHENTICATED_USER, + dest_url=self.ScoURL(), + REQUEST=REQUEST, + ) + return sco_formsemestre_validation.etud_ue_suppress_validation( + self, etudid, formsemestre_id, ue_id, REQUEST=REQUEST + ) + + security.declareProtected(ScoView, "formsemestre_validation_auto") + + def formsemestre_validation_auto(self, formsemestre_id, REQUEST): + "Formulaire saisie automatisee des decisions d'un semestre" + if not self._can_validate_sem(REQUEST, formsemestre_id): + return self.confirmDialog( + message="

    Opération non autorisée pour %s" + % REQUEST.AUTHENTICATED_USER, + dest_url=self.ScoURL(), + REQUEST=REQUEST, + ) + + return sco_formsemestre_validation.formsemestre_validation_auto( + self, formsemestre_id, REQUEST + ) + + security.declareProtected(ScoView, "formsemestre_validation_auto") + + def do_formsemestre_validation_auto(self, formsemestre_id, REQUEST): + "Formulaire saisie automatisee des decisions d'un semestre" + if not self._can_validate_sem(REQUEST, formsemestre_id): + return self.confirmDialog( + message="

    Opération non autorisée pour %s" + % REQUEST.AUTHENTICATED_USER, + dest_url=self.ScoURL(), + REQUEST=REQUEST, + ) + + return sco_formsemestre_validation.do_formsemestre_validation_auto( + self, formsemestre_id, REQUEST + ) + + security.declareProtected(ScoView, "formsemestre_fix_validation_ues") + + def formsemestre_fix_validation_ues(self, formsemestre_id, REQUEST=None): + "Verif/reparation codes UE" + if not self._can_validate_sem(REQUEST, formsemestre_id): + return self.confirmDialog( + message="

    Opération non autorisée pour %s" + % REQUEST.AUTHENTICATED_USER, + dest_url=self.ScoURL(), + REQUEST=REQUEST, + ) + + return sco_formsemestre_validation.formsemestre_fix_validation_ues( + self, formsemestre_id, REQUEST + ) + + security.declareProtected(ScoView, "formsemestre_validation_suppress_etud") + + def formsemestre_validation_suppress_etud( + self, formsemestre_id, etudid, REQUEST=None, dialog_confirmed=False + ): + """Suppression des decisions de jury pour un etudiant. + """ + if not self._can_validate_sem(REQUEST, formsemestre_id): + return self.confirmDialog( + message="

    Opération non autorisée pour %s" + % REQUEST.AUTHENTICATED_USER, + dest_url=self.ScoURL(), + REQUEST=REQUEST, + ) + if not dialog_confirmed: + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + etud = self.getEtudInfo(etudid=etudid, filled=1)[0] + nt = self._getNotesCache().get_NotesTable( + self, formsemestre_id + ) # > get_etud_decision_sem + decision_jury = nt.get_etud_decision_sem(etudid) + if decision_jury: + existing = ( + "

    Décision existante: %(code)s du %(event_date)s

    " + % decision_jury + ) + else: + existing = "" + return self.confirmDialog( + """

    Confirmer la suppression des décisions du semestre %s (%s - %s) pour %s ?

    %s +

    Cette opération est irréversible. +

    + """ + % ( + sem["titre_num"], + sem["date_debut"], + sem["date_fin"], + etud["nomprenom"], + existing, + ), + OK="Supprimer", + dest_url="", + REQUEST=REQUEST, + cancel_url="formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s" + % (formsemestre_id, etudid), + parameters={"etudid": etudid, "formsemestre_id": formsemestre_id}, + ) + + sco_formsemestre_validation.formsemestre_validation_suppress_etud( + self, formsemestre_id, etudid + ) + return REQUEST.RESPONSE.redirect( + self.ScoURL() + + "/Notes/formsemestre_validation_etud_form?formsemestre_id=%s&etudid=%s&head_message=Décision%%20supprimée" + % (formsemestre_id, etudid) + ) + + # ------------- PV de JURY et archives + security.declareProtected(ScoView, "formsemestre_pvjury") + formsemestre_pvjury = sco_pvjury.formsemestre_pvjury + + security.declareProtected(ScoView, "formsemestre_lettres_individuelles") + formsemestre_lettres_individuelles = sco_pvjury.formsemestre_lettres_individuelles + security.declareProtected(ScoView, "formsemestre_pvjury_pdf") + formsemestre_pvjury_pdf = sco_pvjury.formsemestre_pvjury_pdf + + security.declareProtected(ScoView, "feuille_preparation_jury") + feuille_preparation_jury = sco_prepajury.feuille_preparation_jury + + security.declareProtected(ScoView, "formsemestre_archive") + formsemestre_archive = sco_archives.formsemestre_archive + + security.declareProtected(ScoView, "formsemestre_delete_archive") + formsemestre_delete_archive = sco_archives.formsemestre_delete_archive + + security.declareProtected(ScoView, "formsemestre_list_archives") + formsemestre_list_archives = sco_archives.formsemestre_list_archives + + security.declareProtected(ScoView, "formsemestre_get_archived_file") + formsemestre_get_archived_file = sco_archives.formsemestre_get_archived_file + + security.declareProtected(ScoEditApo, "view_apo_csv") + view_apo_csv = sco_etape_apogee_view.view_apo_csv + + security.declareProtected(ScoEditApo, "view_apo_csv_store") + view_apo_csv_store = sco_etape_apogee_view.view_apo_csv_store + + security.declareProtected(ScoEditApo, "view_apo_csv_download_and_store") + view_apo_csv_download_and_store = ( + sco_etape_apogee_view.view_apo_csv_download_and_store + ) + + security.declareProtected(ScoEditApo, "view_apo_csv_delete") + view_apo_csv_delete = sco_etape_apogee_view.view_apo_csv_delete + + security.declareProtected(ScoEditApo, "view_scodoc_etuds") + view_scodoc_etuds = sco_etape_apogee_view.view_scodoc_etuds + + security.declareProtected(ScoEditApo, "view_apo_etuds") + view_apo_etuds = sco_etape_apogee_view.view_apo_etuds + + security.declareProtected(ScoEditApo, "apo_semset_maq_status") + apo_semset_maq_status = sco_etape_apogee_view.apo_semset_maq_status + + security.declareProtected(ScoEditApo, "apo_csv_export_results") + apo_csv_export_results = sco_etape_apogee_view.apo_csv_export_results + + # sco_semset + security.declareProtected(ScoEditApo, "semset_page") + semset_page = sco_semset.semset_page + + security.declareProtected(ScoEditApo, "do_semset_create") + do_semset_create = sco_semset.do_semset_create + + security.declareProtected(ScoEditApo, "do_semset_delete") + do_semset_delete = sco_semset.do_semset_delete + + security.declareProtected(ScoEditApo, "edit_semset_set_title") + edit_semset_set_title = sco_semset.edit_semset_set_title + + security.declareProtected(ScoEditApo, "do_semset_add_sem") + do_semset_add_sem = sco_semset.do_semset_add_sem + + security.declareProtected(ScoEditApo, "do_semset_remove_sem") + do_semset_remove_sem = sco_semset.do_semset_remove_sem + + # sco_export_result + security.declareProtected(ScoEditApo, "scodoc_table_results") + scodoc_table_results = sco_export_results.scodoc_table_results + + security.declareProtected(ScoView, "apo_compare_csv_form") + apo_compare_csv_form = sco_apogee_compare.apo_compare_csv_form + + security.declareProtected(ScoView, "apo_compare_csv") + apo_compare_csv = sco_apogee_compare.apo_compare_csv + + # ------------- INSCRIPTIONS: PASSAGE D'UN SEMESTRE A UN AUTRE + security.declareProtected(ScoEtudInscrit, "formsemestre_inscr_passage") + formsemestre_inscr_passage = sco_inscr_passage.formsemestre_inscr_passage + + security.declareProtected(ScoView, "formsemestre_synchro_etuds") + formsemestre_synchro_etuds = sco_synchro_etuds.formsemestre_synchro_etuds + + # ------------- RAPPORTS STATISTIQUES + security.declareProtected(ScoView, "formsemestre_report_counts") + formsemestre_report_counts = sco_report.formsemestre_report_counts + + security.declareProtected(ScoView, "formsemestre_suivi_cohorte") + formsemestre_suivi_cohorte = sco_report.formsemestre_suivi_cohorte + + security.declareProtected(ScoView, "formsemestre_suivi_parcours") + formsemestre_suivi_parcours = sco_report.formsemestre_suivi_parcours + + security.declareProtected(ScoView, "formsemestre_etuds_lycees") + formsemestre_etuds_lycees = sco_lycee.formsemestre_etuds_lycees + + security.declareProtected(ScoView, "scodoc_table_etuds_lycees") + scodoc_table_etuds_lycees = sco_lycee.scodoc_table_etuds_lycees + + security.declareProtected(ScoView, "formsemestre_graph_parcours") + formsemestre_graph_parcours = sco_report.formsemestre_graph_parcours + + security.declareProtected(ScoView, "formsemestre_poursuite_report") + formsemestre_poursuite_report = sco_poursuite_dut.formsemestre_poursuite_report + + security.declareProtected(ScoView, "pe_view_sem_recap") + pe_view_sem_recap = pe_view.pe_view_sem_recap + + security.declareProtected(ScoView, "report_debouche_date") + report_debouche_date = sco_debouche.report_debouche_date + + security.declareProtected(ScoView, "formsemestre_estim_cost") + formsemestre_estim_cost = sco_cost_formation.formsemestre_estim_cost + + # -------------------------------------------------------------------- + # DEBUG + security.declareProtected(ScoView, "check_sem_integrity") + + def check_sem_integrity(self, formsemestre_id, REQUEST): + """Debug. + Check that ue and module formations are consistents + """ + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + + modimpls = self.do_moduleimpl_list(formsemestre_id=formsemestre_id) + bad_ue = [] + bad_sem = [] + for modimpl in modimpls: + mod = self.do_module_list({"module_id": modimpl["module_id"]})[0] + ue = self.do_ue_list({"ue_id": mod["ue_id"]})[0] + if ue["formation_id"] != mod["formation_id"]: + modimpl["mod"] = mod + modimpl["ue"] = ue + bad_ue.append(modimpl) + if sem["formation_id"] != mod["formation_id"]: + bad_sem.append(modimpl) + modimpl["mod"] = mod + + return ( + self.sco_header(REQUEST=REQUEST) + + "

    formation_id=%s" % sem["formation_id"] + + "

    Inconsistent UE/MOD:

    " + + "
    ".join([str(x) for x in bad_ue]) + + "

    Inconsistent SEM/MOD:

    " + + "
    ".join([str(x) for x in bad_sem]) + + self.sco_footer(REQUEST) + ) + + security.declareProtected(ScoView, "check_form_integrity") + + def check_form_integrity(self, formation_id, fix=False, REQUEST=None): + "debug" + log("check_form_integrity: formation_id=%s fix=%s" % (formation_id, fix)) + F = self.formation_list(args={"formation_id": formation_id})[0] + ues = self.do_ue_list(args={"formation_id": formation_id}) + bad = [] + for ue in ues: + mats = self.do_matiere_list(args={"ue_id": ue["ue_id"]}) + for mat in mats: + mods = self.do_module_list({"matiere_id": mat["matiere_id"]}) + for mod in mods: + if mod["ue_id"] != ue["ue_id"]: + if fix: + # fix mod.ue_id + log( + "fix: mod.ue_id = %s (was %s)" + % (ue["ue_id"], mod["ue_id"]) + ) + mod["ue_id"] = ue["ue_id"] + self.do_module_edit(mod) + bad.append(mod) + if mod["formation_id"] != formation_id: + bad.append(mod) + if bad: + txth = "
    ".join([str(x) for x in bad]) + txt = "\n".join([str(x) for x in bad]) + log( + "check_form_integrity: formation_id=%s\ninconsistencies:" % formation_id + ) + log(txt) + # Notify by e-mail + sendAlarm(self, "Notes: formation incoherente !", txt) + else: + txth = "OK" + log("ok") + return self.sco_header(REQUEST=REQUEST) + txth + self.sco_footer(REQUEST) + + security.declareProtected(ScoView, "check_formsemestre_integrity") + + def check_formsemestre_integrity(self, formsemestre_id, REQUEST=None): + "debug" + log("check_formsemestre_integrity: formsemestre_id=%s" % (formsemestre_id)) + # verifie que tous les moduleimpl d'un formsemestre + # se réfèrent à un module dont l'UE appartient a la même formation + # Ancien bug: les ue_id étaient mal copiés lors des création de versions + # de formations + diag = [] + + Mlist = self.do_moduleimpl_withmodule_list(formsemestre_id=formsemestre_id) + for mod in Mlist: + if mod["module"]["ue_id"] != mod["matiere"]["ue_id"]: + diag.append( + "moduleimpl %s: module.ue_id=%s != matiere.ue_id=%s" + % ( + mod["moduleimpl_id"], + mod["module"]["ue_id"], + mod["matiere"]["ue_id"], + ) + ) + if mod["ue"]["formation_id"] != mod["module"]["formation_id"]: + diag.append( + "moduleimpl %s: ue.formation_id=%s != mod.formation_id=%s" + % ( + mod["moduleimpl_id"], + mod["ue"]["formation_id"], + mod["module"]["formation_id"], + ) + ) + if diag: + sendAlarm( + self, + "Notes: formation incoherente dans semestre %s !" % formsemestre_id, + "\n".join(diag), + ) + log("check_formsemestre_integrity: formsemestre_id=%s" % formsemestre_id) + log("inconsistencies:\n" + "\n".join(diag)) + else: + diag = ["OK"] + log("ok") + return ( + self.sco_header(REQUEST=REQUEST) + + "
    ".join(diag) + + self.sco_footer(REQUEST) + ) + + security.declareProtected(ScoView, "check_integrity_all") + + def check_integrity_all(self, REQUEST=None): + "debug: verifie tous les semestres et tt les formations" + # formations + for F in self.formation_list(): + self.check_form_integrity(F["formation_id"], REQUEST=REQUEST) + # semestres + for sem in sco_formsemestre.do_formsemestre_list(self): + self.check_formsemestre_integrity(sem["formsemestre_id"], REQUEST=REQUEST) + return ( + self.sco_header(REQUEST=REQUEST) + + "

    empty page: see logs and mails

    " + + self.sco_footer(REQUEST) + ) + + # -------------------------------------------------------------------- + + +# -------------------------------------------------------------------- +# +# Zope Product Administration +# +# -------------------------------------------------------------------- +def manage_addZNotes( + self, id="id_ZNotes", title="The Title for ZNotes Object", REQUEST=None +): + "Add a ZNotes instance to a folder." + self._setObject(id, ZNotes(id, title)) + if REQUEST is not None: + return self.manage_main(self, REQUEST) + # return self.manage_editForm(self, REQUEST) + + +# The form used to get the instance id from the user. +manage_addZNotesForm = DTMLFile("dtml/manage_addZNotesForm", globals()) diff --git a/ZScoDoc.py b/ZScoDoc.py new file mode 100644 index 00000000..a3f1d890 --- /dev/null +++ b/ZScoDoc.py @@ -0,0 +1,954 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +"""Site ScoDoc pour plusieurs departements: + gestion de l'installation et des creation de départements. + + Chaque departement est géré par un ZScolar sous ZScoDoc. +""" + +import time, string, glob, re, inspect +import urllib, urllib2, cgi, xml + +try: + from cStringIO import StringIO +except: + from StringIO import StringIO +from zipfile import ZipFile +import os.path, glob +import traceback + +from email.MIMEMultipart import MIMEMultipart +from email.MIMEText import MIMEText +from email.MIMEBase import MIMEBase +from email.Header import Header +from email import Encoders + +from sco_zope import * + +# +try: + import Products.ZPsycopgDA.DA as ZopeDA +except: + import ZPsycopgDA.DA as ZopeDA # interp.py + +from sco_utils import * +from notes_log import log +import sco_find_etud +from ZScoUsers import pwdFascistCheck + + +class ZScoDoc(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit): + + "ZScoDoc object" + + meta_type = "ZScoDoc" + security = ClassSecurityInfo() + file_path = Globals.package_home(globals()) + + # This is the list of the methods associated to 'tabs' in the ZMI + # Be aware that The first in the list is the one shown by default, so if + # the 'View' tab is the first, you will never see your tabs by cliquing + # on the object. + manage_options = ( + ({"label": "Contents", "action": "manage_main"},) + + PropertyManager.manage_options # add the 'Properties' tab + + ( + # this line is kept as an example with the files : + # dtml/manage_editZScolarForm.dtml + # html/ZScolar-edit.stx + # {'label': 'Properties', 'action': 'manage_editForm',}, + {"label": "View", "action": "index_html"}, + ) + + Item.manage_options # add the 'Undo' & 'Owner' tab + + RoleManager.manage_options # add the 'Security' tab + ) + + def __init__(self, id, title): + "Initialise a new instance of ZScoDoc" + self.id = id + self.title = title + self.manage_addProperty("admin_password_initialized", "0", "string") + + security.declareProtected(ScoView, "ScoDocURL") + + def ScoDocURL(self): + "base URL for this instance (top level for ScoDoc site)" + return self.absolute_url() + + def _check_admin_perm(self, REQUEST): + """Check if user has permission to add/delete departements + """ + authuser = REQUEST.AUTHENTICATED_USER + if authuser.has_role("manager") or authuser.has_permission(ScoSuperAdmin, self): + return "" + else: + return """

    Vous n'avez pas le droit d'accéder à cette page

    """ + + def _check_users_folder(self, REQUEST=None): + """Vérifie UserFolder et le crée s'il le faut + """ + try: + udb = self.UsersDB + return "" + except: + e = self._check_admin_perm(REQUEST) + if not e: # admin permissions: + self.create_users_cnx(REQUEST) + self.create_users_folder(REQUEST) + return '
    Création du connecteur utilisateurs réussie
    ' + else: + return """
    Installation non terminée: connectez vous avec les droits d'administrateur
    """ + + security.declareProtected("View", "create_users_folder") + + def create_users_folder(self, REQUEST=None): + """Create Zope user folder + """ + e = self._check_admin_perm(REQUEST) + if e: + return e + + if REQUEST is None: + REQUEST = {} + + REQUEST.form["pgauth_connection"] = "UsersDB" + REQUEST.form["pgauth_table"] = "sco_users" + REQUEST.form["pgauth_usernameColumn"] = "user_name" + REQUEST.form["pgauth_passwordColumn"] = "passwd" + REQUEST.form["pgauth_rolesColumn"] = "roles" + + add_method = self.manage_addProduct["OFSP"].manage_addexUserFolder + log("create_users_folder: in %s" % self.id) + return add_method( + authId="pgAuthSource", + propId="nullPropSource", + memberId="nullMemberSource", + groupId="nullGroupSource", + cryptoId="MD51", + # doAuth='1', doProp='1', doMember='1', doGroup='1', allDone='1', + cookie_mode=2, + session_length=500, + not_session_length=0, + REQUEST=REQUEST, + ) + + def _fix_users_folder(self): + """removes docLogin and docLogout dtml methods from exUserFolder, so that we use ours. + (called each time be index_html, to fix old ScoDoc installations.) + """ + try: + self.acl_users.manage_delObjects(ids=["docLogin", "docLogout"]) + except: + pass + # add missing getAuthFailedMessage (bug in exUserFolder ?) + try: + x = self.getAuthFailedMessage + except: + log("adding getAuthFailedMessage to Zope install") + parent = self.aq_parent + from OFS.DTMLMethod import addDTMLMethod + + addDTMLMethod(parent, "getAuthFailedMessage", file="Identification") + + security.declareProtected("View", "create_users_cnx") + + def create_users_cnx(self, REQUEST=None): + """Create Zope connector to UsersDB + + Note: la connexion est fixée (SCOUSERS) (base crée par l'installeur) ! + Les utilisateurs avancés pourront la changer ensuite. + """ + # ce connecteur zope - db est encore pour l'instant utilisé par exUserFolder.pgAuthSource + # (en lecture seule en principe) + oid = "UsersDB" + log("create_users_cnx: in %s" % self.id) + da = ZopeDA.Connection( + oid, + "Cnx bd utilisateurs", + SCO_DEFAULT_SQL_USERS_CNX, + False, + check=1, + tilevel=2, + encoding="LATIN1", + ) + self._setObject(oid, da) + + security.declareProtected("View", "change_admin_user") + + def change_admin_user(self, password, REQUEST=None): + """Change password of admin user""" + # note: controle sur le role et non pas sur une permission + # (non definies au top level) + if not REQUEST.AUTHENTICATED_USER.has_role("Manager"): + log("user %s is not Manager" % REQUEST.AUTHENTICATED_USER) + log("roles=%s" % REQUEST.AUTHENTICATED_USER.getRolesInContext(self)) + raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération") + log("trying to change admin password") + # 1-- check strong password + if pwdFascistCheck(password) != None: + log("refusing weak password") + return REQUEST.RESPONSE.redirect( + "change_admin_user_form?message=Mot%20de%20passe%20trop%20simple,%20recommencez" + ) + # 2-- change password for admin user + username = "admin" + acl_users = self.aq_parent.acl_users + user = acl_users.getUser(username) + r = acl_users._changeUser( + username, password, password, user.roles, user.domains + ) + if not r: + # OK, set property to indicate we changed the password + log("admin password changed successfully") + self.manage_changeProperties(admin_password_initialized="1") + return r or REQUEST.RESPONSE.redirect("index_html") + + security.declareProtected("View", "change_admin_user_form") + + def change_admin_user_form(self, message="", REQUEST=None): + """Form allowing to change the ScoDoc admin password""" + # note: controle sur le role et non pas sur une permission + # (non definies au top level) + if not REQUEST.AUTHENTICATED_USER.has_role("Manager"): + raise AccessDenied("vous n'avez pas le droit d'effectuer cette opération") + H = [ + self.scodoc_top_html_header( + REQUEST, page_title="ScoDoc: changement mot de passe" + ) + ] + if message: + H.append('
    %s
    ' % message) + H.append( + """

    Changement du mot de passe administrateur (utilisateur admin)

    +

    +

    + + +
    Nouveau mot de passe:
    Confirmation:
    + +""" + ) + H.append("""""") + return "\n".join(H) + + security.declareProtected("View", "list_depts") + + def list_depts(self, REQUEST=None): + """List departments folders + (returns a list of Zope folders containing a ZScolar instance) + """ + folders = self.objectValues("Folder") + # select folders with Scolarite object: + r = [] + for folder in folders: + try: + s = folder.Scolarite + r.append(folder) + except: + pass + return r + + security.declareProtected("View", "create_dept") + + def create_dept(self, REQUEST=None, DeptId="", pass2=False): + """Creation (ajout) d'un site departement + (instance ZScolar + dossier la contenant) + """ + e = self._check_admin_perm(REQUEST) + if e: + return e + + if not DeptId: + raise ValueError("nom de departement invalide") + if not pass2: + # 1- Creation de repertoire Dept + add_method = self.manage_addProduct["OFSP"].manage_addFolder + add_method(DeptId, title="Site dept. " + DeptId) + + DeptFolder = self[DeptId] + + if not pass2: + # 2- Creation du repertoire Fotos + add_method = DeptFolder.manage_addProduct["OFSP"].manage_addFolder + add_method("Fotos", title="Photos identites " + DeptId) + + # 3- Creation instance ScoDoc + add_method = DeptFolder.manage_addProduct["ScoDoc"].manage_addZScolarForm + return add_method(DeptId, REQUEST=REQUEST) + + security.declareProtected("View", "delete_dept") + + def delete_dept(self, REQUEST=None, DeptId="", force=False): + """Supprime un departement (de Zope seulement, ne touche pas la BD) + """ + e = self._check_admin_perm(REQUEST) + if e: + return e + + if not force and DeptId not in [x.id for x in self.list_depts()]: + raise ValueError("nom de departement invalide") + + self.manage_delObjects(ids=[DeptId]) + + return ( + "

    Département " + + DeptId + + """ supprimé du serveur web (la base de données n'est pas affectée)!

    Continuer

    """ + % REQUEST.URL1 + ) + + _top_level_css = """ + """ + + _html_begin = """ + + + +%(page_title)s + + + + + + + + + + + + + + + + + + + + + + + + + +""" + + def scodoc_top_html_header(self, REQUEST, page_title="ScoDoc"): + H = [ + self._html_begin + % {"page_title": "ScoDoc: bienvenue", "encoding": SCO_ENCODING}, + self._top_level_css, + """""", + CUSTOM_HTML_HEADER_CNX, + ] + return "\n".join(H) + + security.declareProtected("View", "index_html") + + def index_html(self, REQUEST=None, message=None): + """Top level page for ScoDoc + """ + authuser = REQUEST.AUTHENTICATED_USER + deptList = self.list_depts() + self._fix_users_folder() # fix our exUserFolder + isAdmin = not self._check_admin_perm(REQUEST) + try: + admin_password_initialized = self.admin_password_initialized + except: + admin_password_initialized = "0" + if isAdmin and admin_password_initialized != "1": + REQUEST.RESPONSE.redirect( + "ScoDoc/change_admin_user_form?message=Le%20mot%20de%20passe%20administrateur%20doit%20etre%20change%20!" + ) + + # Si l'URL indique que l'on est dans un folder, affiche page login du departement + try: + deptfoldername = REQUEST.URL0.split("ScoDoc")[1].split("/")[1] + if deptfoldername in [x.id for x in self.list_depts()]: + return self.index_dept(deptfoldername=deptfoldername, REQUEST=REQUEST) + except: + pass + + H = [ + self.scodoc_top_html_header(REQUEST, page_title="ScoDoc: bienvenue"), + self._check_users_folder(REQUEST=REQUEST), # ensure setup is done + ] + if message: + H.append('
    %s
    ' % message) + + if isAdmin and not message: + H.append('
    Attention: connecté comme administrateur
    ') + + H.append( + """ +
    +

    ScoDoc: gestion scolarité

    + """ + ) + if authuser.has_role("Authenticated"): + H.append( + """

    Bonjour %s.

    """ % str(authuser) + ) + H.append( + """

    N'oubliez pas de vous déconnecter après usage.

    """ + ) + else: + H.append( + """

    Ce site est réservé au personnel autorisé

    """ + ) + H.append(self.authentication_form(destination=".")) + + if not deptList: + H.append("aucun département existant !") + # si pas de dept et pas admin, propose lien pour loger admin + if not isAdmin: + H.append( + """

    Identifiez vous comme administrateur (au début: nom 'admin', mot de passe 'scodoc')

    """ + ) + else: + H.append('
      ') + if isAdmin: + dest_folder = "/Scolarite" + else: + dest_folder = "" + for deptFolder in self.list_depts(): + if authuser.has_permission(ScoView, deptFolder.Scolarite): + link_cls = "link_accessible" + else: + link_cls = "link_unauthorized" + # Essai de recuperer le nom du departement dans ses preferences + try: + DeptName = ( + deptFolder.Scolarite.get_preference("DeptName") or deptFolder.id + ) + except: + DeptName = deptFolder.id + H.append( + '
    • Département %s' + % (link_cls, deptFolder.absolute_url(), dest_folder, DeptName) + ) + # check if roles are initialized in this depts, and do it if necessary + if deptFolder.Scolarite.roles_initialized == "0": + if isAdmin: + deptFolder.Scolarite._setup_initial_roles_and_permissions() + else: + H.append(" (non initialisé, connectez vous comme admin)") + H.append("
    • ") + H.append("
    ") + # Recherche etudiant + H.append(sco_find_etud.form_search_etud_in_accessible_depts(self, REQUEST)) + + if isAdmin: + H.append('

    Administration de ScoDoc

    ') + else: + H.append( + '

    Se connecter comme administrateur

    ' + % REQUEST.BASE0 + ) + + H.append( + """ +
    +

    ScoDoc est un logiciel libre de suivi de la scolarité des étudiants conçu par +E. Viennet (Université Paris 13).

    +
    +
    """ + % (SCO_WEBSITE,) + ) + + H.append("""""") + return "\n".join(H) + + def authentication_form(self, destination=""): + """html snippet for authentication""" + return ( + """ + + +

    + + + + + + + + + +
    Nom:
    Mot de passe:
    +

    +
    """ + % destination + ) + + security.declareProtected("View", "index_dept") + + def index_dept(self, deptfoldername="", REQUEST=None): + """Page d'accueil departement""" + authuser = REQUEST.AUTHENTICATED_USER + try: + dept = getattr(self, deptfoldername) + if authuser.has_permission(ScoView, dept): + return REQUEST.RESPONSE.redirect("ScoDoc/%s/Scolarite" % deptfoldername) + except: + log( + "*** problem in index_dept (%s) user=%s" + % (deptfoldername, str(authuser)) + ) + + H = [ + self.standard_html_header(REQUEST), + """
    +

    Scolarité du département %s

    """ + % deptfoldername, + """

    Ce site est +réservé au personnel du département. +

    """, + self.authentication_form(destination="Scolarite"), + """ +

    Pour quitter, logout

    +

    Retour à l'accueil

    +
    +""" + % self.ScoDocURL(), + self.standard_html_footer(REQUEST), + ] + return "\n".join(H) + + security.declareProtected("View", "doLogin") + + def doLogin(self, REQUEST=None, destination=None): + "redirect to destination after login" + if destination: + return REQUEST.RESPONSE.redirect(destination) + + security.declareProtected("View", "docLogin") + docLogin = DTMLFile("dtml/docLogin", globals()) + security.declareProtected("View", "docLogout") + docLogout = DTMLFile("dtml/docLogout", globals()) + + security.declareProtected("View", "query_string_to_form_inputs") + + def query_string_to_form_inputs(self, query_string=""): + """Return html snippet representing the query string as POST form hidden inputs. + This is useful in conjonction with exUserfolder to correctly redirect the response + after authentication. + """ + H = [] + for a in query_string.split("&"): + if a: + nv = a.split("=") + if len(nv) == 2: + name, value = nv + H.append( + '' + ) + + return "\n" + "\n".join(H) + + security.declareProtected("View", "standard_html_header") + + def standard_html_header(self, REQUEST=None): + """Standard HTML header for pages outside depts""" + # not used in ZScolar, see sco_header + return """ + +ScoDoc: accueil + + + + + + + +%s""" % ( + SCO_ENCODING, + CUSTOM_HTML_HEADER_CNX, + ) + + security.declareProtected("View", "standard_html_footer") + + def standard_html_footer(self, REQUEST=None): + return """ +

    Problèmes et suggestions sur le logiciel: %s

    +

    ScoDoc est un logiciel libre développé par Emmanuel Viennet.

    +""" % ( + SCO_USERS_LIST, + SCO_USERS_LIST, + ) + + # sendEmail is not used through the web + def sendEmail(self, msg): + # sends an email to the address using the mailhost, if there is one + try: + mail_host = self.MailHost + except: + log("warning: sendEmail: no MailHost found !") + return + # a failed notification shouldn't cause a Zope error on a site. + try: + mail_host.send(msg.as_string()) + log("sendEmail: ok") + except Exception as e: + log("sendEmail: exception while sending message") + log(e) + pass + + def sendEmailFromException(self, msg): + # Send email by hand, as it seems to be not possible to use Zope Mail Host + # from an exception handler (see https://bugs.launchpad.net/zope2/+bug/246748) + log("sendEmailFromException") + try: + p = os.popen("sendmail -t", "w") # old brute force method + p.write(msg.as_string()) + exitcode = p.close() + if exitcode: + log("sendmail exit code: %s" % exitcode) + except: + log("an exception occurred sending mail") + + security.declareProtected("View", "standard_error_message") + + def standard_error_message( + self, + error_value=None, + error_message=None, + error_type=None, + error_traceback=None, + error_tb=None, + **kv + ): + "Recuperation des exceptions Zope" + sco_exc_mail = SCO_EXC_MAIL + sco_dev_mail = SCO_DEV_MAIL + # neat (or should I say dirty ?) hack to get REQUEST + # in fact, our caller (probably SimpleItem.py) has the REQUEST variable + # that we'd like to use for our logs, but does not pass it as an argument. + try: + frame = inspect.currentframe() + REQUEST = frame.f_back.f_locals["REQUEST"] + except: + REQUEST = {} + + # Authentication uses exceptions, pass them up + HTTP_X_FORWARDED_FOR = REQUEST.get("HTTP_X_FORWARDED_FOR", "") + if error_type == "LoginRequired": + # raise 'LoginRequired', '' # copied from exuserFolder (beurk, old style exception...) + # if REQUEST: + # REQUEST.response.setStatus( 401, "Unauthorized") # ?????? + log("LoginRequired from %s" % HTTP_X_FORWARDED_FOR) + self.login_page = error_value + return error_value + elif error_type == "Unauthorized": + log("Unauthorized from %s" % HTTP_X_FORWARDED_FOR) + return self.acl_users.docLogin(self, REQUEST=REQUEST) + + log("exception caught: %s" % error_type) + log(traceback.format_exc()) + + if error_type == "ScoGenError": + return "

    " + str(error_value) + "

    " + elif error_type in ("ScoValueError", "FormatError"): + # Not a bug, presents a gentle message to the user: + H = [ + self.standard_html_header(REQUEST), + """

    Erreur !

    %s

    """ % error_value, + ] + if error_value.dest_url: + H.append('

    Continuer

    ' % error_value.dest_url) + H.append(self.standard_html_footer(REQUEST)) + return "\n".join(H) + else: # Other exceptions, try carefully to build an error page... + # log('exc A') + H = [] + try: + H.append(self.standard_html_header(REQUEST)) + except: + pass + H.append( + """ + + +

    Erreur !

    +

    Une erreur est survenue

    +

    + Error Type: %(error_type)s
    + Error Value: %(error_value)s
    +

    +
    +

    L'URL est peut-etre incorrecte ?

    + +

    Si l'erreur persiste, contactez Emmanuel Viennet: + %(sco_dev_mail)s + en copiant ce message d'erreur et le contenu du cadre bleu ci-dessous si possible. +

    +
    """ + % vars() + ) + # display error traceback (? may open a security risk via xss attack ?) + # log('exc B') + txt_html = self._report_request(REQUEST, fmt="html") + H.append( + """

    Zope Traceback (à envoyer par mail à %(sco_dev_mail)s)

    +%(error_tb)s +

    Informations:
    +%(txt_html)s +

    +
    + +

    Merci de votre patience !

    +""" + % vars() + ) + try: + H.append(self.standard_html_footer(REQUEST)) + except: + log("no footer found for error page") + pass + + # --- Mail: + error_traceback_txt = scodoc_html2txt(error_tb) + txt = ( + """ +ErrorType: %(error_type)s + +%(error_traceback_txt)s +""" + % vars() + ) + + self.send_debug_alert(txt, REQUEST=REQUEST) + # --- + log("done processing exception") + # log( '\n page=\n' + '\n'.join(H) ) + return "\n".join(H) + + def _report_request(self, REQUEST, fmt="txt"): + """string describing current request for bug reports""" + AUTHENTICATED_USER = REQUEST.get("AUTHENTICATED_USER", "") + dt = time.asctime() + URL = REQUEST.get("URL", "") + QUERY_STRING = REQUEST.get("QUERY_STRING", "") + if QUERY_STRING: + QUERY_STRING = "?" + QUERY_STRING + METHOD = REQUEST.get("REQUEST_METHOD", "") + + if fmt == "txt": + REFERER = REQUEST.get("HTTP_REFERER", "") + HTTP_USER_AGENT = REQUEST.get("HTTP_USER_AGENT", "") + else: + REFERER = "na" + HTTP_USER_AGENT = "na" + form = REQUEST.get("form", "") + HTTP_X_FORWARDED_FOR = REQUEST.get("HTTP_X_FORWARDED_FOR", "") + svn_version = get_svn_version(self.file_path) + SCOVERSION = VERSION.SCOVERSION + + txt = ( + """ +Version: %(SCOVERSION)s +User: %(AUTHENTICATED_USER)s +Date: %(dt)s +URL: %(URL)s%(QUERY_STRING)s +Method: %(METHOD)s + +REFERER: %(REFERER)s +Form: %(form)s +Origin: %(HTTP_X_FORWARDED_FOR)s +Agent: %(HTTP_USER_AGENT)s + +subversion: %(svn_version)s +""" + % vars() + ) + if fmt == "html": + txt = txt.replace("\n", "
    ") + return txt + + security.declareProtected( + ScoSuperAdmin, "send_debug_alert" + ) # not called through the web + + def send_debug_alert(self, txt, REQUEST=None): + """Send an alert email (bug report) to ScoDoc developpers""" + if not SCO_EXC_MAIL: + log("send_debug_alert: email disabled") + return + if REQUEST: + txt = self._report_request(REQUEST) + txt + URL = REQUEST.get("URL", "") + else: + URL = "send_debug_alert" + msg = MIMEMultipart() + subj = Header("[scodoc] exc %s" % URL, SCO_ENCODING) + msg["Subject"] = subj + recipients = [SCO_EXC_MAIL] + msg["To"] = " ,".join(recipients) + msg["From"] = "scodoc-alert" + msg.epilogue = "" + msg.attach(MIMEText(txt, "plain", SCO_ENCODING)) + self.sendEmailFromException(msg) + log("Sent mail alert:\n" + txt) + + security.declareProtected("View", "scodoc_admin") + + def scodoc_admin(self, REQUEST=None): + """Page Operations d'administration + """ + e = self._check_admin_perm(REQUEST) + if e: + return e + + H = [ + self.scodoc_top_html_header(REQUEST, page_title="ScoDoc: bienvenue"), + """ +

    Administration ScoDoc

    + +

    changer le mot de passe super-administrateur

    +

    retour à la page d'accueil

    + +

    Création d'un département

    +

    Le département doit avoir été créé au préalable sur le serveur en utilisant le script +create_dept.sh (à lancer comme root dans le répertoire config de ScoDoc). +

    """ + % self.absolute_url(), + ] + + deptList = [x.id for x in self.list_depts()] # definis dans Zope + deptIds = Set(self._list_depts_ids()) # definis sur le filesystem + existingDepts = Set(deptList) + addableDepts = deptIds - existingDepts + + if not addableDepts: + # aucun departement defini: aide utilisateur + H.append("

    Aucun département à ajouter !

    ") + else: + H.append("""
    + +
    """ + ) + + if deptList: + H.append( + """ +

    Suppression d'un département

    +

    Ceci permet de supprimer le site web associé à un département, mais n'affecte pas la base de données +(le site peut donc être recréé sans perte de données). +

    +
    + + + +
    """ + ) + + H.append("""""") + return "\n".join(H) + + def _list_depts_ids(self): + """Liste de id de departements definis par create_dept.sh + (fichiers depts/*.cfg) + """ + filenames = glob.glob(SCODOC_VAR_DIR + "/config/depts/*.cfg") + ids = [os.path.split(os.path.splitext(f)[0])[1] for f in filenames] + return ids + + security.declareProtected("View", "http_expiration_date") + + def http_expiration_date(self): + "http expiration date for cachable elements (css, ...)" + d = datetime.timedelta(minutes=10) + return (datetime.datetime.utcnow() + d).strftime("%a, %d %b %Y %H:%M:%S GMT") + + security.declareProtected("View", "get_etud_dept") + + def get_etud_dept(self, REQUEST=None): + """Returns the dept id (eg "GEII") of an etud (identified by etudid, INE or NIP in REQUEST). + Warning: This function is inefficient and its result should be cached. + """ + depts = self.list_depts() + depts_etud = [] # liste des depts où l'etud est defini + for dept in depts: + etuds = dept.Scolarite.getEtudInfo(REQUEST=REQUEST) + if etuds: + depts_etud.append((dept, etuds)) + if not depts_etud: + return "" # not found + elif len(depts_etud) == 1: + return depts_etud[0][0].id + # inscriptions dans plusieurs departements: cherche la plus recente + last_dept = None + last_date = None + for (dept, etuds) in depts_etud: + dept.Scolarite.fillEtudsInfo(etuds) + etud = etuds[0] + if etud["sems"]: + if (not last_date) or (etud["sems"][0]["date_fin_iso"] > last_date): + last_date = etud["sems"][0]["date_fin_iso"] + last_dept = dept + if not last_dept: + # est present dans plusieurs semestres mais inscrit dans aucun + return depts_etud[0][0] + return last_dept.id + + security.declareProtected("View", "table_etud_in_accessible_depts") + table_etud_in_accessible_depts = sco_find_etud.table_etud_in_accessible_depts + + security.declareProtected("View", "search_inscr_etud_by_nip") + search_inscr_etud_by_nip = sco_find_etud.search_inscr_etud_by_nip + + +def manage_addZScoDoc(self, id="ScoDoc", title="Site ScoDoc", REQUEST=None): + "Add a ZScoDoc instance to a folder." + log("============== creating a new ScoDoc instance =============") + zscodoc = ZScoDoc( + id, title + ) # ne cree (presque rien), tout se passe lors du 1er accès + self._setObject(id, zscodoc) + if REQUEST is not None: + REQUEST.RESPONSE.redirect("%s/manage_workspace" % REQUEST.URL1) + return id diff --git a/ZScoUsers.py b/ZScoUsers.py new file mode 100644 index 00000000..f270d742 --- /dev/null +++ b/ZScoUsers.py @@ -0,0 +1,1334 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +""" Gestion des utilisateurs (table SQL pour Zope User Folder) +""" +import string, re +import time +import md5, base64 + + +from sco_zope import * + +# --------------- + +import notesdb +from notesdb import * +from notes_log import log +from scolog import logdb +from sco_utils import * +from scolars import format_prenom, format_nom +import sco_import_users, sco_excel +from TrivialFormulator import TrivialFormulator, TF +from gen_tables import GenTable +import scolars +import sco_cache + +# ----------------- password checking +import cracklib + + +def pwdFascistCheck(cleartxt): + "returns None if OK" + if ( + hasattr(CONFIG, "MIN_PASSWORD_LENGTH") + and CONFIG.MIN_PASSWORD_LENGTH > 0 + and len(cleartxt) < CONFIG.MIN_PASSWORD_LENGTH + ): + return True # invalid + try: + x = cracklib.FascistCheck(cleartxt) + return None + except ValueError as e: + return str(e) + + +# --------------- +# cache global: chaque instance, repérée par son URL, a un cache +# qui est recréé à la demande +# On cache ici la liste des utilisateurs, pour une duree limitee +# (une minute). + +CACHE_userlist = {} + +# --------------- + + +class ZScoUsers( + ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit +): + + "ZScousers object" + + meta_type = "ZScoUsers" + security = ClassSecurityInfo() + + # This is the list of the methods associated to 'tabs' in the ZMI + # Be aware that The first in the list is the one shown by default, so if + # the 'View' tab is the first, you will never see your tabs by cliquing + # on the object. + manage_options = ( + ({"label": "Contents", "action": "manage_main"},) + + PropertyManager.manage_options # add the 'Properties' tab + + ({"label": "View", "action": "index_html"},) + + Item.manage_options # add the 'Undo' & 'Owner' tab + + RoleManager.manage_options # add the 'Security' tab + ) + + # no permissions, only called from python + def __init__(self, id, title): + "initialise a new instance" + self.id = id + self.title = title + + # The form used to edit this object + def manage_editZScousers(self, title, RESPONSE=None): + "Changes the instance values" + self.title = title + self._p_changed = 1 + RESPONSE.redirect("manage_editForm") + + # Ajout (dans l'instance) d'un dtml modifiable par Zope + def defaultDocFile(self, id, title, file): + f = open(file_path + "/dtml-editable/" + file + ".dtml") + file = f.read() + f.close() + self.manage_addDTMLMethod(id, title, file) + + # Connexion to SQL database of users: + + # Ugly but necessary during transition out of Zope: + _db_cnx_string = "dbname=SCOUSERS port=5432" + security.declareProtected("Change DTML Documents", "GetUsersDBConnexion") + GetUsersDBConnexion = notesdb.GetUsersDBConnexion + + # -------------------------------------------------------------------- + # + # Users (top level) + # + # -------------------------------------------------------------------- + # used to view content of the object + security.declareProtected(ScoUsersView, "index_html") + + def index_html(self, REQUEST, all=0, with_olds=0, format="html"): + "gestion utilisateurs..." + all = int(all) + with_olds = int(with_olds) + # Controle d'acces + authuser = REQUEST.AUTHENTICATED_USER + user_name = str(authuser) + # log('user: %s roles: %s'%(user_name,authuser.getRolesInContext(self))) + user = self._user_list(args={"user_name": user_name}) + if not user: + zope_roles = authuser.getRolesInContext(self) + if ("Manager" in zope_roles) or ("manage" in zope_roles): + dept = "" # special case for zope admin + else: + raise AccessDenied("Vous n'avez pas la permission de voir cette page") + else: + dept = user[0]["dept"] + + H = [self.sco_header(REQUEST, page_title="Gestion des utilisateurs")] + H.append("

    Gestion des utilisateurs

    ") + + if authuser.has_permission(ScoUsersAdmin, self): + H.append( + '

    Ajouter un utilisateur' + ) + H.append( + '   Importer des utilisateurs

    ' + ) + if all: + checked = "checked" + else: + checked = "" + if with_olds: + olds_checked = "checked" + else: + olds_checked = "" + H.append( + """

    + Tous les départements + Avec anciens utilisateurs +

    """ + % (REQUEST.URL0, checked, olds_checked) + ) + + L = self.list_users( + dept, + all=all, + with_olds=with_olds, + format=format, + REQUEST=REQUEST, + with_links=authuser.has_permission(ScoUsersAdmin, self), + ) + if format != "html": + return L + H.append(L) + + F = self.sco_footer(REQUEST) + return "\n".join(H) + F + + _userEditor = EditableTable( + "sco_users", + "user_id", + ( + "user_id", + "user_name", + "passwd", + "roles", + "date_modif_passwd", + "nom", + "prenom", + "email", + "dept", + "passwd_temp", + "status", + "date_expiration", + ), + output_formators={ + "date_modif_passwd": DateISOtoDMY, + "date_expiration": DateISOtoDMY, + }, + input_formators={ + "date_modif_passwd": DateDMYtoISO, + "date_expiration": DateDMYtoISO, + }, + sortkey="nom", + filter_nulls=False, + ) + + def _user_list(self, **kw): + # list info sur utilisateur(s) + cnx = self.GetUsersDBConnexion() + users = self._userEditor.list(cnx, **kw) + for u in users: + if u["status"] == "old": + u["status_txt"] = "(ancien)" + else: + u["status_txt"] = "" + + return users + + def _user_edit(self, user_name, vals): + # edit user + cnx = self.GetUsersDBConnexion() + vals["user_name"] = user_name + self._userEditor.edit(cnx, vals) + self.get_userlist_cache().inval_cache() # > + self.acl_users.cache_removeUser(user_name) # exUserFolder's caches + self.acl_users.xcache_removeUser(user_name) + # Ensure that if status is "old", login is disabled + # note that operation is reversible without having to re-enter a password + # We change the roles (to avoid dealing with passwd hash, controled by exUserFolder) + u = self._user_list(args={"user_name": user_name})[0] + if u["status"] == "old" and u["roles"] and u["roles"][0] != "-": + roles = ["-" + r for r in u["roles"].split(",")] + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + self.acl_users.scodoc_editUser(cursor, user_name, roles=roles) + self.get_userlist_cache().inval_cache() + elif not u["status"] and u["roles"] and u["roles"][0] == "-": + roles = [r[1:] for r in u["roles"].split(",") if (r and r[0] == "-")] + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + self.acl_users.scodoc_editUser(cursor, user_name, roles=roles) + self.get_userlist_cache().inval_cache() + + def _user_delete(self, user_name): + # delete user + cnx = self.GetUsersDBConnexion() + user_id = self._user_list(args={"user_name": user_name})[0]["user_id"] + self._userEditor.delete(cnx, user_id) + self.get_userlist_cache().inval_cache() # > + + def _all_roles(self): + "ensemble de tous les roles attribués ou attribuables" + roles = set(self.DeptUsersRoles()) + cnx = self.GetUsersDBConnexion() + L = self._userEditor.list(cnx, {}) + for l in L: + roles.update([x.strip() for x in l["roles"].split(",")]) + return [r for r in roles if r and r[0] != "-"] + + security.declareProtected(ScoUsersAdmin, "user_info") + + def user_info(self, user_name=None, user=None): + """Donne infos sur l'utilisateur (qui peut ne pas etre dans notre base). + Si user_name est specifie, interroge la BD. Sinon, user doit etre un dict. + """ + if user_name: + infos = self._user_list(args={"user_name": user_name}) + else: + infos = [user.copy()] + user_name = user["user_name"] + + if not infos: + # special case: user is not in our database + return { + "user_name": user_name, + "nom": user_name, + "prenom": "", + "email": "", + "dept": "", + "nomprenom": user_name, + "prenomnom": user_name, + "prenom_fmt": "", + "nom_fmt": user_name, + "nomcomplet": user_name, + "nomplogin": user_name, + "nomnoacc": suppress_accents(user_name), + "passwd_temp": 0, + "status": "", + "date_expiration": None, + } + else: + info = infos[0] + # always conceal password ! + del info["passwd"] # always conceal password ! + # + if info["prenom"]: + p = format_prenom(info["prenom"]) + else: + p = "" + if info["nom"]: + n = format_nom( + info["nom"], uppercase=False + ) # strcapitalize(strlower(info['nom'])) + else: + n = user_name + + prenom_abbrv = abbrev_prenom(p) + # nomprenom est le nom capitalisé suivi de l'initiale du prénom + info["nomprenom"] = (n + " " + prenom_abbrv).strip() + # prenomnom est l'initiale du prénom suivie du nom + info["prenomnom"] = (prenom_abbrv + " " + n).strip() + # nom_fmt et prenom_fmt: minuscule capitalisé + info["nom_fmt"] = n + info["prenom_fmt"] = scolars.format_prenom(p) + # nomcomplet est le prenom et le nom complets + info["nomcomplet"] = info["prenom_fmt"] + " " + info["nom_fmt"] + # nomplogin est le nom en majuscules suivi du prénom et du login + # e.g. Dupont Pierre (dupont) + info["nomplogin"] = "%s %s (%s)" % (strupper(n), p, info["user_name"]) + # nomnoacc est le nom en minuscules sans accents + info["nomnoacc"] = suppress_accents(strlower(info["nom"])) + + return info + + def _can_handle_passwd(self, authuser, user_name, allow_admindepts=False): + """true if authuser can see or change passwd of user_name. + If allow_admindepts, allow Admin from all depts (so they can view users from other depts + and add roles to them). + authuser is a Zope user object. user_name is a string. + """ + # Is authuser a zope admin ? + zope_roles = authuser.getRolesInContext(self) + if ("Manager" in zope_roles) or ("manage" in zope_roles): + return True + # Anyone can change its own passwd (or see its informations) + if str(authuser) == user_name: + return True + # has permission ? + if not authuser.has_permission(ScoUsersAdmin, self): + return False + # Ok, now check that authuser can manage users from this departement + # Get user info + user = self._user_list(args={"user_name": user_name}) + if not user: + return False # we don't have infos on this user ! + # Get authuser info + auth_name = str(authuser) + authuser_info = self._user_list(args={"user_name": auth_name}) + if not authuser_info: + return False # not admin, and not in out database + auth_dept = authuser_info[0]["dept"] + if not auth_dept: + return True # if no dept, can access users from all depts ! + if auth_dept == user[0]["dept"] or allow_admindepts: + return True + else: + return False + + def _is_valid_passwd(self, passwd): + "check if passwd is secure enough" + return not pwdFascistCheck(passwd) + + def do_change_password(self, user_name, password): + user = self._user_list(args={"user_name": user_name}) + assert len(user) == 1, "database inconsistency: len(r)=%d" % len(r) + # should not occur, already tested in _can_handle_passwd + cnx = self.GetUsersDBConnexion() # en mode autocommit + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "update sco_users set date_modif_passwd=now(), passwd_temp=0 where user_name=%(user_name)s", + {"user_name": user_name}, + ) + + # Laisse le exUserFolder modifier les donnees: + self.acl_users.scodoc_editUser( + cursor, user_name, password=password, roles=[user[0]["roles"]] + ) + + log("change_password: change ok for %s" % user_name) + self.get_userlist_cache().inval_cache() # > + + security.declareProtected(ScoView, "change_password") + + def change_password(self, user_name, password, password2, REQUEST): + "change a password" + # ScoUsersAdmin: modif tous les passwd de SON DEPARTEMENT + # sauf si pas de dept (admin global) + H = [] + F = self.sco_footer(REQUEST) + # Check access permission + if not self._can_handle_passwd(REQUEST.AUTHENTICATED_USER, user_name): + # access denied + log( + "change_password: access denied (authuser=%s, user_name=%s, ip=%s)" + % (authuser, user_name, REQUEST.REMOTE_ADDR) + ) + raise AccessDenied( + "vous n'avez pas la permission de changer ce mot de passe" + ) + # check password + if password != password2: + H.append( + """

    Les deux mots de passes saisis sont différents !

    +

    Recommencer

    """ + % user_name + ) + else: + if not self._is_valid_passwd(password): + H.append( + """

    ce mot de passe n\'est pas assez compliqué !
    (oui, il faut un mot de passe vraiment compliqué !)

    +

    Recommencer

    + """ + % user_name + ) + else: + # ok, strong password + # MD5 hash (now computed by exUserFolder) + # digest = md5.new() + # digest.update(password) + # digest = digest.digest() + # md5pwd = string.strip(base64.encodestring(digest)) + # + self.do_change_password(user_name, password) + # + # ici page simplifiee car on peut ne plus avoir + # le droit d'acceder aux feuilles de style + H.append( + "

    Changement effectué !

    Ne notez pas ce mot de passe, mais mémorisez le !

    Rappel: il est interdit de communiquer son mot de passe à un tiers, même si c'est un collègue de confiance !

    Si vous n'êtes pas administrateur, le système va vous redemander votre login et nouveau mot de passe au prochain accès.

    " + ) + return ( + """ + + + +Mot de passe changé + +

    Mot de passe changé !

    +""" + % (SCO_ENCODING, SCO_ENCODING) + + "\n".join(H) + + 'Continuer' + % self.ScoURL() + ) + return self.sco_header(REQUEST) + "\n".join(H) + F + + security.declareProtected(ScoView, "form_change_password") + + def form_change_password(self, REQUEST, user_name=None): + """Formulaire changement mot de passe + Un utilisateur peut toujours changer son mot de passe""" + authuser = REQUEST.AUTHENTICATED_USER + if not user_name: + user_name = str(authuser) + H = [self.sco_header(REQUEST, user_check=False)] + F = self.sco_footer(REQUEST) + # check access + if not self._can_handle_passwd(authuser, user_name): + return ( + "\n".join(H) + + "

    Vous n'avez pas la permission de changer ce mot de passe

    " + + F + ) + # + H.append( + """

    Changement du mot de passe de %(user_name)s

    +

    +

    + + +
    Nouveau mot de passe:
    Confirmation:
    + + +

    +

    Vous pouvez aussi: renvoyer un mot de passe aléatoire temporaire par mail à l'utilisateur +""" + % {"user_name": user_name, "url": REQUEST.URL0} + ) + return "\n".join(H) + F + + security.declareProtected(ScoView, "userinfo") + + def userinfo(self, user_name=None, REQUEST=None): + "display page of info about connected user" + authuser = REQUEST.AUTHENTICATED_USER + if not user_name: + user_name = str(authuser) + # peut on divulguer ces infos ? + if not self._can_handle_passwd( + REQUEST.AUTHENTICATED_USER, user_name, allow_admindepts=True + ): + raise AccessDenied("Vous n'avez pas la permission de voir cette page") + H = [self.sco_header(REQUEST, page_title="Utilisateur %s" % user_name)] + F = self.sco_footer(REQUEST) + H.append("

    Utilisateur: %s" % user_name) + info = self._user_list(args={"user_name": user_name}) + if info: + H.append("%(status_txt)s" % info[0]) + H.append("

    ") + if not info: + H.append( + "

    L' utilisateur '%s' n'est pas défini dans ce module.

    " + % user_name + ) + if authuser.has_permission(ScoEditAllNotes, self): + H.append("

    (il peut modifier toutes les notes)

    ") + if authuser.has_permission(ScoEditAllEvals, self): + H.append("

    (il peut modifier toutes les évaluations)

    ") + if authuser.has_permission(ScoImplement, self): + H.append("

    (il peut creer des formations)

    ") + else: + H.append( + """

    + Login : %(user_name)s
    + Nom : %(nom)s
    + Prénom : %(prenom)s
    + Mail : %(email)s
    + Roles : %(roles)s
    + Dept : %(dept)s
    + Dernière modif mot de passe: %(date_modif_passwd)s
    + Date d'expiration: %(date_expiration)s +

    ") + + if str(authuser) == user_name: + H.append( + '

    Se déconnecter: logout

    ' + ) + # Liste des permissions + H.append( + '

    Permission de cet utilisateur:

      ' + ) + permissions = self.ac_inherited_permissions(1) + scoperms = [p for p in permissions if p[0][:3] == "Sco"] + try: + thisuser = self.acl_users.getUser(user_name) + except: + # expired from cache ? retry... + thisuser = self.acl_users.getUser(user_name) + if not thisuser: + # Cas de figure incompris ? (bug IUT Amiens janvier 2014: login avec accent ?) + H.append( + "
    • impossible de retrouver les permissions de l'utilisateur (contacter l'administrateur)
    • " + ) + else: + for p in scoperms: + permname, value = p[:2] + if thisuser.has_permission(permname, self): + b = "oui" + else: + b = "non" + H.append("
    • %s : %s
    • " % (permname, b)) + H.append("
    ") + if authuser.has_permission(ScoUsersAdmin, self): + H.append( + '

    Liste de tous les utilisateurs

    ' + % self.ScoURL() + ) + return "\n".join(H) + F + + security.declareProtected(ScoUsersAdmin, "create_user_form") + + def create_user_form(self, REQUEST, user_name=None, edit=0): + "form. creation ou edit utilisateur" + # Get authuser info + authuser = REQUEST.AUTHENTICATED_USER + auth_name = str(authuser) + authuser_info = self._user_list(args={"user_name": auth_name}) + initvalues = {} + + # Access control + zope_roles = authuser.getRolesInContext(self) + if ( + not authuser_info + and not ("Manager" in zope_roles) + and not ("manage" in zope_roles) + ): + # not admin, and not in database + raise AccessDenied("invalid user (%s)" % auth_name) + if authuser_info: + auth_dept = authuser_info[0]["dept"] + else: + auth_dept = "" + # + edit = int(edit) + H = [self.sco_header(REQUEST, bodyOnLoad="init_tf_form('')")] + F = self.sco_footer(REQUEST) + if edit: + if not user_name: + raise ValueError("missing argument: user_name") + initvalues = self._user_list(args={"user_name": user_name})[0] + H.append("

    Modification de l'utilisateur %s

    " % user_name) + else: + H.append("

    Création d'un utilisateur

    ") + + if authuser.has_permission(ScoSuperAdmin, self): + H.append("""

    Vous êtes super administrateur !

    """) + + # Noms de roles pouvant etre attribues aux utilisateurs via ce dialogue + # si pas SuperAdmin, restreint aux rôles EnsX, SecrX, DeptX + # + if authuser.has_permission(ScoSuperAdmin, self): + log("create_user_form called by %s (super admin)" % (auth_name,)) + editable_roles = set(self._all_roles()) + else: + editable_roles = set(self.DeptUsersRoles()) + # log('create_user_form: editable_roles=%s' % editable_roles) + # + if not edit: + submitlabel = "Créer utilisateur" + orig_roles = set() + else: + submitlabel = "Modifier utilisateur" + initvalues["roles"] = initvalues["roles"].split(",") or [] + orig_roles = set(initvalues["roles"]) + if initvalues["status"] == "old": + editable_roles = set() # can't change roles of a disabled user + # add existing user roles + displayed_roles = list(editable_roles.union(orig_roles)) + displayed_roles.sort() + disabled_roles = {} # pour desactiver les roles que l'on ne peut pas editer + for i in range(len(displayed_roles)): + if displayed_roles[i] not in editable_roles: + disabled_roles[i] = True + + # log('create_user_form: displayed_roles=%s' % displayed_roles) + + descr = [ + ("edit", {"input_type": "hidden", "default": edit}), + ("nom", {"title": "Nom", "size": 20, "allow_null": False}), + ("prenom", {"title": "Prénom", "size": 20, "allow_null": False}), + ] + if auth_name != user_name: # no one can't change its own status + descr.append( + ( + "status", + { + "title": "Statut", + "input_type": "radio", + "labels": ("actif", "ancien"), + "allowed_values": ("", "old"), + }, + ) + ) + if not edit: + descr += [ + ( + "user_name", + { + "title": "Pseudo (login)", + "size": 20, + "allow_null": False, + "explanation": "nom utilisé pour la connexion. Doit être unique parmi tous les utilisateurs.", + }, + ), + ( + "passwd", + { + "title": "Mot de passe", + "input_type": "password", + "size": 14, + "allow_null": False, + }, + ), + ( + "passwd2", + { + "title": "Confirmer mot de passe", + "input_type": "password", + "size": 14, + "allow_null": False, + }, + ), + ] + else: + descr += [ + ( + "user_name", + {"input_type": "hidden", "default": initvalues["user_name"]}, + ), + ("user_id", {"input_type": "hidden", "default": initvalues["user_id"]}), + ] + descr += [ + ( + "email", + { + "title": "e-mail", + "input_type": "text", + "explanation": "vivement recommandé: utilisé pour contacter l'utilisateur", + "size": 20, + "allow_null": True, + }, + ) + ] + + if not auth_dept: + # si auth n'a pas de departement (admin global) + # propose de choisir le dept du nouvel utilisateur + # sinon, il sera créé dans le même département que auth + descr.append( + ( + "dept", + { + "title": "Département", + "input_type": "text", + "size": 12, + "allow_null": True, + "explanation": """département d\'appartenance de l\'utilisateur (s'il s'agit d'un administrateur, laisser vide si vous voulez qu'il puisse créer des utilisateurs dans d'autres départements)""", + }, + ) + ) + can_choose_dept = True + else: + can_choose_dept = False + if edit: + descr.append( + ( + "d", + { + "input_type": "separator", + "title": "L'utilisateur appartient au département %s" + % auth_dept, + }, + ) + ) + else: + descr.append( + ( + "d", + { + "input_type": "separator", + "title": "L'utilisateur sera crée dans le département %s" + % auth_dept, + }, + ) + ) + + descr += [ + ( + "date_expiration", + { + "title": "Date d'expiration", # j/m/a + "input_type": "date", + "explanation": "j/m/a, laisser vide si pas de limite", + "size": 9, + "allow_null": True, + }, + ), + ( + "roles", + { + "title": "Rôles", + "input_type": "checkbox", + "vertical": True, + "allowed_values": displayed_roles, + "disabled_items": disabled_roles, + }, + ), + ( + "force", + { + "title": "Ignorer les avertissements", + "input_type": "checkbox", + "explanation": "passer outre les avertissements (homonymes, etc)", + "labels": ("",), + "allowed_values": ("1",), + }, + ), + ] + + if "tf-submitted" in REQUEST.form and not "roles" in REQUEST.form: + REQUEST.form["roles"] = [] + if "tf-submitted" in REQUEST.form: + # Ajoute roles existants mais non modifiables (disabled dans le form) + # orig_roles - editable_roles + REQUEST.form["roles"] = list( + set(REQUEST.form["roles"]).union(orig_roles - editable_roles) + ) + + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + descr, + initvalues=initvalues, + submitlabel=submitlabel, + ) + if tf[0] == 0: + return "\n".join(H) + "\n" + tf[1] + F + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + vals = tf[2] + roles = set(vals["roles"]).intersection(editable_roles) + if REQUEST.form.has_key("edit"): + edit = int(REQUEST.form["edit"]) + else: + edit = 0 + try: + force = int(vals["force"][0]) + except: + force = 0 + + if edit: + user_name = initvalues["user_name"] + else: + user_name = vals["user_name"] + # ce login existe ? + err = None + users = self._user_list(args={"user_name": user_name}) + if edit and not users: # safety net, le user_name ne devrait pas changer + err = "identifiant %s inexistant" % user_name + if not edit and users: + err = "identifiant %s déjà utilisé" % user_name + if err: + H.append(tf_error_message("""Erreur: %s""" % err)) + return "\n".join(H) + "\n" + tf[1] + F + + if not force: + ok, msg = self._check_modif_user( + edit, + user_name=user_name, + nom=vals["nom"], + prenom=vals["prenom"], + email=vals["email"], + roles=vals["roles"], + ) + if not ok: + H.append( + tf_error_message( + """Attention: %s (vous pouvez forcer l'opération en cochant "Ignorer les avertissements" en bas de page)""" + % msg + ) + ) + + return "\n".join(H) + "\n" + tf[1] + F + + if edit: # modif utilisateur (mais pas passwd) + if (not can_choose_dept) and vals.has_key("dept"): + del vals["dept"] + if vals.has_key("passwd"): + del vals["passwd"] + if vals.has_key("date_modif_passwd"): + del vals["date_modif_passwd"] + if vals.has_key("user_name"): + del vals["user_name"] + if (auth_name == user_name) and vals.has_key("status"): + del vals["status"] # no one can't change its own status + + # traitement des roles: ne doit pas affecter les roles + # que l'on en controle pas: + for role in orig_roles: + if role and not role in editable_roles: + roles.add(role) + + vals["roles"] = ",".join(roles) + + # ok, edit + log("sco_users: editing %s by %s" % (user_name, auth_name)) + # log('sco_users: previous_values=%s' % initvalues) + # log('sco_users: new_values=%s' % vals) + self._user_edit(user_name, vals) + return REQUEST.RESPONSE.redirect( + "userinfo?user_name=%s&head_message=Utilisateur %s modifié" + % (user_name, user_name) + ) + else: # creation utilisateur + vals["roles"] = ",".join(vals["roles"]) + # check identifiant + if not re.match(r"^[a-zA-Z0-9@\\\-_\\\.]+$", vals["user_name"]): + msg = tf_error_message( + "identifiant invalide (pas d'accents ni de caractères spéciaux)" + ) + return "\n".join(H) + msg + "\n" + tf[1] + F + # check passwords + if vals["passwd"] != vals["passwd2"]: + msg = tf_error_message( + """Les deux mots de passes ne correspondent pas !""" + ) + return "\n".join(H) + msg + "\n" + tf[1] + F + if not self._is_valid_passwd(vals["passwd"]): + msg = tf_error_message( + """Mot de passe trop simple, recommencez !""" + ) + return "\n".join(H) + msg + "\n" + tf[1] + F + if not can_choose_dept: + vals["dept"] = auth_dept + # ok, go + log("sco_users: new_user %s by %s" % (vals["user_name"], auth_name)) + self.create_user(vals, REQUEST=REQUEST) + + def _check_modif_user( + self, edit, user_name="", nom="", prenom="", email="", roles=[] + ): + """Vérifie que et utilisateur peut etre crée (edit=0) ou modifié (edit=1) + Cherche homonymes. + returns (ok, msg) + - ok : si vrai, peut continuer avec ces parametres + (si ok est faux, l'utilisateur peut quand même forcer la creation) + - msg: message warning a presenter l'utilisateur + """ + if not user_name or not nom or not prenom: + return False, "champ requis vide" + if not email: + return False, "vous devriez indiquer le mail de l'utilisateur créé !" + # ce login existe ? + users = self._user_list(args={"user_name": user_name}) + if edit and not users: # safety net, le user_name ne devrait pas changer + return False, "identifiant %s inexistant" % user_name + if not edit and users: + return False, "identifiant %s déjà utilisé" % user_name + + # Des noms/prénoms semblables existent ? + cnx = self.GetUsersDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "select * from sco_users where lower(nom) ~ %(nom)s and lower(prenom) ~ %(prenom)s;", + {"nom": nom.lower().strip(), "prenom": prenom.lower().strip()}, + ) + res = cursor.dictfetchall() + if edit: + minmatch = 1 + else: + minmatch = 0 + if len(res) > minmatch: + return ( + False, + "des utilisateurs proches existent: " + + ", ".join( + [ + "%s %s (pseudo=%s)" % (x["prenom"], x["nom"], x["user_name"]) + for x in res + ] + ), + ) + # Roles ? + if not roles and not (edit and users[0]["status"] == "old"): + # nb: si utilisateur desactivé (old), pas de role attribué + return False, "aucun rôle sélectionné, êtes vous sûr ?" + # ok + return True, "" + + security.declareProtected(ScoUsersAdmin, "import_users_form") + + def import_users_form(self, REQUEST, user_name=None, edit=0): + """Import utilisateurs depuis feuille Excel""" + head = self.sco_header(REQUEST, page_title="Import utilisateurs") + H = [ + head, + """

    Téléchargement d'une nouvelle liste d'utilisateurs

    +

    A utiliser pour importer de nouveaux utilisateurs (enseignants ou secrétaires) +

    +

    + L'opération se déroule en deux étapes. Dans un premier temps, + vous téléchargez une feuille Excel type. Vous devez remplir + cette feuille, une ligne décrivant chaque utilisateur. Ensuite, + vous indiquez le nom de votre fichier dans la case "Fichier Excel" + ci-dessous, et cliquez sur "Télécharger" pour envoyer au serveur + votre liste. +

    + """, + ] + help = """

    + Lors de la creation des utilisateurs, les opérations suivantes sont effectuées: +

    +
      +
    1. vérification des données;
    2. +
    3. génération d'un mot de passe alétoire pour chaque utilisateur;
    4. +
    5. création de chaque utilisateur;
    6. +
    7. envoi à chaque utilisateur de son mot de passe initial par mail.
    8. +
    """ + H.append( + """
    1. + Obtenir la feuille excel à remplir
    2. """ + ) + F = self.sco_footer(REQUEST) + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ( + "xlsfile", + {"title": "Fichier Excel:", "input_type": "file", "size": 40}, + ), + ("formsemestre_id", {"input_type": "hidden"}), + ), + submitlabel="Télécharger", + ) + if tf[0] == 0: + return "\n".join(H) + tf[1] + "
    " + help + F + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + # IMPORT + diag = sco_import_users.import_excel_file( + tf[2]["xlsfile"], REQUEST=REQUEST, context=self + ) + H = [head] + H.append("

    Import excel: %s

    " % diag) + H.append("

    OK, import terminé !

    ") + H.append('

    Continuer

    ' % REQUEST.URL1) + return "\n".join(H) + help + F + + security.declareProtected(ScoUsersAdmin, "import_users_generate_excel_sample") + + def import_users_generate_excel_sample(self, REQUEST): + "une feuille excel pour importation utilisateurs" + data = sco_import_users.generate_excel_sample() + return sco_excel.sendExcelFile(REQUEST, data, "ImportUtilisateurs.xls") + + security.declareProtected(ScoUsersAdmin, "create_user") + + def create_user(self, args, REQUEST=None): + "creation utilisateur zope" + cnx = self.GetUsersDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + passwd = args["passwd"] + args["passwd"] = "undefined" + if "passwd2" in args: + del args["passwd2"] + log("create_user: args=%s" % args) # log apres supr. du mot de passe ! + r = self._userEditor.create(cnx, args) + self.get_userlist_cache().inval_cache() # > + + # call exUserFolder to set passwd + roles = args["roles"].split(",") + self.acl_users.scodoc_editUser( + cursor, args["user_name"], password=passwd, roles=roles + ) + + if REQUEST: + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + + security.declareProtected(ScoUsersAdmin, "delete_user_form") + + def delete_user_form(self, REQUEST, user_name, dialog_confirmed=False): + "delete user" + authuser = REQUEST.AUTHENTICATED_USER + if not self._can_handle_passwd(authuser, user_name): + return ( + self.sco_header(REQUEST, user_check=False) + + "

    Vous n'avez pas la permission de supprimer cet utilisateur

    " + + self.sco_footer(REQUEST) + ) + + r = self._user_list(args={"user_name": user_name}) + if len(r) != 1: + return ScoValueError("utilisateur %s inexistant" % user_name) + if not dialog_confirmed: + return self.confirmDialog( + """

    Confirmer la suppression de l\'utilisateur %s ?

    +

    En général, il est déconseillé de supprimer un utilisateur, son + identité étant référencé dans les modules de formation. N'utilisez + cette fonction qu'en cas d'erreur (création de doublons, etc). +

    + """ + % user_name, + dest_url="", + REQUEST=REQUEST, + cancel_url=REQUEST.URL1, + parameters={"user_name": user_name}, + ) + self._user_delete(user_name) + REQUEST.RESPONSE.redirect(REQUEST.URL1) + + def list_users( + self, + dept, + all=False, # tous les departements + with_olds=False, # inclue les anciens utilisateurs (status "old") + format="html", + with_links=True, + REQUEST=None, + ): + "List users" + authuser = REQUEST.AUTHENTICATED_USER + if dept and not all: + r = self.get_userlist(dept=dept, with_olds=with_olds) + comm = "dept. %s" % dept + else: + r = self.get_userlist(with_olds=with_olds) + comm = "tous" + if with_olds: + comm += ", avec anciens" + comm = "(" + comm + ")" + # -- Add some information and links: + for u in r: + # Can current user modify this user ? + can_modify = self._can_handle_passwd( + authuser, u["user_name"], allow_admindepts=True + ) + + # Add links + if with_links and can_modify: + target = "userinfo?user_name=%(user_name)s" % u + u["_user_name_target"] = target + u["_nom_target"] = target + u["_prenom_target"] = target + + # Hide passwd modification date (depending on rights wrt user) + if not can_modify: + u["date_modif_passwd"] = "(non visible)" + + # Add spaces between roles to ease line wrap + if u["roles"]: + u["roles"] = ", ".join(u["roles"].split(",")) + + # Convert dates to ISO if XML output + if format == "xml" and u["date_modif_passwd"] != "NA": + u["date_modif_passwd"] = DateDMYtoISO(u["date_modif_passwd"]) or "" + + # Convert date_expiration and date_modif_passwd to ISO to ease sorting + if u["date_expiration"]: + u["date_expiration_iso"] = DateDMYtoISO(u["date_expiration"]) + else: + u["date_expiration_iso"] = "" + if u["date_modif_passwd"]: + u["date_modif_passwd_iso"] = DateDMYtoISO(u["date_expiration"]) + else: + u["date_modif_passwd_iso"] = "" + + title = "Utilisateurs définis dans ScoDoc" + tab = GenTable( + rows=r, + columns_ids=( + "user_name", + "nom_fmt", + "prenom_fmt", + "email", + "dept", + "roles", + "date_expiration_iso", + "date_modif_passwd_iso", + "passwd_temp", + "status_txt", + ), + titles={ + "user_name": "Login", + "nom_fmt": "Nom", + "prenom_fmt": "Prénom", + "email": "Mail", + "dept": "Dept.", + "roles": "Rôles", + "date_expiration_iso": "Expiration", + "date_modif_passwd_iso": "Modif. mot de passe", + "passwd_temp": "Temp.", + "status_txt": "Etat", + }, + caption=title, + page_title="title", + html_title="""

    %d utilisateurs %s

    +

    Cliquer sur un nom pour changer son mot de passe

    """ + % (len(r), comm), + html_class="table_leftalign list_users", + html_with_td_classes=True, + html_sortable=True, + base_url="%s?all=%s" % (REQUEST.URL0, all), + pdf_link=False, # table is too wide to fit in a paper page => disable pdf + preferences=self.get_preferences(), + ) + + return tab.make_page( + self, format=format, with_html_headers=False, REQUEST=REQUEST + ) + + def get_userlist_cache(self): + url = self.ScoURL() + if CACHE_userlist.has_key(url): + return CACHE_userlist[url] + else: + log("get_userlist_cache: new simpleCache") + CACHE_userlist[url] = sco_cache.expiringCache(max_validity=60) + return CACHE_userlist[url] + + security.declareProtected(ScoView, "get_userlist") + + def get_userlist(self, dept=None, with_olds=False): + """Returns list of users. + If dept, select users from this dept, + else return all users. + """ + # on ne cache que la liste sans les "olds" + if with_olds: + r = None + else: + cache = self.get_userlist_cache() + r = cache.get(dept) + + if r != None: + return r + else: + args = {} + if not with_olds: + args["status"] = None + if dept != None: + args["dept"] = dept + + r = self._user_list(args=args) + + l = [self.user_info(user=user) for user in r] + + if not with_olds: + cache.set(dept, l) + return l + + security.declareProtected(ScoView, "get_userlist_xml") + + def get_userlist_xml(self, dept=None, start="", limit=25, REQUEST=None): + """Returns XML list of users with name (nomplogin) starting with start. + Used for forms auto-completion.""" + userlist = self.get_userlist(dept=dept) + start = suppression_diacritics(unicode(start, "utf-8")) + start = strlower(str(start)) + + userlist = [user for user in userlist if user["nomnoacc"].startswith(start)] + if REQUEST: + REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE) + doc = jaxml.XML_document(encoding=SCO_ENCODING) + doc.results() + for user in userlist[:limit]: + doc._push() + doc.rs(user["nomplogin"], id=user["user_id"], info="") + doc._pop() + return repr(doc) + + security.declareProtected(ScoView, "get_user_name_from_nomplogin") + + def get_user_name_from_nomplogin(self, nomplogin): + """Returns user_name (login) from nomplogin + """ + m = re.match(r".*\((.*)\)", nomplogin.strip()) + if m: + return m.group(1) + else: + return None + + security.declareProtected(ScoView, "reset_password_form") + + def reset_password_form(self, user_name=None, dialog_confirmed=False, REQUEST=None): + """Form to reset a password""" + if not dialog_confirmed: + return self.confirmDialog( + """

    Ré-initialiser le mot de passe de %s ?

    +

    Le mot de passe de %s va être choisi au hasard et lui être envoyé par mail. +Il devra ensuite se connecter et le changer. +

    + """ + % (user_name, user_name), + parameters={"user_name": user_name}, + REQUEST=REQUEST, + ) + self.reset_password(user_name=user_name, REQUEST=REQUEST) + return REQUEST.RESPONSE.redirect( + REQUEST.URL1 + + "?head_message=mot%20de%20passe%20de%20" + + user_name + + "%20reinitialise" + ) + + security.declareProtected(ScoView, "reset_password") + + def reset_password(self, user_name=None, REQUEST=None): + """Reset a password: + - set user's passwd_temp to 1 + - set roles to 'ScoReset' + - generate a random password and mail it + """ + authuser = REQUEST.AUTHENTICATED_USER + auth_name = str(authuser) + if not user_name: + user_name = auth_name + # Access control + if not self._can_handle_passwd(authuser, user_name): + raise AccessDenied( + "vous n'avez pas la permission de changer ce mot de passe" + ) + log("reset_password: %s" % user_name) + # Check that user has valid mail + info = self.user_info(user_name=user_name) + if not is_valid_mail(info["email"]): + raise Exception("pas de mail valide associé à l'utilisateur") + # Generate random password + password = sco_import_users.generate_password() + self.do_change_password(user_name, password) + # Flag it as temporary: + cnx = self.GetUsersDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + ui = {"user_name": user_name} + cursor.execute( + "update sco_users set passwd_temp=1 where user_name='%(user_name)s'" % ui + ) + # Send email + info["passwd"] = password + sco_import_users.mail_password(info, context=self, reset=True) + + +# -------------------------------------------------------------------- +# +# Zope Product Administration +# +# -------------------------------------------------------------------- +def manage_addZScoUsers( + self, id="id_ZScousers", title="The Title for ZScoUsers Object", REQUEST=None +): + "Add a ZScoUsers instance to a folder." + self._setObject(id, ZScoUsers(id, title)) + if REQUEST is not None: + return self.manage_main(self, REQUEST) diff --git a/ZScolar.py b/ZScolar.py new file mode 100644 index 00000000..209a6094 --- /dev/null +++ b/ZScolar.py @@ -0,0 +1,2830 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +"""Site Scolarite pour département IUT +""" + +import sys +import traceback +import time, string, glob, re +import urllib, urllib2, cgi, xml + +try: + from cStringIO import StringIO +except: + from StringIO import StringIO +from zipfile import ZipFile +import thread +import psycopg2 + +from sco_zope import * + + +# --------------- +from notes_log import log + +log.set_log_directory(INSTANCE_HOME + "/log") +log("restarting...") + +log("ZScolar home=%s" % file_path) + + +from sco_utils import * +import notesdb +from notesdb import * +from scolog import logdb + +import scolars +import sco_preferences +import sco_formations +from scolars import ( + format_nom, + format_prenom, + format_sexe, + format_lycee, + format_lycee_from_code, +) +from scolars import format_telephone, format_pays, make_etud_args +import sco_find_etud +import sco_photos +import sco_formsemestre +import sco_formsemestre_edit + +import sco_news +from sco_news import NEWS_INSCR, NEWS_NOTE, NEWS_FORM, NEWS_SEM, NEWS_MISC + +import html_sco_header, html_sidebar + +from gen_tables import GenTable +import sco_excel +import imageresize + +import ZNotes +import ZAbsences +import ZEntreprises +import ZScoUsers +import sco_modalites +import ImportScolars +import sco_portal_apogee +import sco_synchro_etuds +import sco_page_etud +import sco_groups +import sco_trombino +import sco_groups_view +import sco_trombino_tours +import sco_parcours_dut +import sco_report +import sco_archives_etud +import sco_debouche +import sco_groups_edit +import sco_up_to_date +import sco_edt_cal +import sco_dept +import sco_dump_db + +from VERSION import SCOVERSION, SCONEWS + +if CONFIG.ABSOLUTE_URL: + log("ScoDoc: ABSOLUTE_URL='%s'" % CONFIG.ABSOLUTE_URL) +log("ScoDoc: using encoding %s" % SCO_ENCODING) + +# import essai_cas + +# --------------- + + +class ZScolar(ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit): + + "ZScolar object" + + meta_type = "ZScolar" + security = ClassSecurityInfo() + file_path = Globals.package_home(globals()) + + # This is the list of the methods associated to 'tabs' in the ZMI + # Be aware that The first in the list is the one shown by default, so if + # the 'View' tab is the first, you will never see your tabs by cliquing + # on the object. + manage_options = ( + ({"label": "Contents", "action": "manage_main"},) + + PropertyManager.manage_options # add the 'Properties' tab + + ( + # this line is kept as an example with the files : + # dtml/manage_editZScolarForm.dtml + # html/ZScolar-edit.stx + # {'label': 'Properties', 'action': 'manage_editForm',}, + {"label": "View", "action": "index_html"}, + ) + + Item.manage_options # add the 'Undo' & 'Owner' tab + + RoleManager.manage_options # add the 'Security' tab + ) + + # no permissions, only called from python + def __init__(self, id, title, db_cnx_string=None): + "initialise a new instance of ZScolar" + log("*** creating ZScolar instance") + self.id = id + self.title = title + self._db_cnx_string = db_cnx_string + self._cnx = None + # --- add editable DTML documents: + # self.defaultDocFile('sidebar_dept', + # 'barre gauche (partie haute)', + # 'sidebar_dept') + + # --- add DB connector + # id = 'DB' + # da = ZopeDA.Connection( + # id, 'DB connector', db_cnx_string, False, + # check=1, tilevel=2, encoding='utf-8') + # self._setObject(id, da) + # --- add Scousers instance + id = "Users" + obj = ZScoUsers.ZScoUsers(id, "Gestion utilisateurs zope") + self._setObject(id, obj) + # --- add Notes instance + id = "Notes" + obj = ZNotes.ZNotes(id, "Gestion Notes") + self._setObject(id, obj) + # --- add Absences instance + id = "Absences" + obj = ZAbsences.ZAbsences(id, "Gestion absences") + self._setObject(id, obj) + # --- add Entreprises instance + id = "Entreprises" + obj = ZEntreprises.ZEntreprises(id, "Suivi entreprises") + self._setObject(id, obj) + + # + self.manage_addProperty("roles_initialized", "0", "string") + + # The for used to edit this object + def manage_editZScolar(self, title, RESPONSE=None): + "Changes the instance values" + self.title = title + self._p_changed = 1 + RESPONSE.redirect("manage_editForm") + + def _setup_initial_roles_and_permissions(self): + """Initialize roles and permissions + create 3 roles: EnsXXX, SecrXXX, AdminXXX + and set default permissions for each one. + """ + DeptId = self.DeptId() + log("initializing roles and permissions for %s" % DeptId) + H = [] + ok = True + DeptRoles = self.DeptUsersRoles() + + container = self.aq_parent # creates roles and permissions in parent folder + valid_roles = set(container.valid_roles()) + for role_name in DeptRoles: + if role_name not in valid_roles: + r = container._addRole(role_name) + if r: # error + H.append(r) + ok = False + + for permission in Sco_Default_Permissions.keys(): + roles = [r + DeptId for r in Sco_Default_Permissions[permission]] + roles.append("Manager") + log("granting '%s' to %s" % (permission, roles)) + try: + r = container.manage_permission(permission, roles=roles, acquire=0) + if r: + H.append(r) + ok = False + except ValueError: + log(traceback.format_exc()) + log("failed, ignoring.") + # set property indicating that we did the job: + self.manage_changeProperties(roles_initialized="1") + + return ok, "\n".join(H) + + security.declareProtected(ScoView, "DeptId") + + def DeptId(self): + """Returns Id for this department + (retreived as the name of the parent folder) + (c'est normalement l'id donne à create_dept.sh) + NB: la preference DeptName est au depart la même chose de cet id + mais elle peut être modifiée (préférences). + """ + return self.aq_parent.id + + def DeptUsersRoles(self): # not published + # Donne les rôles utilisés dans ce departement. + DeptId = self.DeptId() + DeptRoles = [] + for role_type in ("Ens", "Secr", "Admin", "RespPe"): + role_name = role_type + DeptId + DeptRoles.append(role_name) + return DeptRoles + + security.declareProtected(ScoView, "essai") + + def essai(self, x="", REQUEST=None): + """essai: header / body / footer""" + return """ + + + +Programme DUT R&T + + + + + + + + + + + + + +

    +UE11 Découverte métiers (code UCOD46, 16 ECTS, Apo VRTU11) +[verrouillé] +

    + + """ + return ( + self.sco_header(REQUEST) + + """
    %s
    """ % x + + self.sco_footer(REQUEST) + ) + b = "

    Hello, World !


    " + raise ValueError("essai exception") + # raise ScoValueError('essai exception !', dest_url='totoro', REQUEST=REQUEST) + + # cursor = cnx.cursor(cursor_factory=ScoDocCursor) + # cursor.execute("select * from notes_formations") + # b += str(cursor.fetchall()) + # b = self.Notes.gloups() + # raise NoteProcessError('test exception !') + + # essai: liste des permissions + from AccessControl import getSecurityManager + from AccessControl.Permission import Permission + + permissions = self.ac_inherited_permissions(1) + scoperms = [p for p in permissions if p[0][:3] == "Sco"] + # H.append( str(self.aq_parent.aq_parent.permission_settings()) ) + # H.append('

    perms: %s

    '%str(scoperms)) + # H.append('

    valid_roles: %s

    '%str(self.valid_roles())) + # H.append('

    ac_inherited_permissions=%s

    '%str(self.ac_inherited_permissions(1))) + def collect_roles(context, rd): + for p in scoperms: + name, value = p[:2] + P = Permission(name, value, context) + roles = list(P.getRoles()) + if rd.has_key(name): + rd[name] += roles + else: + rd[name] = roles + if hasattr(context, "aq_parent"): + collect_roles(context.aq_parent, rd) + + b = "" + rd = {} + collect_roles(self, rd) + b = "

    " + str(rd) + "

    " + + authuser = REQUEST.AUTHENTICATED_USER + for p in scoperms: + permname, value = p[:2] + b += "

    " + permname + " : " + if authuser.has_permission(permname, self): + b += "yes" + else: + b += "no" + b += "

    " + b += "

    xxx


    " + str(self.aq_parent.aq_parent) + + return self.sco_header(REQUEST) + str(b) + self.sco_footer(REQUEST) + + # essais calendriers: + security.declareProtected(ScoView, "experimental_calendar") + experimental_calendar = sco_edt_cal.experimental_calendar + security.declareProtected(ScoView, "group_edt_json") + group_edt_json = sco_edt_cal.group_edt_json + + security.declareProtected(ScoView, "ScoURL") + + def ScoURL(self): + "base URL for this sco instance" + # absolute_url is the classic Zope method + # The avoid the burden of configuring a proxy zope object, we offer + # a custom configuration via scodoc_config + return CONFIG.ABSOLUTE_URL or self.absolute_url() + + security.declareProtected(ScoView, "sco_header") + sco_header = html_sco_header.sco_header + security.declareProtected(ScoView, "sco_footer") + sco_footer = html_sco_header.sco_footer + + # -------------------------------------------------------------------- + # + # GESTION DE LA BD + # + # -------------------------------------------------------------------- + security.declareProtected(ScoSuperAdmin, "GetDBConnexionString") + + def GetDBConnexionString(self): + # should not be published (but used from contained classes via acquisition) + return self._db_cnx_string + + security.declareProtected(ScoSuperAdmin, "GetDBConnexion") + GetDBConnexion = notesdb.GetDBConnexion + + # A enlever après re-ecriture de ZEntreprises.py + security.declareProtected(ScoView, "TrivialFormulator") + + def TrivialFormulator( + self, + form_url, + values, + formdescription=(), + initvalues={}, + method="POST", + submitlabel="OK", + formid="tf", + cancelbutton=None, + readonly=False, + ): + "generator/validator of simple forms" + # obsolete, still used by dtml/entreprises old code... + return TrivialFormulator( + form_url, + values, + formdescription=formdescription, + initvalues=initvalues, + method=method, + submitlabel=submitlabel, + formid=formid, + cancelbutton=cancelbutton, + readonly=readonly, + ) + + # -------------------------------------------------------------------- + # + # SCOLARITE (top level) + # + # -------------------------------------------------------------------- + + security.declareProtected(ScoView, "about") + + def about(self, REQUEST): + "version info" + H = [ + """

    Système de gestion scolarité

    +

    © Emmanuel Viennet 1997-2020

    +

    Version %s (subversion %s)

    + """ + % (SCOVERSION, get_svn_version(file_path)) + ] + H.append( + '

    Logiciel libre écrit en Python.

    Utilise ReportLab pour générer les documents PDF, et pyExcelerator pour le traitement des documents Excel.

    ' + ) + H.append("

    Dernières évolutions

    " + SCONEWS) + H.append( + '" + ) + d = "" + return self.sco_header(REQUEST) + "\n".join(H) + d + self.sco_footer(REQUEST) + + security.declareProtected(ScoView, "ScoErrorResponse") + + def ScoErrorResponse(self, msg, format="html", REQUEST=None): + """Send an error message to the client, in html or xml format. + """ + REQUEST.RESPONSE.setStatus(404, reason=msg) + if format == "html" or format == "pdf": + raise ScoValueError(msg) + elif format == "xml": + REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE) + doc = jaxml.XML_document(encoding=SCO_ENCODING) + doc.error(msg=msg) + return repr(doc) + elif format == "json": + REQUEST.RESPONSE.setHeader("content-type", JSON_MIMETYPE) + return "undefined" # XXX voir quoi faire en cas d'erreur json + else: + raise ValueError("ScoErrorResponse: invalid format") + + # XXX essai XXX + # security.declareProtected(ScoView, 'essai_cas') + # essai_cas = essai_cas.essai_cas + + # -------------------------------------------------------------------- + # + # PREFERENCES + # + # -------------------------------------------------------------------- + security.declareProtected(ScoView, "get_preferences") + + def get_preferences(self, formsemestre_id=None): + "Get preferences for this instance (a dict-like instance)" + return sco_preferences.sem_preferences(self, formsemestre_id) + + security.declareProtected(ScoView, "get_preference") + + def get_preference(self, name, formsemestre_id=None): + """Returns value of named preference. + All preferences have a sensible default value (see sco_preferences.py), + this function always returns a usable value for all defined preferences names. + """ + return sco_preferences.get_base_preferences(self).get(formsemestre_id, name) + + security.declareProtected(ScoChangePreferences, "edit_preferences") + + def edit_preferences(self, REQUEST): + """Edit global preferences""" + return sco_preferences.get_base_preferences(self).edit(REQUEST=REQUEST) + + security.declareProtected(ScoView, "formsemestre_edit_preferences") + + def formsemestre_edit_preferences(self, formsemestre_id, REQUEST): + """Edit preferences for a semestre""" + authuser = REQUEST.AUTHENTICATED_USER + sem = sco_formsemestre.get_formsemestre(self.Notes, formsemestre_id) + ok = ( + authuser.has_permission(ScoImplement, self) + or ((str(authuser) in sem["responsables"]) and sem["resp_can_edit"]) + ) and (sem["etat"] == "1") + if ok: + return self.get_preferences(formsemestre_id=formsemestre_id).edit( + REQUEST=REQUEST + ) + else: + raise AccessDenied("Modification impossible pour %s" % authuser) + + security.declareProtected(ScoView, "doc_preferences") + + def doc_preferences(self, REQUEST): + """List preferences for wiki documentation""" + REQUEST.RESPONSE.setHeader("content-type", "text/plain" ) + return sco_preferences.doc_preferences(self) + + # -------------------------------------------------------------------- + # + # ETUDIANTS + # + # -------------------------------------------------------------------- + + # ----------------- BANDEAUX ------------------- + security.declareProtected(ScoView, "sidebar") + sidebar = html_sidebar.sidebar + security.declareProtected(ScoView, "sidebar_dept") + sidebar_dept = html_sidebar.sidebar_dept + + security.declareProtected(ScoView, "showEtudLog") + + def showEtudLog(self, etudid, format="html", REQUEST=None): + """Display log of operations on this student""" + etud = self.getEtudInfo(filled=1, REQUEST=REQUEST)[0] + + ops = self.listScoLog(etudid) + + tab = GenTable( + titles={ + "date": "Date", + "authenticated_user": "Utilisateur", + "remote_addr": "IP", + "method": "Opération", + "msg": "Message", + }, + columns_ids=("date", "authenticated_user", "remote_addr", "method", "msg"), + rows=ops, + html_sortable=True, + html_class="table_leftalign", + base_url="%s?etudid=%s" % (REQUEST.URL0, etudid), + page_title="Opérations sur %(nomprenom)s" % etud, + html_title="

    Opérations effectuées sur l'étudiant %(nomprenom)s

    " + % etud, + filename="log_" + make_filename(etud["nomprenom"]), + html_next_section='' + % etud, + preferences=self.get_preferences(), + ) + + return tab.make_page(self, format=format, REQUEST=REQUEST) + + def listScoLog(self, etudid): + "liste des operations effectuees sur cet etudiant" + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "select * from scolog where etudid=%(etudid)s ORDER BY DATE DESC", + {"etudid": etudid}, + ) + return cursor.dictfetchall() + + # ---------- PAGE ACCUEIL (listes) -------------- + security.declareProtected(ScoView, "index_html") + index_html = sco_dept.index_html + + security.declareProtected(ScoView, "rssnews") + + def rssnews(self, REQUEST=None): + "rss feed" + REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE) + return sco_news.scolar_news_summary_rss( + self, "Nouvelles de " + self.get_preference("DeptName"), self.ScoURL() + ) + + # genere liste html pour accès aux groupes de ce semestre + def make_listes_sem(self, sem, REQUEST=None, with_absences=True): + context = self + authuser = REQUEST.AUTHENTICATED_USER + r = self.ScoURL() # root url + # construit l'URL "destination" + # (a laquelle on revient apres saisie absences) + query_args = cgi.parse_qs(REQUEST.QUERY_STRING) + if "head_message" in query_args: + del query_args["head_message"] + destination = "%s?%s" % (REQUEST.URL, urllib.urlencode(query_args, True)) + destination = destination.replace( + "%", "%%" + ) # car ici utilisee dans un format string ! + + # + H = [] + # pas de menu absences si pas autorise: + if with_absences and not authuser.has_permission(ScoAbsChange, self): + with_absences = False + + # + H.append( + '

    Listes de %(titre)s (%(mois_debut)s - %(mois_fin)s)

    ' + % sem + ) + + formsemestre_id = sem["formsemestre_id"] + + # calcule dates 1er jour semaine pour absences + try: + if with_absences: + first_monday = ZAbsences.ddmmyyyy(sem["date_debut"]).prev_monday() + FA = [] # formulaire avec menu saisi absences + FA.append( + '' + ) + FA.append( + '' % sem + ) + FA.append( + '' + ) + + FA.append( + '' % destination + ) + FA.append('') + FA.append('") + FA.append( + 'état' + % sem + ) + FA.append("
    ") + FormAbs = "\n".join(FA) + else: + FormAbs = "" + except ScoInvalidDateError: # dates incorrectes dans semestres ? + FormAbs = "" + # + H.append('
    ') + # Genere liste pour chaque partition (categorie de groupes) + for partition in sco_groups.get_partitions_list( + context, sem["formsemestre_id"] + ): + if not partition["partition_name"]: + H.append("

    Tous les étudiants

    " % partition) + else: + H.append("

    Groupes de %(partition_name)s

    " % partition) + groups = sco_groups.get_partition_groups(context, partition) + if groups: + H.append("") + for group in groups: + n_members = len( + sco_groups.get_group_members(context, group["group_id"]) + ) + group["url"] = r + if group["group_name"]: + group["label"] = "groupe %(group_name)s" % group + else: + group["label"] = "liste" + H.append('') + H.append( + """""" + % group + ) + H.append("" % n_members) + + if with_absences: + H.append(FormAbs % group) + + H.append("") + H.append("
    ") + else: + H.append('

    Aucun groupe dans cette partition') + if self.Notes.can_change_groups(REQUEST, formsemestre_id): + H.append( + ' (créer)' + % partition["partition_id"] + ) + H.append("

    ") + if self.Notes.can_change_groups(REQUEST, formsemestre_id): + H.append( + '

    Ajouter une partition

    ' + % formsemestre_id + ) + + H.append("
    ") + return "\n".join(H) + + security.declareProtected(ScoView, "trombino") + trombino = sco_trombino.trombino + + security.declareProtected(ScoView, "pdf_trombino_tours") + pdf_trombino_tours = sco_trombino_tours.pdf_trombino_tours + + security.declareProtected(ScoView, "pdf_feuille_releve_absences") + pdf_feuille_releve_absences = sco_trombino_tours.pdf_feuille_releve_absences + + security.declareProtected(ScoView, "trombino_copy_photos") + trombino_copy_photos = sco_trombino.trombino_copy_photos + + security.declareProtected(ScoView, "groups_view") + groups_view = sco_groups_view.groups_view + + security.declareProtected(ScoView, "export_groups_as_moodle_csv") + export_groups_as_moodle_csv = sco_groups_view.export_groups_as_moodle_csv + + security.declareProtected(ScoView, "getEtudInfoGroupes") + + def getEtudInfoGroupes(self, group_ids, etat=None): + """liste triée d'infos (dict) sur les etudiants du groupe indiqué. + Attention: lent, car plusieurs requetes SQL par etudiant ! + """ + etuds = [] + for group_id in group_ids: + members = sco_groups.get_group_members(self, group_id, etat=etat) + for m in members: + etud = self.getEtudInfo(etudid=m["etudid"], filled=True)[0] + etuds.append(etud) + + return etuds + + # -------------------------- INFOS SUR ETUDIANTS -------------------------- + security.declareProtected(ScoView, "getEtudInfo") + + def getEtudInfo(self, etudid=False, code_nip=False, filled=False, REQUEST=None): + """infos sur un etudiant pour utilisation en Zope DTML + On peut specifier etudid + ou bien cherche dans REQUEST.form: etudid, code_nip, code_ine + (dans cet ordre). + """ + if etudid is None: + return [] + cnx = self.GetDBConnexion() + args = make_etud_args(etudid=etudid, code_nip=code_nip, REQUEST=REQUEST) + etud = scolars.etudident_list(cnx, args=args) + if filled: + self.fillEtudsInfo(etud) + return etud + + security.declareProtected(ScoView, "search_etud_in_dept") + search_etud_in_dept = sco_find_etud.search_etud_in_dept + + security.declareProtected(ScoView, "search_etud_by_name") + search_etud_by_name = sco_find_etud.search_etud_by_name + + security.declareProtected(ScoView, "fillEtudsInfo") + + def fillEtudsInfo(self, etuds): + """etuds est une liste d'etudiants (mappings) + Pour chaque etudiant, ajoute ou formatte les champs + -> informations pour fiche etudiant ou listes diverses + """ + cnx = self.GetDBConnexion() + # open('/tmp/t','w').write( str(etuds) ) + for etud in etuds: + etudid = etud["etudid"] + etud["dept"] = self.DeptId() + adrs = scolars.adresse_list(cnx, {"etudid": etudid}) + if not adrs: + # certains "vieux" etudiants n'ont pas d'adresse + adr = {}.fromkeys(scolars._adresseEditor.dbfields, "") + adr["etudid"] = etudid + else: + adr = adrs[0] + if len(adrs) > 1: + log("fillEtudsInfo: etudid=%s a %d adresses" % (etudid, len(adrs))) + etud.update(adr) + scolars.format_etud_ident(etud) + + # Semestres dans lesquel il est inscrit + ins = self.Notes.do_formsemestre_inscription_list({"etudid": etudid}) + etud["ins"] = ins + now = time.strftime("%Y-%m-%d") + sems = [] + cursem = None # semestre "courant" ou il est inscrit + for i in ins: + sem = sco_formsemestre.get_formsemestre(self, i["formsemestre_id"]) + debut = DateDMYtoISO(sem["date_debut"]) + fin = DateDMYtoISO(sem["date_fin"]) + if debut <= now and now <= fin: + cursem = sem + curi = i + sem["ins"] = i + sems.append(sem) + # trie les semestres par date de debut, le plus recent d'abord + # (important, ne pas changer (suivi cohortes)) + sems.sort(lambda x, y: cmp(y["dateord"], x["dateord"])) + etud["sems"] = sems + etud["cursem"] = cursem + if cursem: + etud["inscription"] = cursem["titremois"] + etud["inscriptionstr"] = "Inscrit en " + cursem["titremois"] + etud["inscription_formsemestre_id"] = cursem["formsemestre_id"] + etud["etatincursem"] = curi["etat"] + etud["situation"] = self._descr_situation_etud(etudid, etud["ne"]) + # XXX est-ce utile ? sco_groups.etud_add_group_infos(self, etud, cursem) + else: + if etud["sems"]: + if etud["sems"][0]["dateord"] > time.strftime( + "%Y-%m-%d", time.localtime() + ): + etud["inscription"] = "futur" + etud["situation"] = "futur élève" + else: + etud["inscription"] = "ancien" + etud["situation"] = "ancien élève" + else: + etud["inscription"] = "non inscrit" + etud["situation"] = etud["inscription"] + etud["inscriptionstr"] = etud["inscription"] + etud["inscription_formsemestre_id"] = None + # XXXetud['partitions'] = {} # ne va pas chercher les groupes des anciens semestres + etud["etatincursem"] = "?" + + # nettoyage champs souvents vides + if etud["nomlycee"]: + etud["ilycee"] = "Lycée " + format_lycee(etud["nomlycee"]) + if etud["villelycee"]: + etud["ilycee"] += " (%s)" % etud["villelycee"] + etud["ilycee"] += "
    " + else: + if etud["codelycee"]: + etud["ilycee"] = format_lycee_from_code(etud["codelycee"]) + else: + etud["ilycee"] = "" + rap = "" + if etud["rapporteur"] or etud["commentaire"]: + rap = "Note du rapporteur" + if etud["rapporteur"]: + rap += " (%s)" % etud["rapporteur"] + rap += ": " + if etud["commentaire"]: + rap += "%s" % etud["commentaire"] + etud["rap"] = rap + + # if etud['boursier_prec']: + # pass + + if etud["telephone"]: + etud["telephonestr"] = "Tél.: " + format_telephone( + etud["telephone"] + ) + else: + etud["telephonestr"] = "" + if etud["telephonemobile"]: + etud["telephonemobilestr"] = "Mobile: " + format_telephone( + etud["telephonemobile"] + ) + else: + etud["telephonemobilestr"] = "" + etud["debouche"] = etud["debouche"] or "" + + security.declareProtected(ScoView, "etud_info") + + def etud_info(self, etudid=None, format="xml", REQUEST=None): + "Donne les informations sur un etudiant" + t0 = time.time() + args = make_etud_args(etudid=etudid, REQUEST=REQUEST) + cnx = self.GetDBConnexion() + etuds = scolars.etudident_list(cnx, args) + if not etuds: + # etudiant non trouvé: message d'erreur + d = { + "etudid": etudid, + "nom": "?", + "nom_usuel": "", + "prenom": "?", + "sexe": "?", + "email": "?", + "emailperso": "", + "error": "code etudiant inconnu", + } + return sendResult( + REQUEST, d, name="etudiant", format=format, force_outer_xml_tag=False + ) + d = {} + etud = etuds[0] + self.fillEtudsInfo([etud]) + etud["date_naissance_iso"] = DateDMYtoISO(etud["date_naissance"]) + for a in ( + "etudid", + "code_nip", + "code_ine", + "nom", + "nom_usuel", + "prenom", + "sexe", + "nomprenom", + "email", + "emailperso", + "domicile", + "codepostaldomicile", + "villedomicile", + "paysdomicile", + "telephone", + "telephonemobile", + "fax", + "bac", + "specialite", + "annee_bac", + "nomlycee", + "villelycee", + "codepostallycee", + "codelycee", + "date_naissance_iso", + ): + d[a] = quote_xml_attr(etud[a]) + d["photo_url"] = quote_xml_attr(sco_photos.etud_photo_url(self, etud)) + + sem = etud["cursem"] + if sem: + sco_groups.etud_add_group_infos(self, etud, sem) + d["insemestre"] = [ + { + "current": "1", + "formsemestre_id": sem["formsemestre_id"], + "date_debut": DateDMYtoISO(sem["date_debut"]), + "date_fin": DateDMYtoISO(sem["date_fin"]), + "etat": quote_xml_attr(sem["ins"]["etat"]), + "groupes": quote_xml_attr( + etud["groupes"] + ), # slt pour semestre courant + } + ] + else: + d["insemestre"] = [] + for sem in etud["sems"]: + if sem != etud["cursem"]: + d["insemestre"].append( + { + "formsemestre_id": sem["formsemestre_id"], + "date_debut": DateDMYtoISO(sem["date_debut"]), + "date_fin": DateDMYtoISO(sem["date_fin"]), + "etat": quote_xml_attr(sem["ins"]["etat"]), + } + ) + + log("etud_info (%gs)" % (time.time() - t0)) + return sendResult( + REQUEST, d, name="etudiant", format=format, force_outer_xml_tag=False + ) + + security.declareProtected(ScoView, "XMLgetEtudInfos") + XMLgetEtudInfos = etud_info # old name, deprecated + + def isPrimoEtud(self, etud, sem): + """Determine si un (filled) etud a ete inscrit avant ce semestre. + Regarde la liste des semestres dans lesquels l'étudiant est inscrit + """ + now = sem["dateord"] + for s in etud["sems"]: # le + recent d'abord + if s["dateord"] < now: + return False + return True + + # -------------------------- FICHE ETUDIANT -------------------------- + security.declareProtected(ScoView, "ficheEtud") + ficheEtud = sco_page_etud.ficheEtud + + security.declareProtected(ScoView, "etud_upload_file_form") + etud_upload_file_form = sco_archives_etud.etud_upload_file_form + + security.declareProtected(ScoView, "etud_delete_archive") + etud_delete_archive = sco_archives_etud.etud_delete_archive + + security.declareProtected(ScoView, "etud_get_archived_file") + etud_get_archived_file = sco_archives_etud.etud_get_archived_file + + security.declareProtected(ScoView, "etudarchive_import_files_form") + etudarchive_import_files_form = sco_archives_etud.etudarchive_import_files_form + + security.declareProtected(ScoView, "etudarchive_generate_excel_sample") + etudarchive_generate_excel_sample = ( + sco_archives_etud.etudarchive_generate_excel_sample + ) + + def _descr_situation_etud(self, etudid, ne=""): + """chaine decrivant la situation actuelle de l'etudiant + """ + cnx = self.GetDBConnexion() + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "select I.formsemestre_id, I.etat from notes_formsemestre_inscription I, notes_formsemestre S where etudid=%(etudid)s and S.formsemestre_id = I.formsemestre_id and date_debut < now() and date_fin > now() order by S.date_debut desc;", + {"etudid": etudid}, + ) + r = cursor.dictfetchone() + if not r: + situation = "non inscrit" + else: + sem = sco_formsemestre.get_formsemestre(self, r["formsemestre_id"]) + if r["etat"] == "I": + situation = "inscrit%s en %s" % (ne, sem["titremois"]) + # Cherche la date d'inscription dans scolar_events: + events = scolars.scolar_events_list( + cnx, + args={ + "etudid": etudid, + "formsemestre_id": sem["formsemestre_id"], + "event_type": "INSCRIPTION", + }, + ) + if not events: + log( + "*** situation inconsistante pour %s (inscrit mais pas d'event)" + % etudid + ) + date_ins = "???" # ??? + else: + date_ins = events[0]["event_date"] + situation += " le " + str(date_ins) + else: + situation = "démission de %s" % sem["titremois"] + # Cherche la date de demission dans scolar_events: + events = scolars.scolar_events_list( + cnx, + args={ + "etudid": etudid, + "formsemestre_id": sem["formsemestre_id"], + "event_type": "DEMISSION", + }, + ) + if not events: + log( + "*** situation inconsistante pour %s (demission mais pas d'event)" + % etudid + ) + date_dem = "???" # ??? + else: + date_dem = events[0]["event_date"] + situation += " le " + str(date_dem) + return situation + + # Debouche / devenir etudiant + + # vrai si l'utilisateur peut modifier les informations de suivi sur la page etud" + def can_edit_suivi(self, REQUEST=None): + authuser = REQUEST.AUTHENTICATED_USER + return authuser.has_permission(ScoEtudChangeAdr, self) + + security.declareProtected(ScoEtudChangeAdr, "itemsuivi_suppress") + itemsuivi_suppress = sco_debouche.itemsuivi_suppress + security.declareProtected(ScoEtudChangeAdr, "itemsuivi_create") + itemsuivi_create = sco_debouche.itemsuivi_create + security.declareProtected(ScoEtudChangeAdr, "itemsuivi_set_date") + itemsuivi_set_date = sco_debouche.itemsuivi_set_date + security.declareProtected(ScoEtudChangeAdr, "itemsuivi_set_situation") + itemsuivi_set_situation = sco_debouche.itemsuivi_set_situation + security.declareProtected(ScoView, "itemsuivi_list_etud") + itemsuivi_list_etud = sco_debouche.itemsuivi_list_etud + security.declareProtected(ScoView, "itemsuivi_tag_list") + itemsuivi_tag_list = sco_debouche.itemsuivi_tag_list + security.declareProtected(ScoView, "itemsuivi_tag_search") + itemsuivi_tag_search = sco_debouche.itemsuivi_tag_search + security.declareProtected(ScoEtudChangeAdr, "itemsuivi_tag_set") + itemsuivi_tag_set = sco_debouche.itemsuivi_tag_set + + security.declareProtected(ScoEtudAddAnnotations, "doAddAnnotation") + + def doAddAnnotation(self, etudid, comment, REQUEST): + "ajoute annotation sur etudiant" + authuser = REQUEST.AUTHENTICATED_USER + cnx = self.GetDBConnexion() + scolars.etud_annotations_create( + cnx, + args={ + "etudid": etudid, + "comment": comment, + "zope_authenticated_user": str(authuser), + "zope_remote_addr": REQUEST.REMOTE_ADDR, + }, + ) + logdb(REQUEST, cnx, method="addAnnotation", etudid=etudid) + REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid) + + security.declareProtected(ScoView, "canSuppressAnnotation") + + def canSuppressAnnotation(self, annotation_id, REQUEST): + """True if current user can suppress this annotation + Seuls l'auteur de l'annotation et le chef de dept peuvent supprimer + une annotation. + """ + cnx = self.GetDBConnexion() + annos = scolars.etud_annotations_list(cnx, args={"id": annotation_id}) + if len(annos) != 1: + raise ScoValueError("annotation inexistante !") + anno = annos[0] + authuser = REQUEST.AUTHENTICATED_USER + # note: les anciennes installations n'ont pas le role ScoEtudSupprAnnotations + # c'est pourquoi on teste aussi ScoEtudInscrit (normalement détenue par le chef) + return ( + (str(authuser) == anno["zope_authenticated_user"]) + or authuser.has_permission(ScoEtudSupprAnnotations, self) + or authuser.has_permission(ScoEtudInscrit, self) + ) + + security.declareProtected(ScoView, "doSuppressAnnotation") + + def doSuppressAnnotation(self, etudid, annotation_id, REQUEST): + """Suppression annotation. + """ + if not self.canSuppressAnnotation(annotation_id, REQUEST): + raise AccessDenied("Vous n'avez pas le droit d'effectuer cette opération !") + + cnx = self.GetDBConnexion() + annos = scolars.etud_annotations_list(cnx, args={"id": annotation_id}) + if len(annos) != 1: + raise ScoValueError("annotation inexistante !") + anno = annos[0] + log("suppress annotation: %s" % str(anno)) + logdb(REQUEST, cnx, method="SuppressAnnotation", etudid=etudid) + scolars.etud_annotations_delete(cnx, annotation_id) + + REQUEST.RESPONSE.redirect( + "ficheEtud?etudid=%s&head_message=Annotation%%20supprimée" % (etudid) + ) + + security.declareProtected(ScoEtudChangeAdr, "formChangeCoordonnees") + + def formChangeCoordonnees(self, etudid, REQUEST): + "edit coordonnees etudiant" + cnx = self.GetDBConnexion() + etud = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0] + adrs = scolars.adresse_list(cnx, {"etudid": etudid}) + if adrs: + adr = adrs[0] + else: + adr = {} # no data for this student + H = [ + '

    Changement des coordonnées de %(nomprenom)s

    ' + % etud + ] + header = self.sco_header( + REQUEST, page_title="Changement adresse de %(nomprenom)s" % etud + ) + + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ("adresse_id", {"input_type": "hidden"}), + ("etudid", {"input_type": "hidden"}), + ( + "email", + { + "size": 40, + "title": "e-mail", + "explanation": "adresse institutionnelle", + }, + ), + ( + "emailperso", + { + "size": 40, + "title": "e-mail", + "explanation": "adresse personnelle", + }, + ), + ( + "domicile", + {"size": 65, "explanation": "numéro, rue", "title": "Adresse"}, + ), + ("codepostaldomicile", {"size": 6, "title": "Code postal"}), + ("villedomicile", {"size": 20, "title": "Ville"}), + ("paysdomicile", {"size": 20, "title": "Pays"}), + ("", {"input_type": "separator", "default": " "}), + ("telephone", {"size": 13, "title": "Téléphone"}), + ("telephonemobile", {"size": 13, "title": "Mobile"}), + ), + initvalues=adr, + submitlabel="Valider le formulaire", + ) + if tf[0] == 0: + return header + "\n".join(H) + tf[1] + self.sco_footer(REQUEST) + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + if adrs: + scolars.adresse_edit(cnx, args=tf[2], context=self) + else: + scolars.adresse_create(cnx, args=tf[2]) + logdb(REQUEST, cnx, method="changeCoordonnees", etudid=etudid) + REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid) + + security.declareProtected(ScoView, "formChangeGroup") + + def formChangeGroup(self, formsemestre_id, etudid, REQUEST): + "changement groupe etudiant dans semestre" + if not self.Notes.can_change_groups(REQUEST, formsemestre_id): + raise ScoValueError( + "Vous n'avez pas le droit d'effectuer cette opération !" + ) + cnx = self.GetDBConnexion() + etud = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0] + sem = sco_formsemestre.do_formsemestre_list(self, formsemestre_id) + ins = self.Notes.do_formsemestre_inscription_list( + {"etudid": etudid, "formsemestre_id": formsemestre_id} + )[0] + # + # -- check lock + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + if sem["etat"] != "1": + raise ScoValueError("Modification impossible: semestre verrouille") + # + etud["semtitre"] = sem["titremois"] + H = [ + '

    Changement de groupe de %(nomprenom)s (semestre %(semtitre)s)

    ' + % etud + ] + header = self.sco_header( + REQUEST, page_title="Changement de groupe de %(nomprenom)s" % etud + ) + # Liste des groupes existants + raise NotImplementedError # XXX utiliser form_group_choice ou supprimer completement ? + # + H.append("""

    """) + + H.append( + """ + +

    +(attention, vérifier que les groupes sont compatibles, selon votre organisation) +

    + + +

    Créer un nouveau groupe: + + + + +

    +

    + + + + +
    """ + % ( + etudid, + formsemestre_id, + sem["nomgroupetd"], + sem["nomgroupeta"], + sem["nomgroupetp"], + REQUEST.URL1, + ) + ) + + return header + "\n".join(H) + self.sco_footer(REQUEST) + + # --- Gestion des groupes: + security.declareProtected(ScoView, "affectGroups") + affectGroups = sco_groups_edit.affectGroups + + security.declareProtected(ScoView, "XMLgetGroupsInPartition") + XMLgetGroupsInPartition = sco_groups.XMLgetGroupsInPartition + + security.declareProtected(ScoView, "formsemestre_partition_list") + formsemestre_partition_list = sco_groups.formsemestre_partition_list + + security.declareProtected(ScoView, "setGroups") + setGroups = sco_groups.setGroups + + security.declareProtected(ScoView, "createGroup") + createGroup = sco_groups.createGroup + + security.declareProtected(ScoView, "suppressGroup") + suppressGroup = sco_groups.suppressGroup + + security.declareProtected(ScoView, "group_set_name") + group_set_name = sco_groups.group_set_name + + security.declareProtected(ScoView, "group_rename") + group_rename = sco_groups.group_rename + + security.declareProtected(ScoView, "groups_auto_repartition") + groups_auto_repartition = sco_groups.groups_auto_repartition + + security.declareProtected(ScoView, "editPartitionForm") + editPartitionForm = sco_groups.editPartitionForm + + security.declareProtected(ScoView, "partition_delete") + partition_delete = sco_groups.partition_delete + + security.declareProtected(ScoView, "partition_set_attr") + partition_set_attr = sco_groups.partition_set_attr + + security.declareProtected(ScoView, "partition_move") + partition_move = sco_groups.partition_move + + security.declareProtected(ScoView, "partition_set_name") + partition_set_name = sco_groups.partition_set_name + + security.declareProtected(ScoView, "partition_rename") + partition_rename = sco_groups.partition_rename + + security.declareProtected(ScoView, "partition_create") + partition_create = sco_groups.partition_create + + security.declareProtected(ScoView, "etud_info_html") + etud_info_html = sco_page_etud.etud_info_html + + # --- Gestion des photos: + security.declareProtected(ScoView, "get_photo_image") + + get_photo_image = sco_photos.get_photo_image + + security.declareProtected(ScoView, "etud_photo_html") + + etud_photo_html = sco_photos.etud_photo_html + + security.declareProtected(ScoView, "etud_photo_orig_page") + + def etud_photo_orig_page(self, etudid=None, REQUEST=None): + "Page with photo in orig. size" + etud = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0] + H = [ + self.sco_header(REQUEST, page_title=etud["nomprenom"]), + "

    %s

    " % etud["nomprenom"], + '", + self.sco_footer(REQUEST), + ] + return "\n".join(H) + + security.declareProtected(ScoEtudChangeAdr, "formChangePhoto") + + def formChangePhoto(self, etudid=None, REQUEST=None): + """Formulaire changement photo étudiant + """ + etud = self.getEtudInfo(filled=1, REQUEST=REQUEST)[0] + if sco_photos.etud_photo_is_local(self, etud): + etud["photoloc"] = "dans ScoDoc" + else: + etud["photoloc"] = "externe" + H = [ + self.sco_header(REQUEST, page_title="Changement de photo"), + """

    Changement de la photo de %(nomprenom)s

    +

    Photo actuelle (%(photoloc)s): + """ + % etud, + sco_photos.etud_photo_html( + self, etud, title="photo actuelle", REQUEST=REQUEST + ), + """

    Le fichier ne doit pas dépasser 500Ko (recadrer l'image, format "portrait" de préférence).

    +

    L'image sera automagiquement réduite pour obtenir une hauteur de 90 pixels.

    + """, + ] + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ("etudid", {"default": etudid, "input_type": "hidden"}), + ( + "photofile", + {"input_type": "file", "title": "Fichier image", "size": 20}, + ), + ), + submitlabel="Valider", + cancelbutton="Annuler", + ) + if tf[0] == 0: + return ( + "\n".join(H) + + tf[1] + + '

    Supprimer cette photo

    ' + % etudid + + self.sco_footer(REQUEST) + ) + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect( + REQUEST.URL1 + "/ficheEtud?etudid=" + etud["etudid"] + ) + else: + data = tf[2]["photofile"].read() + status, diag = sco_photos.store_photo(self, etud, data, REQUEST=REQUEST) + if status != 0: + return REQUEST.RESPONSE.redirect( + self.ScoURL() + "/ficheEtud?etudid=" + etud["etudid"] + ) + else: + H.append('

    Erreur:' + diag + "

    ") + return "\n".join(H) + self.sco_footer(REQUEST) + + security.declareProtected(ScoEtudChangeAdr, "formSuppressPhoto") + + def formSuppressPhoto(self, etudid=None, REQUEST=None, dialog_confirmed=False): + """Formulaire suppression photo étudiant + """ + etud = self.getEtudInfo(filled=1, REQUEST=REQUEST)[0] + if not dialog_confirmed: + return self.confirmDialog( + "

    Confirmer la suppression de la photo de %(nomprenom)s ?

    " % etud, + dest_url="", + REQUEST=REQUEST, + cancel_url="ficheEtud?etudid=%s" % etudid, + parameters={"etudid": etudid}, + ) + + sco_photos.suppress_photo(self, etud, REQUEST=REQUEST) + + return REQUEST.RESPONSE.redirect( + REQUEST.URL1 + "/ficheEtud?etudid=" + etud["etudid"] + ) + + # + security.declareProtected(ScoEtudInscrit, "formDem") + + def formDem(self, etudid, formsemestre_id, REQUEST): + "Formulaire Démission Etudiant" + return self._formDem_of_Def( + etudid, + formsemestre_id, + REQUEST=REQUEST, + operation_name="Démission", + operation_method="doDemEtudiant", + ) + + security.declareProtected(ScoEtudInscrit, "formDef") + + def formDef(self, etudid, formsemestre_id, REQUEST): + "Formulaire Défaillance Etudiant" + return self._formDem_of_Def( + etudid, + formsemestre_id, + REQUEST=REQUEST, + operation_name="Défaillance", + operation_method="doDefEtudiant", + ) + + def _formDem_of_Def( + self, + etudid, + formsemestre_id, + REQUEST=None, + operation_name="", + operation_method="", + ): + "Formulaire démission ou défaillance Etudiant" + cnx = self.GetDBConnexion() + etud = self.getEtudInfo(etudid=etudid, filled=1, REQUEST=REQUEST)[0] + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + if sem["etat"] != "1": + raise ScoValueError("Modification impossible: semestre verrouille") + + etud["formsemestre_id"] = formsemestre_id + etud["semtitre"] = sem["titremois"] + etud["nowdmy"] = time.strftime("%d/%m/%Y") + etud["operation_name"] = operation_name + # + header = self.sco_header( + REQUEST, + page_title="%(operation_name)s de %(nomprenom)s (du semestre %(semtitre)s)" + % etud, + ) + H = [ + '

    %(operation_name)s de %(nomprenom)s (semestre %(semtitre)s)

    ' + % etud + ] + H.append( + """

    + Date de la %s (J/M/AAAA):  + """ + % (operation_method, strlower(operation_name)) + ) + H.append( + """ + + + +

    + +

    """ + % etud + ) + return header + "\n".join(H) + self.sco_footer(REQUEST) + + security.declareProtected(ScoEtudInscrit, "doDemEtudiant") + + def doDemEtudiant(self, etudid, formsemestre_id, event_date=None, REQUEST=None): + "Déclare la démission d'un etudiant dans le semestre" + return self._doDem_or_Def_Etudiant( + etudid, + formsemestre_id, + event_date=event_date, + etat_new="D", + operation_method="demEtudiant", + event_type="DEMISSION", + REQUEST=REQUEST, + ) + + security.declareProtected(ScoEtudInscrit, "doDefEtudiant") + + def doDefEtudiant(self, etudid, formsemestre_id, event_date=None, REQUEST=None): + "Déclare la défaillance d'un etudiant dans le semestre" + return self._doDem_or_Def_Etudiant( + etudid, + formsemestre_id, + event_date=event_date, + etat_new=DEF, + operation_method="defailleEtudiant", + event_type="DEFAILLANCE", + REQUEST=REQUEST, + ) + + def _doDem_or_Def_Etudiant( + self, + etudid, + formsemestre_id, + event_date=None, + etat_new="D", # 'D' or DEF + operation_method="demEtudiant", + event_type="DEMISSION", + REQUEST=None, + ): + "Démission ou défaillance d'un étudiant" + # marque 'D' ou DEF dans l'inscription au semestre et ajoute + # un "evenement" scolarite + cnx = self.GetDBConnexion() + # check lock + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + if sem["etat"] != "1": + raise ScoValueError("Modification impossible: semestre verrouille") + # + ins = self.Notes.do_formsemestre_inscription_list( + {"etudid": etudid, "formsemestre_id": formsemestre_id} + )[0] + if not ins: + raise ScoException("etudiant non inscrit ?!") + ins["etat"] = etat_new + self.Notes.do_formsemestre_inscription_edit( + args=ins, formsemestre_id=formsemestre_id + ) + logdb(REQUEST, cnx, method=operation_method, etudid=etudid) + scolars.scolar_events_create( + cnx, + args={ + "etudid": etudid, + "event_date": event_date, + "formsemestre_id": formsemestre_id, + "event_type": event_type, + }, + ) + if REQUEST: + return REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid) + + security.declareProtected(ScoEtudInscrit, "doCancelDem") + + def doCancelDem( + self, etudid, formsemestre_id, dialog_confirmed=False, args=None, REQUEST=None + ): + "Annule une démission" + return self._doCancelDem_or_Def( + etudid, + formsemestre_id, + dialog_confirmed=dialog_confirmed, + args=args, + operation_name="démission", + etat_current="D", + etat_new="I", + operation_method="cancelDem", + event_type="DEMISSION", + REQUEST=REQUEST, + ) + + security.declareProtected(ScoEtudInscrit, "doCancelDef") + + def doCancelDef( + self, etudid, formsemestre_id, dialog_confirmed=False, args=None, REQUEST=None + ): + "Annule la défaillance de l'étudiant" + return self._doCancelDem_or_Def( + etudid, + formsemestre_id, + dialog_confirmed=dialog_confirmed, + args=args, + operation_name="défaillance", + etat_current=DEF, + etat_new="I", + operation_method="cancelDef", + event_type="DEFAILLANCE", + REQUEST=REQUEST, + ) + + def _doCancelDem_or_Def( + self, + etudid, + formsemestre_id, + dialog_confirmed=False, + args=None, + operation_name="", # "démission" ou "défaillance" + etat_current="D", + etat_new="I", + operation_method="cancelDem", + event_type="DEMISSION", + REQUEST=None, + ): + "Annule une demission ou une défaillance" + # check lock + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + if sem["etat"] != "1": + raise ScoValueError("Modification impossible: semestre verrouille") + # verif + info = self.getEtudInfo(etudid, filled=True)[0] + ok = False + for i in info["ins"]: + if i["formsemestre_id"] == formsemestre_id: + if i["etat"] != etat_current: + raise ScoValueError("etudiant non %s !" % operation_name) + ok = True + break + if not ok: + raise ScoValueError("etudiant non inscrit ???") + if not dialog_confirmed: + return self.confirmDialog( + "

    Confirmer l'annulation de la %s ?

    " % operation_name, + dest_url="", + REQUEST=REQUEST, + cancel_url="ficheEtud?etudid=%s" % etudid, + parameters={"etudid": etudid, "formsemestre_id": formsemestre_id}, + ) + # + ins = self.Notes.do_formsemestre_inscription_list( + {"etudid": etudid, "formsemestre_id": formsemestre_id} + )[0] + if ins["etat"] != etat_current: + raise ScoException("etudiant non %s !!!" % etat_current) # obviously a bug + ins["etat"] = etat_new + cnx = self.GetDBConnexion() + self.Notes.do_formsemestre_inscription_edit( + args=ins, formsemestre_id=formsemestre_id + ) + logdb(REQUEST, cnx, method=operation_method, etudid=etudid) + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute( + "delete from scolar_events where etudid=%(etudid)s and formsemestre_id=%(formsemestre_id)s and event_type='" + + event_type + + "'", + {"etudid": etudid, "formsemestre_id": formsemestre_id}, + ) + cnx.commit() + return REQUEST.RESPONSE.redirect("ficheEtud?etudid=%s" % etudid) + + security.declareProtected(ScoEtudInscrit, "etudident_create_form") + + def etudident_create_form(self, REQUEST=None): + "formulaire creation individuelle etudiant" + return self.etudident_create_or_edit_form(REQUEST, edit=False) + + security.declareProtected(ScoEtudInscrit, "etudident_edit_form") + + def etudident_edit_form(self, REQUEST=None): + "formulaire edition individuelle etudiant" + return self.etudident_create_or_edit_form(REQUEST, edit=True) + + security.declareProtected(ScoEtudInscrit, "etudident_create_or_edit_form") + + def etudident_create_or_edit_form(self, REQUEST, edit): + "Le formulaire HTML" + H = [self.sco_header(REQUEST, init_jquery_ui=True)] + F = self.sco_footer(REQUEST) + AUTHENTICATED_USER = REQUEST.AUTHENTICATED_USER + etudid = REQUEST.form.get("etudid", None) + cnx = self.GetDBConnexion() + descr = [] + if not edit: + # creation nouvel etudiant + initvalues = {} + submitlabel = "Ajouter cet étudiant" + H.append( + """

    Création d'un étudiant

    +

    En général, il est recommandé d'importer les étudiants depuis Apogée. + N'utilisez ce formulaire que pour les cas particuliers ou si votre établissement + n'utilise pas d'autre logiciel de gestion des inscriptions.

    +

    L'étudiant créé ne sera pas inscrit. + Pensez à l'inscrire dans un semestre !

    + """ + ) + else: + # edition donnees d'un etudiant existant + # setup form init values + if not etudid: + raise ValueError("missing etudid parameter") + descr.append(("etudid", {"default": etudid, "input_type": "hidden"})) + H.append( + '

    Modification d\'un étudiant (fiche)

    ' + % etudid + ) + initvalues = scolars.etudident_list(cnx, {"etudid": etudid}) + assert len(initvalues) == 1 + initvalues = initvalues[0] + submitlabel = "Modifier les données" + + # recuperation infos Apogee + # Si on a le code NIP, fait juste une requete, sinon tente de rechercher par nom + # (la recherche par nom ne fonctionne plus à Paris 13) + code_nip = initvalues.get("code_nip", "") + if code_nip: + try: + info = sco_portal_apogee.get_etud_apogee(self, code_nip) + except ValueError: + pass # XXX a terminer + nom = REQUEST.form.get("nom", None) + if nom is None: + nom = initvalues.get("nom", None) + if nom is None: + infos = [] + else: + prenom = REQUEST.form.get("prenom", "") + if REQUEST.form.get("tf-submitted", False) and not prenom: + prenom = initvalues.get("prenom", "") + infos = sco_portal_apogee.get_infos_apogee(self, nom, prenom) + + if infos: + formatted_infos = [ + """ + +
      """ + ] + nanswers = len(infos) + nmax = 10 # nb max de reponse montrees + infos = infos[:nmax] + for i in infos: + formatted_infos.append("
      • ") + for k in i.keys(): + if k != "nip": + item = "
      • %s : %s
      • " % (k, i[k]) + else: + item = ( + '
      • %s : %s
      • ' + % (k, i[k], i[k]) + ) + formatted_infos.append(item) + + formatted_infos.append("
    1. ") + formatted_infos.append("
    ") + m = "%d étudiants trouvés" % nanswers + if len(infos) != nanswers: + m += " (%d montrés)" % len(infos) + A = """
    +
    Informations Apogée
    +

    %s

    + %s +
    """ % ( + m, + "\n".join(formatted_infos), + ) + else: + A = """

    Pas d'informations d'Apogée

    """ + + require_ine = self.get_preference("always_require_ine") + + descr += [ + ("adm_id", {"input_type": "hidden"}), + ("nom", {"size": 25, "title": "Nom", "allow_null": False}), + ("nom_usuel", {"size": 25, "title": "Nom usuel", "allow_null": True}), + ( + "prenom", + {"size": 25, "title": "Prénom", "allow_null": CONFIG.ALLOW_NULL_PRENOM}, + ), + ( + "sexe", + { + "input_type": "menu", + "labels": ["H", "F"], + "allowed_values": ["MR", "MME"], + "title": "Genre", + }, + ), + ( + "date_naissance", + { + "title": "Date de naissance", + "input_type": "date", + "explanation": "j/m/a", + }, + ), + ("lieu_naissance", {"title": "Lieu de naissance", "size": 32}), + ("dept_naissance", {"title": "Département de naissance", "size": 5}), + ("nationalite", {"size": 25, "title": "Nationalité"}), + ( + "statut", + { + "size": 25, + "title": "Statut", + "explanation": '("salarie", ...) inutilisé par ScoDoc', + }, + ), + ( + "annee", + { + "size": 5, + "title": "Année admission IUT", + "type": "int", + "allow_null": False, + "explanation": "année 1ere inscription (obligatoire)", + }, + ), + # + ("sep", {"input_type": "separator", "title": "Scolarité antérieure:"}), + ("bac", {"size": 5, "explanation": "série du bac (S, STI, STT, ...)"}), + ( + "specialite", + { + "size": 25, + "title": "Spécialité", + "explanation": "spécialité bac: SVT M, GENIE ELECTRONIQUE, ...", + }, + ), + ( + "annee_bac", + { + "size": 5, + "title": "Année bac", + "type": "int", + "explanation": "année obtention du bac", + }, + ), + ( + "math", + { + "size": 3, + "type": "float", + "title": "Note de mathématiques", + "explanation": "note sur 20 en terminale", + }, + ), + ( + "physique", + { + "size": 3, + "type": "float", + "title": "Note de physique", + "explanation": "note sur 20 en terminale", + }, + ), + ( + "anglais", + { + "size": 3, + "type": "float", + "title": "Note d'anglais", + "explanation": "note sur 20 en terminale", + }, + ), + ( + "francais", + { + "size": 3, + "type": "float", + "title": "Note de français", + "explanation": "note sur 20 obtenue au bac", + }, + ), + ( + "type_admission", + { + "input_type": "menu", + "title": "Voie d'admission", + "allowed_values": TYPES_ADMISSION, + }, + ), + ( + "boursier_prec", + { + "input_type": "boolcheckbox", + "labels": ["non", "oui"], + "title": "Boursier ?", + "explanation": "dans le cycle précédent (lycée)", + }, + ), + ( + "rang", + { + "size": 1, + "type": "int", + "title": "Position établissement", + "explanation": "rang de notre établissement dans les voeux du candidat (si connu)", + }, + ), + ( + "qualite", + { + "size": 3, + "type": "float", + "title": "Qualité", + "explanation": "Note de qualité attribuée au dossier (par le jury d'adm.)", + }, + ), + ( + "decision", + { + "input_type": "menu", + "title": "Décision", + "allowed_values": [ + "ADMIS", + "ATTENTE 1", + "ATTENTE 2", + "ATTENTE 3", + "REFUS", + "?", + ], + }, + ), + ( + "score", + { + "size": 3, + "type": "float", + "title": "Score", + "explanation": "score calculé lors de l'admission", + }, + ), + ( + "classement", + { + "size": 3, + "type": "int", + "title": "Classement", + "explanation": "Classement par le jury d'admission (de 1 à N)", + }, + ), + ("apb_groupe", {"size": 15, "title": "Groupe APB ou PS"}), + ( + "apb_classement_gr", + { + "size": 3, + "type": "int", + "title": "Classement", + "explanation": "Classement par le jury dans le groupe ABP ou PS (de 1 à Ng)", + }, + ), + ("rapporteur", {"size": 50, "title": "Enseignant rapporteur"}), + ( + "commentaire", + { + "input_type": "textarea", + "rows": 4, + "cols": 50, + "title": "Note du rapporteur", + }, + ), + ("nomlycee", {"size": 20, "title": "Lycée d'origine"}), + ("villelycee", {"size": 15, "title": "Commune du lycée"}), + ("codepostallycee", {"size": 15, "title": "Code Postal lycée"}), + ( + "codelycee", + { + "size": 15, + "title": "Code Lycée", + "explanation": "Code national établissement du lycée ou établissement d'origine", + }, + ), + ("sep", {"input_type": "separator", "title": "Codes Apogée: (optionnels)"}), + ( + "code_nip", + { + "size": 25, + "title": "Numéro NIP", + "allow_null": True, + "explanation": "numéro identité étudiant (Apogée)", + }, + ), + ( + "code_ine", + { + "size": 25, + "title": "Numéro INE", + "allow_null": not require_ine, + "explanation": "numéro INE", + }, + ), + ( + "dont_check_homonyms", + { + "title": "Autoriser les homonymes", + "input_type": "boolcheckbox", + "explanation": "ne vérifie pas les noms et prénoms proches", + }, + ), + ] + initvalues["dont_check_homonyms"] = False + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + descr, + submitlabel=submitlabel, + cancelbutton="Re-interroger Apogee", + initvalues=initvalues, + ) + if tf[0] == 0: + return "\n".join(H) + tf[1] + "

    " + A + F + elif tf[0] == -1: + return "\n".join(H) + tf[1] + "

    " + A + F + # return '\n'.join(H) + '

    annulation

    ' + F + else: + # form submission + if edit: + etudid = tf[2]["etudid"] + else: + etudid = None + ok, NbHomonyms = scolars.check_nom_prenom( + cnx, nom=tf[2]["nom"], prenom=tf[2]["prenom"], etudid=etudid + ) + if not ok: + return ( + "\n".join(H) + + tf_error_message("Nom ou prénom invalide") + + tf[1] + + "

    " + + A + + F + ) + # log('NbHomonyms=%s' % NbHomonyms) + if not tf[2]["dont_check_homonyms"] and NbHomonyms > 0: + return ( + "\n".join(H) + + tf_error_message( + """Attention: il y a déjà un étudiant portant des noms et prénoms proches. Vous pouvez forcer la présence d'un homonyme en cochant "autoriser les homonymes" en bas du formulaire.""" + ) + + tf[1] + + "

    " + + A + + F + ) + + if not edit: + # creation d'un etudiant + etudid = scolars.etudident_create( + cnx, tf[2], context=self, REQUEST=REQUEST + ) + # crée une adresse vide (chaque etudiant doit etre dans la table "adresse" !) + adresse_id = scolars.adresse_create( + cnx, + { + "etudid": etudid, + "typeadresse": "domicile", + "description": "(creation individuelle)", + }, + ) + + # event + scolars.scolar_events_create( + cnx, + args={ + "etudid": etudid, + "event_date": time.strftime("%d/%m/%Y"), + "formsemestre_id": None, + "event_type": "CREATION", + }, + ) + # log + logdb( + REQUEST, + cnx, + method="etudident_edit_form", + etudid=etudid, + msg="creation initiale", + ) + etud = scolars.etudident_list(cnx, {"etudid": etudid})[0] + self.fillEtudsInfo([etud]) + etud["url"] = "ficheEtud?etudid=%(etudid)s" % etud + sco_news.add( + self, + REQUEST, + typ=NEWS_INSCR, + object=None, # pas d'object pour ne montrer qu'un etudiant + text='Nouvel étudiant %(nomprenom)s' % etud, + url=etud["url"], + ) + else: + # modif d'un etudiant + scolars.etudident_edit(cnx, tf[2], context=self, REQUEST=REQUEST) + etud = scolars.etudident_list(cnx, {"etudid": etudid})[0] + self.fillEtudsInfo([etud]) + # Inval semesters with this student: + to_inval = [s["formsemestre_id"] for s in etud["sems"]] + if to_inval: + self.Notes._inval_cache( + formsemestre_id_list=to_inval + ) # > etudident_create_or_edit + # + return REQUEST.RESPONSE.redirect("ficheEtud?etudid=" + etudid) + + security.declareProtected(ScoEtudInscrit, "etudident_delete") + + def etudident_delete(self, etudid, dialog_confirmed=False, REQUEST=None): + "Delete a student" + cnx = self.GetDBConnexion() + etuds = scolars.etudident_list(cnx, {"etudid": etudid}) + if not etuds: + raise ScoValueError("Etudiant inexistant !") + else: + etud = etuds[0] + self.fillEtudsInfo([etud]) + if not dialog_confirmed: + return self.confirmDialog( + """

    Confirmer la suppression de l'étudiant %(nomprenom)s ?

    +

    +

    Prenez le temps de vérifier que vous devez vraiment supprimer cet étudiant !

    +

    Cette opération irréversible efface toute trace de l'étudiant: inscriptions, notes, absences... dans tous les semestres qu'il a fréquenté.

    +

    Dans la plupart des cas, vous avez seulement besoin de le

      désinscrire
    d'un semestre ? (dans ce cas passez par sa fiche, menu associé au semestre)

    + +

    Vérifier la fiche de %(nomprenom)s +

    """ + % etud, + dest_url="", + REQUEST=REQUEST, + cancel_url="ficheEtud?etudid=%s" % etudid, + OK="Supprimer définitivement cet étudiant", + parameters={"etudid": etudid}, + ) + log("etudident_delete: etudid=%(etudid)s nomprenom=%(nomprenom)s" % etud) + # delete in all tables ! + tables = [ + "notes_appreciations", + "scolar_autorisation_inscription", + "scolar_formsemestre_validation", + "scolar_events", + "notes_notes_log", + "notes_notes", + "notes_moduleimpl_inscription", + "notes_formsemestre_inscription", + "group_membership", + "entreprise_contact", + "etud_annotations", + "scolog", + "admissions", + "adresse", + "absences", + "billet_absence", + "identite", + ] + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + for table in tables: + cursor.execute("delete from %s where etudid=%%(etudid)s" % table, etud) + cnx.commit() + # Inval semestres où il était inscrit: + to_inval = [s["formsemestre_id"] for s in etud["sems"]] + if to_inval: + self.Notes._inval_cache(formsemestre_id_list=to_inval) # > + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + + security.declareProtected(ScoEtudInscrit, "check_group_apogee") + + def check_group_apogee( + self, group_id, REQUEST=None, etat=None, fix=False, fixmail=False + ): + """Verification des codes Apogee et mail de tout un groupe. + Si fix == True, change les codes avec Apogée. + + XXX A re-écrire pour API 2: prendre liste dans l'étape et vérifier à partir de cela. + """ + etat = etat or None + members, group, group_tit, sem, nbdem = sco_groups.get_group_infos( + self, group_id, etat=etat + ) + formsemestre_id = group["formsemestre_id"] + + cnx = self.GetDBConnexion() + H = [ + self.Notes.html_sem_header( + REQUEST, "Etudiants du %s" % (group["group_name"] or "semestre"), sem + ), + '', + "", + ] + nerrs = 0 # nombre d'anomalies détectées + nfix = 0 # nb codes changes + nmailmissing = 0 # nb etuds sans mail + for t in members: + nom, nom_usuel, prenom, etudid, email, code_nip = ( + t["nom"], + t["nom_usuel"], + t["prenom"], + t["etudid"], + t["email"], + t["code_nip"], + ) + infos = sco_portal_apogee.get_infos_apogee(self, nom, prenom) + if not infos: + info_apogee = ( + 'Pas d\'information (Modifier identité)' + % etudid + ) + nerrs += 1 + else: + if len(infos) == 1: + nip_apogee = infos[0]["nip"] + if code_nip != nip_apogee: + if fix: + # Update database + scolars.identite_edit( + cnx, + args={"etudid": etudid, "code_nip": nip_apogee}, + context=self, + ) + info_apogee = ( + 'copié %s' % nip_apogee + ) + nfix += 1 + else: + info_apogee = ( + '%s' % nip_apogee + ) + nerrs += 1 + else: + info_apogee = "ok" + else: + info_apogee = ( + '%d correspondances (Choisir)' + % (len(infos), etudid) + ) + nerrs += 1 + # check mail + if email: + mailstat = "ok" + else: + if fixmail and len(infos) == 1 and "mail" in infos[0]: + mail_apogee = infos[0]["mail"] + adrs = scolars.adresse_list(cnx, {"etudid": etudid}) + if adrs: + adr = adrs[0] # modif adr existante + args = {"adresse_id": adr["adresse_id"], "email": mail_apogee} + scolars.adresse_edit(cnx, args=args) + else: + # creation adresse + args = {"etudid": etudid, "email": mail_apogee} + scolars.adresse_create(cnx, args=args) + mailstat = 'copié' + else: + mailstat = "inconnu" + nmailmissing += 1 + H.append( + '' + % (etudid, nom, nom_usuel, prenom, mailstat, code_nip, info_apogee) + ) + H.append("
    NomNom usuelPrénomMailNIP (ScoDoc)Apogée
    %s%s%s%s%s%s
    ") + H.append("
      ") + if nfix: + H.append("
    • %d codes modifiés
    • " % nfix) + H.append("
    • Codes NIP: %d anomalies détectées
    • " % nerrs) + H.append( + "
    • Adresse mail: %d étudiants sans adresse
    • " % nmailmissing + ) + H.append("
    ") + H.append( + """ +
    + + + + + +
    +

    Retour au semestre + """ + % ( + REQUEST.URL0, + formsemestre_id, + strnone(group_id), + strnone(etat), + formsemestre_id, + ) + ) + H.append( + """ +

    + + + + + +
    +

    Retour au semestre + """ + % ( + REQUEST.URL0, + formsemestre_id, + strnone(group_id), + strnone(etat), + formsemestre_id, + ) + ) + + return "\n".join(H) + self.sco_footer(REQUEST) + + security.declareProtected(ScoEtudInscrit, "form_students_import_excel") + + def form_students_import_excel(self, REQUEST, formsemestre_id=None): + "formulaire import xls" + if formsemestre_id: + sem = sco_formsemestre.get_formsemestre(self.Notes, formsemestre_id) + else: + sem = None + if sem and sem["etat"] != "1": + raise ScoValueError("Modification impossible: semestre verrouille") + H = [ + self.sco_header(REQUEST, page_title="Import etudiants"), + """

    Téléchargement d\'une nouvelle liste d\'etudiants

    +
    +

    A utiliser pour importer de nouveaux étudiants (typiquement au + premier semestre).

    +

    Si les étudiants à inscrire sont déjà dans un autre + semestre, utiliser le menu "Inscriptions (passage des étudiants) + depuis d'autres semestres à partir du semestre destination. +

    +

    Si vous avez un portail Apogée, il est en général préférable d'importer les + étudiants depuis Apogée, via le menu "Synchroniser avec étape Apogée". +

    +
    +

    + L'opération se déroule en deux étapes. Dans un premier temps, + vous téléchargez une feuille Excel type. Vous devez remplir + cette feuille, une ligne décrivant chaque étudiant. Ensuite, + vous indiquez le nom de votre fichier dans la case "Fichier Excel" + ci-dessous, et cliquez sur "Télécharger" pour envoyer au serveur + votre liste. +

    + """, + ] # ' + if sem: + H.append( + """

    Les étudiants importés seront inscrits dans + le semestre %s

    """ + % sem["titremois"] + ) + else: + H.append( + """ +

    Pour inscrire directement les étudiants dans un semestre de + formation, il suffit d'indiquer le code de ce semestre + (qui doit avoir été créé au préalable). Cliquez ici pour afficher les codes +

    + """ + % (self.ScoURL()) + ) + + H.append("""
    1. """) + if formsemestre_id: + H.append( + """ + + """ + ) + else: + H.append("""""") + H.append( + """Obtenir la feuille excel à remplir
    2. +
    3. """ + ) + + F = self.sco_footer(REQUEST) + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ( + "csvfile", + {"title": "Fichier Excel:", "input_type": "file", "size": 40}, + ), + ( + "check_homonyms", + { + "title": "Vérifier les homonymes", + "input_type": "boolcheckbox", + "explanation": "arrète l'importation si plus de 10% d'homonymes", + }, + ), + ( + "require_ine", + { + "title": "Importer INE", + "input_type": "boolcheckbox", + "explanation": "n'importe QUE les étudiants avec nouveau code INE", + }, + ), + ("formsemestre_id", {"input_type": "hidden"}), + ), + initvalues={"check_homonyms": True, "require_ine": False}, + submitlabel="Télécharger", + ) + S = [ + """

      Le fichier Excel décrivant les étudiants doit comporter les colonnes suivantes. +

      Les colonnes peuvent être placées dans n'importe quel ordre, mais +le titre exact (tel que ci-dessous) doit être sur la première ligne. +

      +

      +Les champs avec un astérisque (*) doivent être présents (nulls non autorisés). +

      + + +

      + +""" + ] + for t in ImportScolars.sco_import_format( + with_codesemestre=(formsemestre_id == None) + ): + if int(t[3]): + ast = "" + else: + ast = "*" + S.append( + "" + % (t[0], t[1], t[4], ast) + ) + if tf[0] == 0: + return "\n".join(H) + tf[1] + "" + "\n".join(S) + F + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + return ImportScolars.students_import_excel( + self, + tf[2]["csvfile"], + REQUEST=REQUEST, + formsemestre_id=formsemestre_id, + check_homonyms=tf[2]["check_homonyms"], + require_ine=tf[2]["require_ine"], + ) + + security.declareProtected(ScoEtudInscrit, "import_generate_excel_sample") + + def import_generate_excel_sample(self, REQUEST, with_codesemestre="1"): + "une feuille excel pour importation etudiants" + if with_codesemestre: + with_codesemestre = int(with_codesemestre) + else: + with_codesemestre = 0 + format = ImportScolars.sco_import_format() + data = ImportScolars.sco_import_generate_excel_sample( + format, with_codesemestre, exclude_cols=["photo_filename"], REQUEST=REQUEST + ) + return sco_excel.sendExcelFile(REQUEST, data, "ImportEtudiants.xls") + + # --- Données admission + security.declareProtected(ScoView, "import_generate_admission_sample") + + def import_generate_admission_sample(self, REQUEST, formsemestre_id): + "une feuille excel pour importation données admissions" + group = sco_groups.get_group( + self, sco_groups.get_default_group(self, formsemestre_id) + ) + fmt = ImportScolars.sco_import_format() + data = ImportScolars.sco_import_generate_excel_sample( + fmt, + only_tables=["identite", "admissions", "adresse"], + exclude_cols=["nationalite", "foto", "photo_filename"], + group_ids=[group["group_id"]], + context=self.Notes, + REQUEST=REQUEST, + ) + return sco_excel.sendExcelFile(REQUEST, data, "AdmissionEtudiants.xls") + + # --- Données admission depuis fichier excel (version nov 2016) + security.declareProtected(ScoView, "form_students_import_infos_admissions") + + def form_students_import_infos_admissions(self, REQUEST, formsemestre_id=None): + "formulaire import xls" + sem = sco_formsemestre.get_formsemestre(self.Notes, formsemestre_id) + authuser = REQUEST.AUTHENTICATED_USER + F = self.sco_footer(REQUEST) + if not authuser.has_permission(ScoEtudInscrit, self): + # autorise juste l'export + H = [ + self.sco_header( + REQUEST, + page_title="Export données admissions (Parcoursup ou autre)", + ), + """

      Téléchargement des informations sur l'admission des étudiants

      +

      + Exporter les informations de ScoDoc (classeur Excel) (ce fichier peut être ré-importé après d'éventuelles modifications) +

      +

      Vous n'avez pas le droit d'importer les données

      + """ + % {"formsemestre_id": formsemestre_id}, + ] + return "\n".join(H) + F + + # On a le droit d'importer: + H = [ + self.sco_header(REQUEST, page_title="Import données admissions Parcoursup"), + """

      Téléchargement des informations sur l'admission des étudiants depuis feuilles import Parcoursup

      +
      +

      A utiliser pour renseigner les informations sur l'origine des étudiants (lycées, bac, etc). Ces informations sont facultatives mais souvent utiles pour mieux connaitre les étudiants et aussi pour effectuer des statistiques (résultats suivant le type de bac...). Les données sont affichées sur les fiches individuelles des étudiants.

      +
      +

      + Importer ici la feuille excel utilisée pour envoyer le classement Parcoursup. + Seuls les étudiants actuellement inscrits dans ce semestre ScoDoc seront affectés, + les autres lignes de la feuille seront ignorées. Et seules les colonnes intéressant ScoDoc + seront importées: il est inutile d'éliminer les autres. +
      + Seules les données "admission" seront modifiées (et pas l'identité de l'étudiant). +
      + Les colonnes "nom" et "prenom" sont requises, ou bien une colonne "etudid". +

      +

      + Avant d'importer vos données, il est recommandé d'enregistrer les informations actuelles: + exporter les données actuelles de ScoDoc (ce fichier peut être ré-importé après d'éventuelles modifications) +

      + """ + % {"formsemestre_id": formsemestre_id}, + ] # ' + + type_admission_list = ( + "Autre", + "Parcoursup", + "Parcoursup PC", + "APB", + "APB PC", + "CEF", + "Direct", + ) + + tf = TrivialFormulator( + REQUEST.URL0, + REQUEST.form, + ( + ( + "csvfile", + {"title": "Fichier Excel:", "input_type": "file", "size": 40}, + ), + ( + "type_admission", + { + "title": "Type d'admission", + "explanation": "sera attribué aux étudiants modifiés par cet import n'ayant pas déjà un type", + "input_type": "menu", + "allowed_values": type_admission_list, + }, + ), + ("formsemestre_id", {"input_type": "hidden"}), + ), + submitlabel="Télécharger", + ) + + help_text = ( + """

      Les colonnes importables par cette fonction sont indiquées dans la table ci-dessous. + Seule la première feuille du classeur sera utilisée. +

      + """ + + ImportScolars.adm_table_description_format(self).html() + + """
      """ + ) + + if tf[0] == 0: + return "\n".join(H) + tf[1] + help_text + F + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + return self._students_import_admission( + tf[2]["csvfile"], + type_admission=tf[2]["type_admission"], + REQUEST=REQUEST, + formsemestre_id=formsemestre_id, + ) + + # unpublished + def _students_import_admission( + self, csvfile, type_admission="", REQUEST=None, formsemestre_id=None + ): + "import donnees admission from Excel file (v2016)" + diag = ImportScolars.scolars_import_admission( + csvfile, + self.Notes, + REQUEST, + formsemestre_id=formsemestre_id, + type_admission=type_admission, + ) + if REQUEST: + H = [self.sco_header(REQUEST, page_title="Import données admissions")] + H.append("

      Import terminé !

      ") + H.append( + '

      Continuer

      ' + % "formsemestre_status?formsemestre_id=%s" + % formsemestre_id + ) + if diag: + H.append( + "

      Diagnostic:

      • %s

      " % "
    4. ".join(diag) + ) + + return "\n".join(H) + self.sco_footer(REQUEST) + + security.declareProtected(ScoEtudInscrit, "formsemestre_import_etud_admission") + + def formsemestre_import_etud_admission( + self, formsemestre_id, import_email=True, REQUEST=None + ): + """Reimporte donnees admissions par synchro Portail Apogée""" + ( + no_nip, + unknowns, + changed_mails, + ) = sco_synchro_etuds.formsemestre_import_etud_admission( + self.Notes, formsemestre_id, import_identite=True, import_email=import_email + ) + H = [ + self.Notes.html_sem_header(REQUEST, "Reimport données admission"), + "

      Opération effectuée

      ", + ] + if no_nip: + H.append("

      Attention: étudiants sans NIP: " + str(no_nip) + "

      ") + if unknowns: + H.append( + "

      Attention: étudiants inconnus du portail: codes NIP=" + + str(unknowns) + + "

      " + ) + if changed_mails: + H.append("

      Adresses mails modifiées:

      ") + for (info, new_mail) in changed_mails: + H.append( + "%s: %s devient %s
      " + % (info["nom"], info["email"], new_mail) + ) + return "\n".join(H) + self.sco_footer(REQUEST) + + security.declareProtected(ScoEtudChangeAdr, "photos_import_files_form") + photos_import_files_form = sco_trombino.photos_import_files_form + security.declareProtected(ScoEtudChangeAdr, "photos_generate_excel_sample") + photos_generate_excel_sample = sco_trombino.photos_generate_excel_sample + + # --- Statistiques + security.declareProtected(ScoView, "stat_bac") + + def stat_bac(self, formsemestre_id): + "Renvoie statistisques sur nb d'etudiants par bac" + cnx = self.GetDBConnexion() + sem = sco_formsemestre.get_formsemestre(self, formsemestre_id) + ins = self.Notes.do_formsemestre_inscription_list( + args={"formsemestre_id": formsemestre_id} + ) + Bacs = {} # type bac : nb etud + for i in ins: + etud = scolars.etudident_list(cnx, {"etudid": i["etudid"]})[0] + typebac = "%(bac)s %(specialite)s" % etud + Bacs[typebac] = Bacs.get(typebac, 0) + 1 + return Bacs + + # --- Dump + security.declareProtected(ScoView, "sco_dump_and_send_db") + sco_dump_and_send_db = sco_dump_db.sco_dump_and_send_db + + # + def confirmDialog( + self, + message="

      Confirmer ?

      ", + OK="OK", + Cancel="Annuler", + dest_url="", + cancel_url="", + target_variable="dialog_confirmed", + parameters={}, + add_headers=True, # complete page + REQUEST=None, # required + helpmsg=None, + ): + # dialog de confirmation simple + parameters[target_variable] = 1 + # Attention: la page a pu etre servie en GET avec des parametres + # si on laisse l'url "action" vide, les parametres restent alors que l'on passe en POST... + if not dest_url: + dest_url = REQUEST.URL + # strip remaining parameters from destination url: + dest_url = urllib.splitquery(dest_url)[0] + H = [ + """
      """ % dest_url, + message, + """""" % OK, + ] + if cancel_url: + H.append( + """""" + % (Cancel, cancel_url) + ) + for param in parameters.keys(): + if parameters[param] is None: + parameters[param] = "" + if type(parameters[param]) == type([]): + for e in parameters[param]: + H.append('' % (param, e)) + else: + H.append( + '' + % (param, parameters[param]) + ) + H.append("") + if helpmsg: + H.append('

      ' + helpmsg + "

      ") + if add_headers and REQUEST: + return self.sco_header(REQUEST) + "\n".join(H) + self.sco_footer(REQUEST) + else: + return "\n".join(H) + + # -------------------------------------------------------------------- + # Certaines methodes devant etre appeles en dehors de ZNotes: + security.declareProtected(ScoView, "formsemestre_edit_uecoefs") + formsemestre_edit_uecoefs = sco_formsemestre_edit.formsemestre_edit_uecoefs + + +# +# Product Administration +# + + +def manage_addZScolar( + self, + id="id_ZScolar", + title="The Title for ZScolar Object", + db_cnx_string="the db connexion string", + REQUEST=None, +): + "Add a ZScolar instance to a folder." + zscolar = ZScolar(id, title, db_cnx_string=db_cnx_string) + self._setObject(id, zscolar) + + +# The form used to get the instance id from the user. +def manage_addZScolarForm(context, DeptId, REQUEST=None): + """Form used to create a new ZScolar instance""" + + if not re.match("^[a-zA-Z0-9_]+$", DeptId): + raise ScoValueError("Invalid department id: %s" % DeptId) + + H = [ + context.standard_html_header(context), + "

      Ajout d'un département ScoDoc

      ", + """

      Cette page doit être utilisée pour ajouter un nouveau + département au site.

      + +

      Avant d'ajouter le département, il faut impérativement + avoir préparé la base de données en lançant le script + create_dept.sh nom_du_site en tant que + root sur le serveur. +

      """, + ] + + descr = [ + ( + "db_cnx_string", + { + "title": "DB connexion string", + "size": 32, + "explanation": "laisser vide si BD locale standard", + }, + ), + ("pass2", {"input_type": "hidden", "default": "1"}), + ("DeptId", {"input_type": "hidden", "default": DeptId}), + ] + + tf = TrivialFormulator( + REQUEST.URL0, REQUEST.form, descr, submitlabel="Créer le site ScoDoc" + ) + if tf[0] == 0: + return "\n".join(H) + tf[1] + context.standard_html_footer(context) + elif tf[0] == -1: + return REQUEST.RESPONSE.redirect(REQUEST.URL1) + else: + DeptId = tf[2]["DeptId"].strip() + db_cnx_string = tf[2]["db_cnx_string"].strip() + # default connexion string + if not db_cnx_string: + db_name = "SCO" + DeptId.upper() + db_user = SCO_DEFAULT_SQL_USER + db_cnx_string = "user=%s dbname=%s port=%s" % ( + db_user, + db_name, + SCO_DEFAULT_SQL_PORT, + ) + # vérifie que la bd existe et possede le meme nom de dept. + try: + cnx = psycopg2.connect(db_cnx_string) + cursor = cnx.cursor(cursor_factory=ScoDocCursor) + cursor.execute("select * from sco_prefs where name='DeptName'") + except: + return _simple_error_page( + context, "Echec de la connexion à la BD (%s)" % db_cnx_string, DeptId + ) + r = cursor.dictfetchall() + if not r: + return _simple_error_page( + context, "Pas de departement défini dans la BD", DeptId + ) + if r[0]["value"] != DeptId: + return _simple_error_page( + context, + "La BD ne correspond pas: nom departement='%s'" % r[0]["value"], + DeptId, + ) + # ok, crée instance ScoDoc: + manage_addZScolar( + context, + id="Scolarite", + title="ScoDoc for %s" % DeptId, + db_cnx_string=db_cnx_string, + ) + + return REQUEST.RESPONSE.redirect("index_html") + + +def _simple_error_page(context, msg, DeptId=None): + """Minimal error page (used by installer only). + """ + H = [context.standard_html_header(context), "

      Erreur !

      ", "

      ", msg, "

      "] + if DeptId: + H.append( + '

      Supprimer le dossier %s(très recommandé !)

      ' + % (DeptId, DeptId) + ) + H.append(context.standard_html_footer(context)) + return "\n".join(H) diff --git a/ZopeProducts/README b/ZopeProducts/README new file mode 100644 index 00000000..74226803 --- /dev/null +++ b/ZopeProducts/README @@ -0,0 +1,5 @@ + + Produits Zope2 anciens et adaptes pour ScoDoc + +E. Viennet 2013 + diff --git a/ZopeProducts/ZPsycopgDA/DA.py b/ZopeProducts/ZPsycopgDA/DA.py new file mode 100644 index 00000000..723a6880 --- /dev/null +++ b/ZopeProducts/ZPsycopgDA/DA.py @@ -0,0 +1,372 @@ +# ZPsycopgDA/DA.py - ZPsycopgDA Zope product: Database Connection +# +# Copyright (C) 2004-2010 Federico Di Gregorio +# +# psycopg2 is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# psycopg2 is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. + +# Import modules needed by _psycopg to allow tools like py2exe to do +# their work without bothering about the module dependencies. + + +import sys +import time +import db +import re + +import Acquisition +import Shared.DC.ZRDB.Connection + +from db import DB +from Globals import HTMLFile +from ExtensionClass import Base +from App.Dialogs import MessageDialog +from DateTime import DateTime + +# ImageFile is deprecated in Zope >= 2.9 +try: + from App.ImageFile import ImageFile +except ImportError: + # Zope < 2.9. If PIL's installed with a .pth file, we're probably + # hosed. + from ImageFile import ImageFile + +# import psycopg and functions/singletons needed for date/time conversions + +import psycopg2 +from psycopg2 import NUMBER, STRING, ROWID, DATETIME +from psycopg2.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE +from psycopg2.extensions import TIME, INTERVAL +from psycopg2.extensions import new_type, register_type + + +# add a new connection to a folder + +manage_addZPsycopgConnectionForm = HTMLFile('dtml/add',globals()) + +def manage_addZPsycopgConnection(self, id, title, connection_string, + zdatetime=None, tilevel=2, + encoding='', check=None, REQUEST=None): + """Add a DB connection to a folder.""" + self._setObject(id, Connection(id, title, connection_string, + zdatetime, check, tilevel, encoding)) + if REQUEST is not None: return self.manage_main(self, REQUEST) + + +# the connection object + +class Connection(Shared.DC.ZRDB.Connection.Connection): + """ZPsycopg Connection.""" + _isAnSQLConnection = 1 + + id = 'Psycopg2_database_connection' + database_type = 'Psycopg2' + meta_type = title = 'Z Psycopg 2 Database Connection' + icon = 'misc_/conn' + + def __init__(self, id, title, connection_string, + zdatetime, check=None, tilevel=2, encoding='UTF-8'): + self.zdatetime = zdatetime + self.id = str(id) + self.edit(title, connection_string, zdatetime, + check=check, tilevel=tilevel, encoding=encoding) + + def factory(self): + return DB + + ## connection parameters editing ## + + def edit(self, title, connection_string, + zdatetime, check=None, tilevel=2, encoding='UTF-8'): + self.title = title + self.connection_string = connection_string + self.zdatetime = zdatetime + self.tilevel = tilevel + self.encoding = encoding + + if check: self.connect(self.connection_string) + + manage_properties = HTMLFile('dtml/edit', globals()) + + def manage_edit(self, title, connection_string, + zdatetime=None, check=None, tilevel=2, encoding='UTF-8', + REQUEST=None): + """Edit the DB connection.""" + self.edit(title, connection_string, zdatetime, + check=check, tilevel=tilevel, encoding=encoding) + if REQUEST is not None: + msg = "Connection edited." + return self.manage_main(self,REQUEST,manage_tabs_message=msg) + + def connect(self, s): + try: + self._v_database_connection.close() + except: + pass + + # check psycopg version and raise exception if does not match + check_psycopg_version(psycopg2.__version__) + + self._v_connected = '' + dbf = self.factory() + + # TODO: let the psycopg exception propagate, or not? + self._v_database_connection = dbf( + self.connection_string, self.tilevel, self.get_type_casts(), self.encoding) + self._v_database_connection.open() + self._v_connected = DateTime() + + return self + + def get_type_casts(self): + # note that in both cases order *is* important + if self.zdatetime: + return ZDATETIME, ZDATE, ZTIME + else: + return DATETIME, DATE, TIME + + ## browsing and table/column management ## + + manage_options = Shared.DC.ZRDB.Connection.Connection.manage_options + # + ( + # {'label': 'Browse', 'action':'manage_browse'},) + + #manage_tables = HTMLFile('dtml/tables', globals()) + #manage_browse = HTMLFile('dtml/browse', globals()) + + info = None + + def table_info(self): + return self._v_database_connection.table_info() + + + def __getitem__(self, name): + if name == 'tableNamed': + if not hasattr(self, '_v_tables'): self.tpValues() + return self._v_tables.__of__(self) + raise KeyError, name + + def tpValues(self): + res = [] + conn = self._v_database_connection + for d in conn.tables(rdb=0): + try: + name = d['TABLE_NAME'] + b = TableBrowser() + b.__name__ = name + b._d = d + b._c = c + try: + b.icon = table_icons[d['TABLE_TYPE']] + except: + pass + r.append(b) + except: + pass + return res + +def check_psycopg_version(version): + """ + Check that the psycopg version used is compatible with the zope adpter. + """ + try: + m = re.match(r'\d+\.\d+(\.\d+)?', version.split(' ')[0]) + tver = tuple(map(int, m.group().split('.'))) + except: + raise ImportError("failed to parse psycopg version %s" % version) + + if tver < (2, 4): + raise ImportError("psycopg version %s is too old" % version) + + if tver in ((2,4,2), (2,4,3)): + raise ImportError("psycopg version %s is known to be buggy" % version) + + +## database connection registration data ## + +classes = (Connection,) + +meta_types = ({'name':'Z Psycopg 2 Database Connection', + 'action':'manage_addZPsycopgConnectionForm'},) + +folder_methods = { + 'manage_addZPsycopgConnection': manage_addZPsycopgConnection, + 'manage_addZPsycopgConnectionForm': manage_addZPsycopgConnectionForm} + +__ac_permissions__ = ( + ('Add Z Psycopg Database Connections', + ('manage_addZPsycopgConnectionForm', 'manage_addZPsycopgConnection')),) + +# add icons + +misc_={'conn': ImageFile('icons/DBAdapterFolder_icon.gif', globals())} + +for icon in ('table', 'view', 'stable', 'what', 'field', 'text', 'bin', + 'int', 'float', 'date', 'time', 'datetime'): + misc_[icon] = ImageFile('icons/%s.gif' % icon, globals()) + + +## zope-specific psycopg typecasters ## + +# convert an ISO timestamp string from postgres to a Zope DateTime object +def _cast_DateTime(iso, curs): + if iso: + if iso in ['-infinity', 'infinity']: + return iso + else: + return DateTime(iso) + +# convert an ISO date string from postgres to a Zope DateTime object +def _cast_Date(iso, curs): + if iso: + if iso in ['-infinity', 'infinity']: + return iso + else: + return DateTime(iso) + +# Convert a time string from postgres to a Zope DateTime object. +# NOTE: we set the day as today before feeding to DateTime so +# that it has the same DST settings. +def _cast_Time(iso, curs): + if iso: + if iso in ['-infinity', 'infinity']: + return iso + else: + return DateTime(time.strftime('%Y-%m-%d %H:%M:%S', + time.localtime(time.time())[:3]+ + time.strptime(iso[:8], "%H:%M:%S")[3:])) + +# NOTE: we don't cast intervals anymore because they are passed +# untouched to Zope. +def _cast_Interval(iso, curs): + return iso + +ZDATETIME = new_type((1184, 1114), "ZDATETIME", _cast_DateTime) +ZINTERVAL = new_type((1186,), "ZINTERVAL", _cast_Interval) +ZDATE = new_type((1082,), "ZDATE", _cast_Date) +ZTIME = new_type((1083,), "ZTIME", _cast_Time) + + +## table browsing helpers ## + +class TableBrowserCollection(Acquisition.Implicit): + pass + +class Browser(Base): + def __getattr__(self, name): + try: + return self._d[name] + except KeyError: + raise AttributeError, name + +class values: + def len(self): + return 1 + + def __getitem__(self, i): + try: + return self._d[i] + except AttributeError: + pass + self._d = self._f() + return self._d[i] + +class TableBrowser(Browser, Acquisition.Implicit): + icon = 'what' + Description = check = '' + info = HTMLFile('table_info', globals()) + menu = HTMLFile('table_menu', globals()) + + def tpValues(self): + v = values() + v._f = self.tpValues_ + return v + + def tpValues_(self): + r=[] + tname=self.__name__ + for d in self._c.columns(tname): + b=ColumnBrowser() + b._d=d + try: b.icon=field_icons[d['Type']] + except: pass + b.TABLE_NAME=tname + r.append(b) + return r + + def tpId(self): return self._d['TABLE_NAME'] + def tpURL(self): return "Table/%s" % self._d['TABLE_NAME'] + def Name(self): return self._d['TABLE_NAME'] + def Type(self): return self._d['TABLE_TYPE'] + + manage_designInput=HTMLFile('designInput',globals()) + def manage_buildInput(self, id, source, default, REQUEST=None): + "Create a database method for an input form" + args=[] + values=[] + names=[] + columns=self._columns + for i in range(len(source)): + s=source[i] + if s=='Null': continue + c=columns[i] + d=default[i] + t=c['Type'] + n=c['Name'] + names.append(n) + if s=='Argument': + values.append("'" % + (n, vartype(t))) + a='%s%s' % (n, boboType(t)) + if d: a="%s=%s" % (a,d) + args.append(a) + elif s=='Property': + values.append("'" % + (n, vartype(t))) + else: + if isStringType(t): + if find(d,"\'") >= 0: d=join(split(d,"\'"),"''") + values.append("'%s'" % d) + elif d: + values.append(str(d)) + else: + raise ValueError, ( + 'no default was given for %s' % n) + +class ColumnBrowser(Browser): + icon='field' + + def check(self): + return ('\t' % + (self.TABLE_NAME, self._d['Name'])) + def tpId(self): return self._d['Name'] + def tpURL(self): return "Column/%s" % self._d['Name'] + def Description(self): + d=self._d + if d['Scale']: + return " %(Type)s(%(Precision)s,%(Scale)s) %(Nullable)s" % d + else: + return " %(Type)s(%(Precision)s) %(Nullable)s" % d + +table_icons={ + 'TABLE': 'table', + 'VIEW':'view', + 'SYSTEM_TABLE': 'stable', + } + +field_icons={ + NUMBER.name: 'i', + STRING.name: 'text', + DATETIME.name: 'date', + INTEGER.name: 'int', + FLOAT.name: 'float', + BOOLEAN.name: 'bin', + ROWID.name: 'int' + } diff --git a/ZopeProducts/ZPsycopgDA/__init__.py b/ZopeProducts/ZPsycopgDA/__init__.py new file mode 100644 index 00000000..b501cbfd --- /dev/null +++ b/ZopeProducts/ZPsycopgDA/__init__.py @@ -0,0 +1,29 @@ +# ZPsycopgDA/__init__.py - ZPsycopgDA Zope product +# +# Copyright (C) 2004-2010 Federico Di Gregorio +# +# psycopg2 is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# psycopg2 is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. + +# Import modules needed by _psycopg to allow tools like py2exe to do +# their work without bothering about the module dependencies. + +__doc__ = "ZPsycopg Database Adapter Registration." +__version__ = '2.4.6' + +import DA + +def initialize(context): + context.registerClass( + DA.Connection, + permission = 'Add Z Psycopg 2 Database Connections', + constructors = (DA.manage_addZPsycopgConnectionForm, + DA.manage_addZPsycopgConnection), + icon = 'icons/DBAdapterFolder_icon.gif') diff --git a/ZopeProducts/ZPsycopgDA/db.py b/ZopeProducts/ZPsycopgDA/db.py new file mode 100644 index 00000000..a272c02d --- /dev/null +++ b/ZopeProducts/ZPsycopgDA/db.py @@ -0,0 +1,209 @@ +# ZPsycopgDA/db.py - query execution +# +# Copyright (C) 2004-2010 Federico Di Gregorio +# +# psycopg2 is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# psycopg2 is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. + +# Import modules needed by _psycopg to allow tools like py2exe to do +# their work without bothering about the module dependencies. + +from Shared.DC.ZRDB.TM import TM +from Shared.DC.ZRDB import dbi_db + +from ZODB.POSException import ConflictError + +import site +import pool + +import psycopg2 +from psycopg2.extensions import INTEGER, LONGINTEGER, FLOAT, BOOLEAN, DATE, TIME +from psycopg2.extensions import TransactionRollbackError, register_type +from psycopg2 import NUMBER, STRING, ROWID, DATETIME + + +# the DB object, managing all the real query work + +class DB(TM, dbi_db.DB): + + _p_oid = _p_changed = _registered = None + + def __init__(self, dsn, tilevel, typecasts, enc='utf-8'): + self.dsn = dsn + self.tilevel = tilevel + self.typecasts = typecasts + if enc is None or enc == "": + self.encoding = "utf-8" + else: + self.encoding = enc + self.failures = 0 + self.calls = 0 + self.make_mappings() + + def getconn(self, init=True): + # if init is False we are trying to get hold on an already existing + # connection, so we avoid to (re)initialize it risking errors. + conn = pool.getconn(self.dsn) + if init: + # use set_session where available as in these versions + # set_isolation_level generates an extra query. + if psycopg2.__version__ >= '2.4.2': + conn.set_session(isolation_level=int(self.tilevel)) + else: + conn.set_isolation_level(int(self.tilevel)) + conn.set_client_encoding(self.encoding) + for tc in self.typecasts: + register_type(tc, conn) + return conn + + def putconn(self, close=False): + try: + conn = pool.getconn(self.dsn, False) + except AttributeError: + pass + pool.putconn(self.dsn, conn, close) + + def getcursor(self): + conn = self.getconn(False) + return conn.cursor() + + def _finish(self, *ignored): + try: + conn = self.getconn(False) + conn.commit() + self.putconn() + except AttributeError: + pass + + def _abort(self, *ignored): + try: + conn = self.getconn(False) + conn.rollback() + self.putconn() + except AttributeError: + pass + + def open(self): + # this will create a new pool for our DSN if not already existing, + # then get and immediately release a connection + self.getconn() + self.putconn() + + def close(self): + # FIXME: if this connection is closed we flush all the pool associated + # with the current DSN; does this makes sense? + pool.flushpool(self.dsn) + + def sortKey(self): + return 1 + + def make_mappings(self): + """Generate the mappings used later by self.convert_description().""" + self.type_mappings = {} + for t, s in [(INTEGER,'i'), (LONGINTEGER, 'i'), (NUMBER, 'n'), + (BOOLEAN,'n'), (ROWID, 'i'), + (DATETIME, 'd'), (DATE, 'd'), (TIME, 'd')]: + for v in t.values: + self.type_mappings[v] = (t, s) + + def convert_description(self, desc, use_psycopg_types=False): + """Convert DBAPI-2.0 description field to Zope format.""" + items = [] + for name, typ, width, ds, p, scale, null_ok in desc: + m = self.type_mappings.get(typ, (STRING, 's')) + items.append({ + 'name': name, + 'type': use_psycopg_types and m[0] or m[1], + 'width': width, + 'precision': p, + 'scale': scale, + 'null': null_ok, + }) + return items + + ## tables and rows ## + + def tables(self, rdb=0, _care=('TABLE', 'VIEW')): + self._register() + c = self.getcursor() + c.execute( + "SELECT t.tablename AS NAME, 'TABLE' AS TYPE " + " FROM pg_tables t WHERE tableowner <> 'postgres' " + "UNION SELECT v.viewname AS NAME, 'VIEW' AS TYPE " + " FROM pg_views v WHERE viewowner <> 'postgres' " + "UNION SELECT t.tablename AS NAME, 'SYSTEM_TABLE\' AS TYPE " + " FROM pg_tables t WHERE tableowner = 'postgres' " + "UNION SELECT v.viewname AS NAME, 'SYSTEM_TABLE' AS TYPE " + "FROM pg_views v WHERE viewowner = 'postgres'") + res = [] + for name, typ in c.fetchall(): + if typ in _care: + res.append({'TABLE_NAME': name, 'TABLE_TYPE': typ}) + self.putconn() + return res + + def columns(self, table_name): + self._register() + c = self.getcursor() + try: + r = c.execute('SELECT * FROM "%s" WHERE 1=0' % table_name) + except: + return () + self.putconn() + return self.convert_description(c.description, True) + + ## query execution ## + + def query(self, query_string, max_rows=None, query_data=None): + self._register() + self.calls = self.calls+1 + + desc = () + res = [] + nselects = 0 + + c = self.getcursor() + + try: + for qs in [x for x in query_string.split('\0') if x]: + try: + if query_data: + c.execute(qs, query_data) + else: + c.execute(qs) + except TransactionRollbackError: + # Ha, here we have to look like we are the ZODB raising conflict errrors, raising ZPublisher.Publish.Retry just doesn't work + #logging.debug("Serialization Error, retrying transaction", exc_info=True) + raise ConflictError("TransactionRollbackError from psycopg2") + except psycopg2.OperationalError: + #logging.exception("Operational error on connection, closing it.") + try: + # Only close our connection + self.putconn(True) + except: + #logging.debug("Something went wrong when we tried to close the pool", exc_info=True) + pass + if c.description is not None: + nselects += 1 + if c.description != desc and nselects > 1: + raise psycopg2.ProgrammingError( + 'multiple selects in single query not allowed') + if max_rows: + res = c.fetchmany(max_rows) + else: + res = c.fetchall() + desc = c.description + self.failures = 0 + + except StandardError, err: + self._abort() + raise err + + return self.convert_description(desc), res diff --git a/ZopeProducts/ZPsycopgDA/dtml/add.dtml b/ZopeProducts/ZPsycopgDA/dtml/add.dtml new file mode 100644 index 00000000..330a001b --- /dev/null +++ b/ZopeProducts/ZPsycopgDA/dtml/add.dtml @@ -0,0 +1,108 @@ + + + + +

      +A Zope Psycopg 2 Database Connection is used to connect and execute +queries on a PostgreSQL database. +

      + +

      +In the form below Connection String (also called the Data Source Name +or DSN for short) is a string... (TODO: finish docs) +

      + +
      +
    5. AttributTypeDescription
      %s%s%s%s
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      + Id +
      +
      + +
      +
      + Title +
      +
      + +
      +
      + Connection string +
      +
      + +
      +
      + Connect immediately +
      +
      + +
      +
      + Use Zope's internal DateTime +
      +
      + +
      +
      + Transaction isolation level +
      +
      + +
      +
      + Encoding +
      +
      + +
      +
      + +
      +
      + + + diff --git a/ZopeProducts/ZPsycopgDA/dtml/browse.dtml b/ZopeProducts/ZPsycopgDA/dtml/browse.dtml new file mode 100644 index 00000000..deffd0ab --- /dev/null +++ b/ZopeProducts/ZPsycopgDA/dtml/browse.dtml @@ -0,0 +1,11 @@ + + <dtml-var title_or_id >tables + + + + <dtml-var Type> + + + + diff --git a/ZopeProducts/ZPsycopgDA/dtml/edit.dtml b/ZopeProducts/ZPsycopgDA/dtml/edit.dtml new file mode 100644 index 00000000..cffb43bf --- /dev/null +++ b/ZopeProducts/ZPsycopgDA/dtml/edit.dtml @@ -0,0 +1,84 @@ + + + +

      + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      + Title +
      +
      + +
      +
      + Connection string +
      +
      + +
      +
      + Use Zope's internal DateTime +
      +
      + checked="YES" /> +
      +
      + Transaction isolation level +
      +
      + +
      +
      + Encoding +
      +
      + +
      +
      + +
      +
      +
      + + diff --git a/ZopeProducts/ZPsycopgDA/dtml/table_info.dtml b/ZopeProducts/ZPsycopgDA/dtml/table_info.dtml new file mode 100644 index 00000000..639c23fd --- /dev/null +++ b/ZopeProducts/ZPsycopgDA/dtml/table_info.dtml @@ -0,0 +1,7 @@ + + + + owned by +
      + + diff --git a/ZopeProducts/ZPsycopgDA/icons/DBAdapterFolder_icon.gif b/ZopeProducts/ZPsycopgDA/icons/DBAdapterFolder_icon.gif new file mode 100755 index 00000000..ced0ef26 Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/DBAdapterFolder_icon.gif differ diff --git a/ZopeProducts/ZPsycopgDA/icons/bin.gif b/ZopeProducts/ZPsycopgDA/icons/bin.gif new file mode 100644 index 00000000..e4691265 Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/bin.gif differ diff --git a/ZopeProducts/ZPsycopgDA/icons/date.gif b/ZopeProducts/ZPsycopgDA/icons/date.gif new file mode 100644 index 00000000..0d88a573 Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/date.gif differ diff --git a/ZopeProducts/ZPsycopgDA/icons/datetime.gif b/ZopeProducts/ZPsycopgDA/icons/datetime.gif new file mode 100644 index 00000000..faa540b1 Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/datetime.gif differ diff --git a/ZopeProducts/ZPsycopgDA/icons/field.gif b/ZopeProducts/ZPsycopgDA/icons/field.gif new file mode 100644 index 00000000..9bf8692b Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/field.gif differ diff --git a/ZopeProducts/ZPsycopgDA/icons/float.gif b/ZopeProducts/ZPsycopgDA/icons/float.gif new file mode 100644 index 00000000..dd427299 Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/float.gif differ diff --git a/ZopeProducts/ZPsycopgDA/icons/int.gif b/ZopeProducts/ZPsycopgDA/icons/int.gif new file mode 100644 index 00000000..ef2c5e36 Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/int.gif differ diff --git a/ZopeProducts/ZPsycopgDA/icons/stable.gif b/ZopeProducts/ZPsycopgDA/icons/stable.gif new file mode 100644 index 00000000..acdd37df Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/stable.gif differ diff --git a/ZopeProducts/ZPsycopgDA/icons/table.gif b/ZopeProducts/ZPsycopgDA/icons/table.gif new file mode 100644 index 00000000..cce83bea Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/table.gif differ diff --git a/ZopeProducts/ZPsycopgDA/icons/text.gif b/ZopeProducts/ZPsycopgDA/icons/text.gif new file mode 100644 index 00000000..a2e5aab6 Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/text.gif differ diff --git a/ZopeProducts/ZPsycopgDA/icons/time.gif b/ZopeProducts/ZPsycopgDA/icons/time.gif new file mode 100644 index 00000000..6d089150 Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/time.gif differ diff --git a/ZopeProducts/ZPsycopgDA/icons/view.gif b/ZopeProducts/ZPsycopgDA/icons/view.gif new file mode 100644 index 00000000..71b30de1 Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/view.gif differ diff --git a/ZopeProducts/ZPsycopgDA/icons/what.gif b/ZopeProducts/ZPsycopgDA/icons/what.gif new file mode 100644 index 00000000..8b5516e3 Binary files /dev/null and b/ZopeProducts/ZPsycopgDA/icons/what.gif differ diff --git a/ZopeProducts/ZPsycopgDA/pool.py b/ZopeProducts/ZPsycopgDA/pool.py new file mode 100644 index 00000000..b47f46cc --- /dev/null +++ b/ZopeProducts/ZPsycopgDA/pool.py @@ -0,0 +1,193 @@ +# ZPsycopgDA/pool.py - ZPsycopgDA Zope product: connection pooling +# +# Copyright (C) 2004-2010 Federico Di Gregorio +# +# psycopg2 is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# psycopg2 is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. + +# Import modules needed by _psycopg to allow tools like py2exe to do +# their work without bothering about the module dependencies. + +# All the connections are held in a pool of pools, directly accessible by the +# ZPsycopgDA code in db.py. + +import threading +import psycopg2 +from psycopg2.pool import PoolError + + +class AbstractConnectionPool(object): + """Generic key-based pooling code.""" + + def __init__(self, minconn, maxconn, *args, **kwargs): + """Initialize the connection pool. + + New 'minconn' connections are created immediately calling 'connfunc' + with given parameters. The connection pool will support a maximum of + about 'maxconn' connections. + """ + self.minconn = minconn + self.maxconn = maxconn + self.closed = False + + self._args = args + self._kwargs = kwargs + + self._pool = [] + self._used = {} + self._rused = {} # id(conn) -> key map + self._keys = 0 + + for i in range(self.minconn): + self._connect() + + def _connect(self, key=None): + """Create a new connection and assign it to 'key' if not None.""" + conn = psycopg2.connect(*self._args, **self._kwargs) + if key is not None: + self._used[key] = conn + self._rused[id(conn)] = key + else: + self._pool.append(conn) + return conn + + def _getkey(self): + """Return a new unique key.""" + self._keys += 1 + return self._keys + + def _getconn(self, key=None): + """Get a free connection and assign it to 'key' if not None.""" + if self.closed: raise PoolError("connection pool is closed") + if key is None: key = self._getkey() + + if key in self._used: + return self._used[key] + + if self._pool: + self._used[key] = conn = self._pool.pop() + self._rused[id(conn)] = key + return conn + else: + if len(self._used) == self.maxconn: + raise PoolError("connection pool exausted") + return self._connect(key) + + def _putconn(self, conn, key=None, close=False): + """Put away a connection.""" + if self.closed: raise PoolError("connection pool is closed") + if key is None: key = self._rused[id(conn)] + + if not key: + raise PoolError("trying to put unkeyed connection") + + if len(self._pool) < self.minconn and not close: + self._pool.append(conn) + else: + conn.close() + + # here we check for the presence of key because it can happen that a + # thread tries to put back a connection after a call to close + if not self.closed or key in self._used: + del self._used[key] + del self._rused[id(conn)] + + def _closeall(self): + """Close all connections. + + Note that this can lead to some code fail badly when trying to use + an already closed connection. If you call .closeall() make sure + your code can deal with it. + """ + if self.closed: raise PoolError("connection pool is closed") + for conn in self._pool + list(self._used.values()): + try: + conn.close() + except: + pass + self.closed = True + + +class PersistentConnectionPool(AbstractConnectionPool): + """A pool that assigns persistent connections to different threads. + + Note that this connection pool generates by itself the required keys + using the current thread id. This means that until a thread puts away + a connection it will always get the same connection object by successive + `!getconn()` calls. This also means that a thread can't use more than one + single connection from the pool. + """ + + def __init__(self, minconn, maxconn, *args, **kwargs): + """Initialize the threading lock.""" + import threading + AbstractConnectionPool.__init__( + self, minconn, maxconn, *args, **kwargs) + self._lock = threading.Lock() + + # we we'll need the thread module, to determine thread ids, so we + # import it here and copy it in an instance variable + import thread + self.__thread = thread + + def getconn(self): + """Generate thread id and return a connection.""" + key = self.__thread.get_ident() + self._lock.acquire() + try: + return self._getconn(key) + finally: + self._lock.release() + + def putconn(self, conn=None, close=False): + """Put away an unused connection.""" + key = self.__thread.get_ident() + self._lock.acquire() + try: + if not conn: conn = self._used[key] + self._putconn(conn, key, close) + finally: + self._lock.release() + + def closeall(self): + """Close all connections (even the one currently in use.)""" + self._lock.acquire() + try: + self._closeall() + finally: + self._lock.release() + + +_connections_pool = {} +_connections_lock = threading.Lock() + +def getpool(dsn, create=True): + _connections_lock.acquire() + try: + if not _connections_pool.has_key(dsn) and create: + _connections_pool[dsn] = \ + PersistentConnectionPool(4, 200, dsn) + finally: + _connections_lock.release() + return _connections_pool[dsn] + +def flushpool(dsn): + _connections_lock.acquire() + try: + _connections_pool[dsn].closeall() + del _connections_pool[dsn] + finally: + _connections_lock.release() + +def getconn(dsn, create=True): + return getpool(dsn, create=create).getconn() + +def putconn(dsn, conn, close=False): + getpool(dsn).putconn(conn, close=close) diff --git a/ZopeProducts/exUserFolder/AuthSources/__init__.py b/ZopeProducts/exUserFolder/AuthSources/__init__.py new file mode 100644 index 00000000..dedaed0a --- /dev/null +++ b/ZopeProducts/exUserFolder/AuthSources/__init__.py @@ -0,0 +1,47 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: __init__.py,v 1.1 2004/11/10 14:15:34 akm Exp $ + +#import etcAuthSource +#import httpsAuthSource +#import mysqlAuthSource +import pgAuthSource +#import pgAuthSourceAlt +#import radiusAuthSource +#import smbAuthSource +#import usAuthSource +#import zodbAuthSource +#import zodbBTreeAuthSource + +# +# These have special requirements for external libraries +# that my not be present. +# + +# try: +# import nisAuthSource +# except: +# pass + +# try: +# import LDAPAuthSource +# except: +# pass diff --git a/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/.cvsignore b/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/.cvsignore new file mode 100644 index 00000000..57c4bccb --- /dev/null +++ b/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/.cvsignore @@ -0,0 +1,4 @@ +*.pyc +*.pyo +*~ +.*.swp diff --git a/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/__init__.py b/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/__init__.py new file mode 100644 index 00000000..a23a103c --- /dev/null +++ b/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/__init__.py @@ -0,0 +1,2 @@ +# $Id: __init__.py,v 1.1 2004/11/10 14:15:36 akm Exp $ +import pgAuthSource diff --git a/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/manage_addpgAuthSourceForm.dtml b/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/manage_addpgAuthSourceForm.dtml new file mode 100644 index 00000000..ff0790c3 --- /dev/null +++ b/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/manage_addpgAuthSourceForm.dtml @@ -0,0 +1,40 @@ + +
      + +"> + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Database Connection: + +
      Table Name:
      Username Column:
      Password Column:
      Roles Column:

      Add">
      +
      + diff --git a/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/manage_editpgAuthSourceForm.dtml b/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/manage_editpgAuthSourceForm.dtml new file mode 100644 index 00000000..7cd811cf --- /dev/null +++ b/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/manage_editpgAuthSourceForm.dtml @@ -0,0 +1,37 @@ + + +
      + + + + + + + + + + + + + + + + + + + + + + + + +
      Database Connection: + +
      Table Name:">
      Username Column:">
      Password Column:">
      Roles Column:">

      Edit ">
      +
      + diff --git a/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/pgAuthSource.py b/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/pgAuthSource.py new file mode 100644 index 00000000..bffe3b71 --- /dev/null +++ b/ZopeProducts/exUserFolder/AuthSources/pgAuthSource/pgAuthSource.py @@ -0,0 +1,333 @@ +# +# Extensible User Folder +# +# Postgres Authentication Source for exUserFolder +# +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: pgAuthSource.py,v 1.1 2004/11/10 14:15:36 akm Exp $ + +# +# This class only authenticates users, it stores no properties. +# + +import string,Acquisition + +from Globals import HTMLFile, MessageDialog, INSTANCE_HOME + +from OFS.Folder import Folder + +from Products.ZSQLMethods.SQL import SQL + +from Products.exUserFolder.exUserFolder import exUserFolder +from Products.exUserFolder.Plugins import PluginRegister + +try: + from crypt import crypt +except: + from Products.exUserFolder.fcrypt.fcrypt import crypt + +# debug XXX +# def xLOG(msg): +# f = open('/tmp/debug.log','a') +# f.write(msg+'\n') +# f.close() + +def manage_addpgAuthSource(self, REQUEST): + """ Add a Postgres Auth Source """ + + connection=REQUEST['pgauth_connection'] + table=REQUEST['pgauth_table'] + usernameColumn=REQUEST['pgauth_usernameColumn'] + passwordColumn=REQUEST['pgauth_passwordColumn'] + rolesColumn=REQUEST['pgauth_rolesColumn'] + o = pgAuthSource(connection, table, usernameColumn, passwordColumn, + rolesColumn) + self._setObject('pgAuthSource', o, None, None, 0) + o=getattr(self,'pgAuthSource') + if hasattr(o, 'postInitialisation'): + o.postInitialisation(REQUEST) + + self.currentAuthSource=o + return '' + +manage_addpgAuthSourceForm=HTMLFile('manage_addpgAuthSourceForm', globals()) +manage_editpgAuthSourceForm=HTMLFile('manage_editpgAuthSourceForm', globals()) + +class pgAuthSource(Folder): + """ Authenticate Users against a Postgres Database """ + + meta_type='Authentication Source' + title='Postgresql Authentication' + + icon ='misc_/exUserFolder/exUserFolderPlugin.gif' + + manage_tabs=Acquisition.Acquired + + manage_editForm=manage_editpgAuthSourceForm + + # + # You can define this to go off and do the authentication instead of + # using the basic one inside the User Object + # + remoteAuthMethod=None + + def __init__(self, connection, table, usernameColumn, passwordColumn, + rolesColumn): + self.id='pgAuthSource' + self.connection=connection + self.table=table + self.usernameColumn=usernameColumn + self.passwordColumn=passwordColumn + self.rolesColumn=rolesColumn + self.addSQLQueries() + + def manage_editAuthSource(self, REQUEST): + """ Edit a Postgres Auth Source """ + + self.connection=REQUEST['pgauth_connection'] + self.table=REQUEST['pgauth_table'] + self.usernameColumn=REQUEST['pgauth_usernameColumn'] + self.passwordColumn=REQUEST['pgauth_passwordColumn'] + self.rolesColumn=REQUEST['pgauth_rolesColumn'] + self.delSQLQueries() + self.addSQLQueries() # Re-add queries with new parameters + + def createUser(self, username, password, roles): + """ Add A Username """ + + if type(roles) != type([]): + if roles: + roles=list(roles) + else: + roles=[] + + rolestring='' + for role in roles: + rolestring=rolestring+role+',' + + rolestring=rolestring[:-1] + secret=self.cryptPassword(username, password) + self.sqlInsertUser(username=username, + password=secret, + roles=rolestring) + self._v_lastUser={} + + def updateUser(self, username, password, roles): + if type(roles) != type([]): + if roles: + roles=list(roles) + else: + roles=[] + + rolestring='' + for role in roles: + print role + rolestring=rolestring+role+',' + + rolestring=rolestring[:-1] + + # Don't change passwords if it's null + if password: + secret=self.cryptPassword(username, password) + self.sqlUpdateUserPassword(username=username, + password=secret) + + self.sqlUpdateUser(username=username, + roles=rolestring) + self._v_lastUser={} + + def delSQLQueries(self): + sqllist=self.objectIds('Z SQL Method') + self.manage_delObjects(ids=sqllist) + + def addSQLQueries(self): + sqlListUsers=SQL( + 'sqlListUsers', + 'List All Users', + self.connection, + 'table=%s'%(self.table), + _sqlListUsers) + + self._setObject('sqlListUsers', sqlListUsers) + + sqlListOneUser=SQL( + 'sqlListOneUser', + 'List ONE User', + self.connection, + 'table=%s usernameColumn=%s username:string'%( + self.table, self.usernameColumn), + _sqlListOneUser) + + self._setObject('sqlListOneUser', sqlListOneUser) + + sqlDeleteOneUser=SQL( + 'sqlDeleteOneUser', + 'Delete One User', + self.connection, + 'table=%s usernameColumn=%s username:string'%( + self.table,self.usernameColumn), + _sqlDeleteOneUser) + + self._setObject('sqlDeleteOneUser', sqlDeleteOneUser) + + sqlInsertUser=SQL( + 'sqlInsertUser', + 'Insert One User', + self.connection, + 'table=%s usernameColumn=%s passwordColumn=%s rolesColumn=%s username:string password:string roles:string'%( + self.table, self.usernameColumn, self.passwordColumn, self.rolesColumn), + _sqlInsertUser) + + self._setObject('sqlInsertUser', sqlInsertUser) + + sqlUpdateUser=SQL( + 'sqlUpdateUser', + 'Update User', + self.connection, + 'table=%s rolesColumn=%s username:string roles:string'%(self.table, self.rolesColumn), + _sqlUpdateUser) + + self._setObject('sqlUpdateUser', sqlUpdateUser) + + sqlUpdateUserPassword=SQL( + 'sqlUpdateUserPassword', + 'Update just the password', + self.connection, + 'table=%s usernameColumn=%s passwordColumn=%s username:string password:string'%(self.table, self.usernameColumn, self.passwordColumn), + _sqlUpdateUserPassword) + + self._setObject('sqlUpdateUserPassword', sqlUpdateUserPassword) + + def cryptPassword_old(self, username, password): + salt =username[:2] + secret = crypt(password, salt) + return secret + + def deleteUsers(self, userids): + for uid in userids: + self.sqlDeleteOneUser(username=uid) + self._v_lastUser={} + + def listUserNames(self): + """Returns a real list of user names """ + users = [] + result=self.sqlListUsers() + for n in result: + username=sqlattr(n,self.usernameColumn) + users.append(username) + return users + + def listUsers(self): + """Returns a list of user names or [] if no users exist""" + users = [] + result=self.sqlListUsers() + for n in result: + roles=[] + username=sqlattr(n,self.usernameColumn) + if sqlattr(n, self.rolesColumn): + roles=string.split(sqlattr(n,self.rolesColumn),',') + password=sqlattr(n, self.passwordColumn) + N={'username':username, 'password':password, 'roles':roles} + users.append(N) + return users + + def listOneUser(self,username): + #xLOG('pg.listOneUser(%s)' % username) + if getattr(self, '_v_lastUser', {}): + if self._v_lastUser['username']==username: + return self._v_lastUser['users'] + #xLOG('pg.listOneUser continuing') + users = [] + result=self.sqlListOneUser(username=username) + #xLOG('pg.listOneUser result=%s' % result) + for n in result: + roles=[] + username=sqlattr(n,self.usernameColumn) + password=sqlattr(n,self.passwordColumn) + if sqlattr(n, self.rolesColumn): + roles=string.split(sqlattr(n,self.rolesColumn),',') #Andreas + N={'username':username, 'password':password, 'roles':roles} + users.append(N) + self._v_lastUser={} + self._v_lastUser['username']=username + self._v_lastUser['users']=users + return users + + def postInitialisation(self, REQUEST): + self._v_lastUser={} + +pgAuthReg=PluginRegister('pgAuthSource', 'Postgresql Authentication Source', + pgAuthSource, manage_addpgAuthSourceForm, + manage_addpgAuthSource, + manage_editpgAuthSourceForm) +exUserFolder.authSources['pgAuthSource']=pgAuthReg + +from string import upper, lower +import Missing +mt=type(Missing.Value) + +def typeconv(val): + if type(val)==mt: + return '' + return val + +def sqlattr(ob, attr): + name=attr + if hasattr(ob, attr): + return typeconv(getattr(ob, attr)) + attr=upper(attr) + if hasattr(ob, attr): + return typeconv(getattr(ob, attr)) + attr=lower(attr) + if hasattr(ob, attr): + return typeconv(getattr(ob, attr)) + raise NameError, name + + + +_sqlListUsers=""" +SELECT * FROM +""" + +_sqlListOneUser=""" +SELECT * FROM +where = +""" + +_sqlDeleteOneUser=""" +DELETE FROM +where = +""" + +_sqlInsertUser=""" +INSERT INTO (, , ) +VALUES (, + , + ) +""" + +_sqlUpdateUserPassword=""" +UPDATE set = +WHERE = +""" + +_sqlUpdateUser=""" +UPDATE set = +WHERE = +""" diff --git a/ZopeProducts/exUserFolder/CHANGES.txt b/ZopeProducts/exUserFolder/CHANGES.txt new file mode 100644 index 00000000..6271ba5e --- /dev/null +++ b/ZopeProducts/exUserFolder/CHANGES.txt @@ -0,0 +1,865 @@ +Changes for 0.50.1 + +Add a README.Upgrading file to explain the impact of the 0.50.0 source +restructure, since people don't seem to be reading this file. --akm + +Fix the default docLogin to use &dtml-URL as the default destination. +I porked the fcrypt import. It obviously doesn't get imported here since +I have a crypt module installed. -- akm + +Fixed; https://sourceforge.net/tracker/?func=detail&aid=1084903&group_id=36318&atid=416446 +thanks to vigine -- akm + +Changes for 0.50.0 + +Restructured Source Tree. This will make this version incompatible with +previous versions, as the classes have moved. This breaks upgrading existing +installs unless you keep the old classes around. If you only use external +Auth/Prop/Group sources, you will probably be unaffected. + +o Auth Sources moved to single directory +o Prop Sources moved to single directory +o Group Sources moved to single directory +o Docs moved to doc directory +--akm + +Added Pluggable Crypto methods. Any authSource that contains a +cryptPassword method, will have it's method called, otherwise the +method selected by the user is called. --akm + +Removed the cryptPassword method from existing Auth Sources. --akm + +docLoginRedirect is no longer used. --akm + +Changes for 0.20.2 +BLAH! I missed some LDAP changes! --akm + +Changes for 0.20.1 + +Fix import problem for pgPropSource --akm +Add performance boost to pgAuthSource and pgPropSource --akm +Make zodbAuthSource.listUsernames return a list. --akm +Update some LDAP Auth source bugs. --akm +Change references to "Authorisation" to "Authentication" since XUF +auth sources authenticate, they don't authorise. --akm +Changed the

      tags to tags in the manage_adds. + +Changes for 0.20.0 + +Fix: +https://sourceforge.net/tracker/index.php?func=detail&aid=547327&group_id=36318&atid=416446 +https://sourceforge.net/tracker/index.php?func=detail&aid=616485&group_id=36318&atid=416448 +https://sourceforge.net/tracker/index.php?func=detail&aid=594081&group_id=36318&atid=416448 +https://sourceforge.net/tracker/index.php?func=detail&aid=594526&group_id=36318&atid=416448 + +Added LDAPAuthSource, based on the auth_ldap module for Apache +(http://www.rudedog.org/auth_ldap/) and the NDS Auth Source of +Phil Harris (AKA ftmpsh). This is only lightly tested, I don't have +the LDAP resources here to test all the features. Binding using uid/ +cn and using various filters works (if the userPassword item is +present). This needs more testing by people with better LDAP setups +that I do. --akm + +Padded docLoginRedirect to prevent IE from displaying "Friendly" error +messages when -D flag not present when running Zope --akm. + +Update UZG to contain entry for LDAPAuthSource. Reformat text +slightly. --akm + +Propogate "unable to auth" here requests up. This means the Manager +doesn't get locked out in cookie mode after adding an XUF instance. +It also means that people using a non-existant username at this level +get thrown up a level higher. This might not be what people want to +happen. --akm + +Added method makeRedirectPath which is called from docLoginRedirect. +This makes the destination include any querystring that was present +when needing to redirect. -- akm. + +Removed some Class globals from exUseFolder.py. These are now set +in __set_state__ if not present in the class so that upgrading users +don't get a crash (hopefully). -- akm. + +pgPropSource was losing track of properties under heavy load. +Only noticable if you were setting and deleting a lot of temporary +properties. There is a global property timeout for pgPropSource. --akm + +Jason Gibson provided a nisAuthSource, +I've added it here --akm. + +Refactored validate method to behave a lot more like BasicUserFolder. +Among other things, this fixes the issue where a local role could not +be granted to a user and granted permissions on the same object. --mb + +Add NuxUserGroups support (previously on NuxUserGroups_support_branch) +and group sources. --bmh, mb + +Now passes authFailedCode to Membership Login Page, The Default Login +Page as defined in the README.Membership will correctly display reason +for login being required --cab + +Fixed Edit management pages for user-supplied auth and property +sources --bmh + +Removed overriding of __len__ to return the number of users. This was +causing performance problems during authentication. See +http://sourceforge.net/mailarchive/message.php?msg_id=2230743 for +details. WARNING: this means using len(acl_users) to get the number +of users will no longer work! If you were using this trick, please +use len(acl_users.listUsers()) instead. --bmh + +Make title property editable --bmh + +Make Group Sources changeable dynamically after the acl_users folder has +been created --bmh + +Inital import of https Auth source. Also, added a listUsers method +to the zodbBTreeProps source to support listUsers. -- jsb + +Changes for 0.10.10 + +Added mysql Auth and mysql Prop source and mysql.sql schema. Just a +copy of the appropriate pg source with sql that works with myqsl -cab + +Fixed negative user cache lookup in std_validade so that it actually +works for users being authenticated thru basic auth, especially if +they're authenticating in outer user folders -- rochael + +Made smbAuthSource catch NetBIOSTimeout errors during authentication -- rochael + +Fixed dtml/mainUser.dtml to be virtualhost-sensitive when displaying user +icons -- rochael + +Updated UZG per user request. Fixed numbering, added information about +addition parameters like Negative Caching. + +Changes for 0.10.9 + +Made dummyZBabelTag compatible to replace the NoBabel in OrderedFolder +while keeping its functionality in XUF -- cab + +Changed _doAddUser, _doChangeUser to work with the public interface for +userfolders introduced in Zope2.5. Optional keyword arguments can now +be passed to _doAddUser and _doChangeUser. + +PropertySource: Please note that createUser and updateUser, when called +from _doAddUser and _doChangeUser, will no longer be passed a REQUEST, +but a mapping with items from REQUEST updated with those from the +optional keyword arguments. -- pj + +Fixed the problem with upgrading from 0.10.7 and below that didn't +account for existing XUF's not having a MessageDialog in their +contents. Now unless specificy replace it will use the MessageDialog +provided. Added how to do that to FAQ and README.Membership --cab + +Made docLoginRedirect provide an absolute URL --bmh + +MessageDialog in common no longer uses mangage_page_header and +mangage_page_footer v--cab + +Changes for 0.10.8 + +Added the ability for members to change properties, and a default page +in the README.Membership to show how to do it --cab + +MessageDialog is now an object in the ZODB that can be changed to fit +the site --cab + +Now with 100% guaranteed race-condition-free UserCache goodness! Those +subclassing XUFUser, you will have to change your code. See User.py +for details. --mb + +zodbBTreePropSource was returning None instead of the requested +default value, when called with (e.g.) someuser.getProperty('shoesize',13). +(Other property sources didn't have that bug.) +--davidc@debian.org + +The tutorial loginform was wrong for Membership in README.Membership + +Seems delProperty has never worked.. fixed --akm +Seems delProperty for pgPropSource has never worked.. fixed --akm + +Fixed Basic Auth not auth problem. --akm +Fixed Basic Auth not cache problem. --akm +Fixed Cached Users bypassing some auth checks. --akm + +Added usPropSource, which allows users to supply property methods TTW. +--bmh + +Changes for 0.10.7 + +PropertyEditor had a typo in dtml and was casting int to None. --zxc + +BasicAuth is now broken the other way, it'll allow any user to validate +with any password. --akm + +Negative cache checking move was bogus. --akm + +redirectToLogin didn't have a security declaration so 2.5.0 refused to +work in cookie mode *sigh* --akm + +Fixed the 'None' object has no attribute 'load' setstate errors that +could crop up on propSources, and preemptively took care of the +authSources as well. Also fixed some of the weirder bugs relating to +user object acquisition context. --mb + +Bug fixes from sf applied. --akm + +Changes for 0.10.6 + +dummyZBabelTag used the python 2 re, which broke installations using +python 1.5 which still used the now deprecated regex, changed it to +catch the exception and use regex instead for python 1.5, else still +use re --cab + +The redirectToLogin without Membership had a little logic problem where it +would basically garantee the existence of a query string, with at least a +lonely question mark even when there was no query string in the original +URL --rochael + +smbAuthSource needed to cast NULL role properties to an empty list --akm + +smbAuthSource had some dodgey zLOGing in it. --akm + +smbAuthSource had some methods that should return [] instead of None. --akm + +s/postgres/RADIUS/ in the radiusAuthSource DTML --akm + +cookie_validate no longer pulls you from the cache if you're +logging in (which means your cookie wouldn't get set). --akm + +Cookies are no longer expired if you're successfully authenticated but +merely unauthorized. --mb + +Basic auth resynched with standard user folder, trying to fix +some basic auth issues. --akm. + +Negative cache checking now performed outside of the two specific +validate methods. --akm. + +A fairly innocuous print debug statement turned into a zLOG at error +level, removed --akm. + +Clean up smbAuthSource log messages, and quieten. Only truly +exceptional cases are now logged above BLATHER. --mb + +Changes for 0.10.5 + +Membership redirecting to login was still broken. It should be better +now (twice) --akm + +logout() wasn't clearing the advanced cookie. --akm + +Negative Cache Value wasn't being passed through to the XUF constructor. --akm +Log Users Out DTML code was broken, should work now. --akm + +The User object now contains the authSource as well as the propSource, +making access to roles for custom User-objects possible. --dlk + +Following akm's advice, fixed manage_beforeDelete to use two separate +try:except blocks to ensure that if cache-removal fails, deleting +the container.__allow_groups__ property is attempted. This should +fix the problem where deleted xuf instances remain as "ghost" products +causing interference with newer versions of xuf, and also fixes the +problem where deleting a xuf acl_users in a folder makes that folder +inaccessible. --dlk + +Fixed cache_delete that was missing the "self" parameter in the method +defintion. --dlk + +Fixed xcache_delete that was missing the "self" parameter in the method +definition --akm d8) + +These previous two fix the problems with manage_beforeDelete, but, it +will stay the same for now --akm. + +Fixed cache_deleteCookieCache that was missing the "self" parameter in +the method defintion. --dlk ;) + +Changes for 0.10.4 + +The instructions for File Based Auth were incorrect in the UZG --akm + +redirectToLogin was totally wrong for membership... --akm +docLogin was fixed for VHM use. --akm + +Advanced Cookie Mode has changed so that it no longer sends the username +and password. Instead a hash is used as a key into a module level cache. +This should be 100% more secure than standard cookie mode, and removes +the stupid back doors I enabled in the previous version. This work was +based on conversations I had with Stuart Bishop (I basically lifted +the hashing scheme from GUF). This makes use of the Module level cache +code. --akm + +There was a code cleanup and a slight reorganisation of some files. --akm + +The main User Object has migrated to XUFUser and simarly with the +AnonUser. There is now an empty [Anon]User class that has XUFUser as +it's base. This allows people to create custom User Objects without +jumping through hoops (and simplifies maintaining patches) --akm + +Cache Code has changed again. Now there is a module level cache, so +that auth data is shared between threads for a single XUF (thanks to +Stuart Bishop for an enlightening discussion on this and other issues, +and thanks to Chris McDonough for talking me through setting up module +level globals [and sending me some code to work from]) --akm + +A Negative User Cache now exists. This is only generally useful for +use with remote auth sources where repeatedly trying to auth non-existant +users is very expensive (where they are authed at a higher level). +You can enable this on creation or from the parameters screen (positive +time in seconds enables). --akm + +Domain checking code finally removed. --akm + +zodbBTreePropSource changed to be friendlier about users that exist +in remote locations (i.e. aren't create as such through the ZMI). -- akm + +Changed some 'print's in the code to use zLOG.LOG +instead. Files affected so far (more to follow): -- rochael + + * exUserFolder.py + * basicMemberSource/basicMemberSource.py + * zodbBTreePropSource/zodbBTreePropSource.py + * zodbPropSource/zodbPropSource.py + +Changed a couple things in smbAuthSource.py: -- rbanffy + + * Method _authenticate_retry now logs several kinds of information + for debugging and diagnostics. + + * Modified socket.error handling in _authenticate_retry: changed + "raise" to "return 0". + + * Since this generated more problems (failed authentications) than + it solved (our impression it was not right not to return 0 in an + auth fail even due to a communications malfunction), we also + changed socket.error handling to retry no mather what errno tells + us (it said different things for the same problem under Windows + and Linux). + + * In order to prevent infinite retries, changed retry handling a + bit. It now retries 3 times. Real-use data will tell us if we + should increase or not retries. To better convey the meaning of + the parameter, changed "retry_depth" to "retries". I strongly + advise the use of credential caching with smbAuthSource, tough, as + it reduces socket errors and load on the domain controllers. + +Changes for 0.10.3.1 + +Readded support for I18N without ZBabel installation, somehow missed +during the transition to SF CVS. + +Some text changes as well as an update to the dictionary while we're +at it. No functional changes for this release though. + +Changes for 0.10.3 + +Missed a few LoginRequireds. + +Fixed a bug with __allow_groups__ not being set after paste +(probably also not after import). + +The sources are now sorted by name in the drop down box.. + +a BTree version of zodbAuthSource +a BTree version of zodbPropSource + +These aren't really all that different to the originals that were +provided by Alex, but, they use BTrees instead of PersistentMappings, +and try to avoid various persistence problems associated with dicts. +Both versions will continue to be supported. + +Patches from SF applied. + +Advanced Cookie Mode added. +This mode adds a rotor cipher around the cookie. A secret is provided +in order to encode the cookie. The username and password are placed +within a small class which is pickled and then encrypted and then +base64 encoded for transport. There is also a timestamp inside the cookie, +so the ultra-paranoid of you can rotate the cookie based on the timestamp +inside. + +Abstracted out the setting and decoding of cookies. + +Changes for 0.10.2 + +all raise 'LoginRequired' <- raise 'Unauthorized' + +Raising unauthorizes breaks a million things. CMF people can just +put up with configuring their portal properly. + +Radius resynced with version from sourceforge. +manage_tabs redone to be ZBabel'd and to look like standard tabs. + +German Language added to the ZBabel dictionary. + + +Changes for 0.10.1 + +all raise 'LoginRequired' -> raise 'Unauthorized' + +Bug in etcAuthSource listUsers fixed, +and cryptPassword also fixed to get the actual salt. + +Zope 2.4.3 has dicked with security settings again.. I've had a round +of permission whacking. + +Buggy handling of empty role lists was fixed. + +Change to smbAuthSource to use string.lower on usernames for +python 1.5.2 compatibility? + + +Changes for 0.10.0 + +Added explicit roles for manage_editUser and friends, to allow +the "Manage users" permission to be useful to non-Manager Users. +Thanks to Heimo Laukkanen for reporting this +one. + +zodbAuthSource made more persistent +zodbPropSource was blowing when deleting temporary properties. + +XUF is now ZBabel'd which means you can view XUF in different languages +for logging in and installation, if your browser locale is set up. +You will need the latest ZBabel installed. The translation file is in the +I18N directory. + +Import this (using Import/Export in ZODB) at the same level as your +ZBabelTower, and then import it from ZBabel. If you have ZBabel installed, +but, your application can't find a ZBabelTower, because of a bug in the +current dtml-fish tag, you might experience some problems. This ZBabel +bug should be fixed sometime soon. + +You do not need ZBabel installed to run XUF, XUF installs a dummy +interface for ZBabel so that XUF can continue to run (sorry folks it +defaults to Australian English). + +getUserNames() was returning the wrong stuff (notably affected TheJester's +WorkOrders Product) + +There is a now an 'Advanced Postgres' Auth Source that uses a seperate +Roles table and a 'more relational' layout. The schema is with the +auth source in pgAuthSourceAlt. Contributed by +Adam Manock + +If you had a membership source and had specified a login page, XUF was +still using the stock docLogin instead of the membership specified page +(for redirectToLogin, exceptions still raise the docLogin). + +I changed the icon to something a *little* less hideous + +Leonardo Rochael Almeida made the following changes +to smbAuthSource + +* Added a 'winsserver' constructor parameter and a '_winsserver' + instance variable to the 'smbAuthSource' class. This variable should + be the empty string, meaning that the authenticaton host will be + looked up by broadcast, or an IP address string pointing to a WINS + server. + +* Modified the dtml templates to ask for the above mentioned WINS + server (and also to replace 'Add' with 'Change' in + 'manage_editsmbAuthSourceForm'). + +* Refactored the smbAuthSource class to isolate all smb interaction + inside well defined methods. + + +Changes for 0.9.0 + +Messages are now sent back to the docLogin form. There's a file called +LoginRequiredMessages.py where the messages are kept for now (it might +end up a run-time configurable thing later). + +There's a new docLogin.dtml file on disk that shows how to use the new +messages. Because docLogin is in the ZODB this won't be automatically +upgraded. + +Idle Session Timeouts are in (this is the reason for the minor bump). +If you flick the switch, then users are forced back to the login form +(with a message saying their session timed out), when they're removed +from the cache. + +I made some adjustments to the tabs on the management interface because +they were too big, and I cleaned it up a bit for times when they run +together. + +The internal API was inconsistent, so that's been updated. +AuthSources no longer need to provide getUsers(), it was never +being called anyway since exUserFolder built it's own. +listUsers now returns the same data as listOneUser, this is used in +other places as if it were a list of listOneUser calls. + +Fixed pgAuthSource to deal with NULL rather than empty roles +columns (legacy columns). + +Changed Home Directory creation to use copy & paste functions to +copy the skeleton data. + +Changes for 0.8.5 + +I forgot to update the schema file for userproperties to reflect +the temporary properties flag. + +Checks for existing cache weren't being performed before removing users +from it, when their data was updated. + +Reversed the order for checking in cookie_validate, to allow logging +in as a new user, when session tracking was on. Also now you can +login as a different user, without logging out first, which might +be useful to some people. + +etcAuthSource now looks for the correct salt from the file for +encrypting the user supplied password + +Changes for 0.8.4 + +Activating Session Tracking and then adding a new user when there +were none in the XUF was broken. + +Changes for 0.8.3 + +The idle users are flushed from the cache when you ask for the list +of cache users (since it's iterating over the whole list anyway). So +you can manually clear your cache by looking at the Cache Stats page. + +If you display the list of logged in users on your site, then your cache +will be flushed for you automagically. + +Allowed a destination to be sent to redirectToLogin to allow you to +manually override the destination after logging in. + +Added in a __setstate__ for pgPropSource to deal with new ZSQL Methods +being added. + +Changes for 0.8.2 +A number of bugs related to temp properties fixed in pgPropSource + +FTP Access to folders protected with cookie_mode has been fixed, it +now reverts to std_auth (which handles the FTP connection fine), since +FTP auths are handled by getting a "Basic" auth tag coming through, which +should never happen in cookie mode. + +This has the knock-on effect of authenticating users that auth from a +higher acl_users that doesn't use cookies, 'more' correctly now. Which is +if you have a user defined above, and in XUF and the XUF user has less +permissions, it'll 401 you if you don't have permissions locally +(which is the correct behaviour). This bit me in the arse when I changed it, +and I'm still leaving it this way. d8) + +Users are now flushed from the cache when you edit them (in case you changed +roles), so that new roles should take effect immediately. + +The credential cache now uses the (Zope) builtin BTree Module for caching +rather than the AVL Tree implementation. There was a nasty issue with users +appearing multiple times in the AVL Tree which sucked. + +There is a report of the Radius Auth Source being broken (most likely +by me), if your radius source stops working, you can try copying the +py-radius.py file from sourceforge over the top of radius.py. If someone +gives me a traceback, I can fix it. I don't seem to be having problems, +but, I don't have a full time RADIUS source either. + + +Changes for 0.8.1 + +A bug in _doAddUser was fixed +A bug in the User Object unconditionally calling the prop source was fixed. + + +Changes for 0.8.0 + +Experimental "Session Tracking" added (why is it called that? we don't really +track anything, just associate arbitrary data with anonymous users). +This relies on the credential cache being active. Your session will +automatically expire when the anonymous user is so idle that they are +expired from the cache. This is not currently acceptable (to me), but, +it might be to other people, I await feedback on how sessions should expire +gracefully. + +Updated the README.txt file to point at the UZG and to explain the +version numbering system. + +All this time you couldn't delete properties from a user... who knew? +It's fixed now. + +Temporary properties now available, you can setTempProperty() on a +user object, and also flushTempProperties() on a user object. +Temporary properties are accessed like normal properties, and can be +deleted in the same way. flushTempProperties is there to do a quick +flush of all the crap you might have inserted (useful for sessions). +If your user is flushed from the cache, then all temp properties will +also be removed at that point. + +Propsource providers should look at the new temp properties stuff and +update accordingly. + +Alex provided a whole heap of patches to make basicMembership more usable, +well make it actually work. + +Matt Behrens supplied patches to prevent null logins and to allow case +insensitive logins for smbAuthSource + +Added a basic FAQ. + + +Changes for 0.7.10 + +Active Users type functionality was added. The new function is called +getUserCacheUsers(). It returns a list of dicts; + +{'username': theusername, 'lastAccessed': float_value} + +lastAccessed represents the last time the user touched something. +The Cache Stats page shows an example usage showing idle time (very cool +I think :-) + +The logout method was not correctly removing users from the cache, +although the cookie was removed, so logins were still enforced. I'm not +sure of any side-effects related to it, but, + +Some permissions were a little too liberal, including allowing arbitrary +users to set and get Properties on the acl_users folder. + +Copy/Paste support for pasting exUserFolders into the root was added. +I'm not sure I like the way this is done. I haven't found any side effects +so far, but, just be wary. Adding an exUserFolder to the root becomes +semi-trivial now. Create one in a sub-folder. Login as the emergency user. +CUT the exUserFolder. Delete the standard acl_users folder. Paste exUserFolder. +You should be away. At least it worked fine for me... YMMV + +_doChangeUser and _doDelUsers added so users can be altered and deleted +like for Standard UserFolder. + +_createInitialUser added so there should always be your initUser (hopefully) +when you create your exUserFolder. + +Emergency User checking brought into line with Standard Folder + +__creatable_by_emergency_user_ added and returns 1 to explicitly allow this. + +Unenlightened Zopistas Guide updated to have a 'Recipe' like section. +Currently contains a section about adding exUserFolders from python. + + +Changes for 0.7.9 + +RADIUS authSource had a problem with non-integers being extracted from +REQUEST (I wish someone at DC would fix this already). I worked around +this problem + +Default port for RADIUS is now 1812 in line with the IANA sanctioned list. + +Unenlightened Zopistas Guide to exUserFolder version 0.0 included, +covers installation and authentication sources, and the most common +configuration mistake (or misunderstanding). + +I almost released with the daggy management screens all Purple or SkyBlue, +so consider yoursevles lucky. This would have been the "Blue" release. + +Changes for 0.7.8 + +zodbPropSource had a bug that must have been there since 0.0.0 where +_p_changed wasn't being called on create, update, or delete user. +Thanks to Bouke Scheurwater for spotting that one. + +Alex provided a number of patched to fix a whole bunch of goofy stuff +with Basic Member Source that was stupidly wrong. + +Matt Behrens provided a patch to allow emergency user to own exUserFolders +and some of the sources. I've grudgingly updated all the sources to allow +this. It's just a hey nonny nonny to people using it as a root authenticator +now. + +Matt Behrens also provided a patch to fix 'broken pipe' problems with +smbAuthSource. + +pySMB is now at 0.2 for smbAuthSource WARNING: This will try to use DES +encrypted passwords. Apparently it should be ok if your server doesn't want +them. However if it breaks, unpack the pySMB distribution in the +smbAuthSource directory, there are registry examples there to turn +it off. It unfortunately needs the mxCrypto tools for encrypted passwords +to work. When I've got a bit more time, I'll see if I can make it use +crypt or fcrypt if available instead. + +Explicit checks for the emergency user were placed into the cookie_validate +routines. I suspect this may have been the cause of some grief with people +doing weird things like trying to make it the root auth folder. + +Changes for 0.7.7 + +Some Auth sources had problems coping with no roles being selected when +a user was created from the management interface, the stock ones were fixed. + +I screwed up some of the DTML, and forgot to change the loading of two of +the methods from the dtml directory. + +NO MORE TRACEBACKS ON LOGIN FORMS, there is a little redirector dtml file +dtml/docLoginRedirect that redirects to acl_users/docLogin with destination +set to take them back to where they were going. If you have a custom loginPage +change the redirector dtml to point to your new page. + +standard_html swapped for manage_page on Management Pages. Hopefully +this doesn't break someone with an old copy of Zope. + +Credential Caching is now available by default for all Authentication Sources, +upgrading installs will get this defaulted to 0 for no caching. You can alter +the cache level from the Parameters Tab. Authors of external sources should +remove any internal auth caching they're doing, and allow the user to decide +how long to cache the credentials for. + + +Changes for 0.7.6 + +smbAuthSource included. Doesn't require any external libraries, or compiling. +Uses pySMB from Micheal Teo + +Changes for 0.7.5 +The Management Interface now batches the user list by 10. This isn't +configurable at the moment (just change the dtml). + +The code was re-organised slightly, with all the DTML moving into its +own directory for core. + +radiusAuthSource added, but, is so far untested. It is a direct port of +ZRadius for GUF, but, I haven't had a chance to setup a RADIUS server to +test it out. + +You can add properties to a user from the management interface. + +List Properties on users can be added and edited, if I can work out a decent +way to edit Dicts/Mappings, I'll add that feature in. + +This paves the way for defining a set of properties in the Membership +source, so it can create a Signup and Edit page for you automatically. +You will also be able to specify which properties the user can edit, or +roles required to edit a property, this will be in a later release though. + +pgPropSource was updated to take into account non-scalar types, and now +pickles all data going into the database, this means ints will stay as ints, +et al. +There is code in there to cope with older properties coming out as strings. +The Schema remains the same. + +Changes for 0.7.2 +Changes to make it work with older version of python +Some minor bug fixes for membership. + +Changes for 0.7.1 +DTML Change for cmfPropSource + +Changes for 0.7.0 +exUserFolder was a little too liberal in removing its cruft, this is now +fixed. + +cmfPropSource was provided by Alan Runyan which is a layer around the CMF +property stuff. It's conditionally imported, so if you don't have CMF +installed you don't need to worry that'll it'll break. + +Property Sources are optional, and there is a NULL Property Source for this +purpose. + +Membership hooks, and a rough start at membership (basicMemberSource), +which has some usable functionality (you MUST read README.Membership before +using this). + +Membership Sources are optional and there is a NULL Membership Source for +this purpose. + + +Changes for 0.6.2 +exUserFolder was leaving cruft around when it was being deleted from +Folders. The cruft should now be obliterated if you delete an exUserFolder. + +Changes for 0.6.1 +Ownership tab enabled, for those sick monkeys that want to use it as a root +Folder (there are some). + +fcrypt got the __init__.py that was missing from the 0.6.0 release +zodbAuthSource updated to pull in fcrypt if crypt was missing. + +Changes for 0.6.0 + +Updated for 2.4.1 / Python 2.1 +Bug in pgPropSource not deleting users from the property cache fixed. +Bug with Local Roles not getting what it expected fixed. +Alex Verstraeten provided zodbAuthSource, there's a README.zodbAuthSource, +and the same README inside the zodbAuthSource directory. +fcrypt is now included and used if crypt cannot be imported. More information +on fcrypt can be found at http://home.clear.net.nz/pages/c.evans/sw/. This +should help particularly Windows users a lot. +Rudimentary API doc included. + +Changes for 0.5.0 + +A serious bug in zodbPropSource was fixed. + +There is now the option of providing a 'Remote Auth' function for +validating. This allows things like IMAP/LDAP auth sources to do their +authentication, since they don't return passwords you can use in general. + +There's already a 3rd Party solution that provides IMAP/POP3 authentication, +using the new API. + +Changes for 0.4.6 + +Minor dtml hacks + +Changes for 0.4.5 + +Hooks for 'editing' Authentication and Property Sources were added, along +with the relevant methods in each of the sources. + +The management interfaces got a little overhaul, just to make them +a little different (yes I know everything I do looks the same). The two +I didn't want to mess with still have the acquired management interfaces. + +A fix for the ZODB Property Source which was missing a few methods. + +Changes for 0.4.0 + +Based on an idea from Martin von Loewis, I added in support for defining +roles for etcAuthSource. This basically uses the current Prop source to +store a 'roles' property. The default role is still there as well for +those of you who might be using it. + +Changes for 0.3.0 + +Adrien Hernot noticed that properties for new users using zodbPropSource +were causing havoc, and that the version.txt file was completely wrong. +Andreas also noticed the version.txt was wrong. + +I've been bugged enough by the pair of them to change the single += +into 1.5.2 compliant syntax. + +I don't make any claims about it working under 1.5.2 though. + +Changes for 0.2.0 + +Even more embarassment... + +Andreas Heckel provided fixes for some stupid things I left out including; + +o Fixing the way I was handling multiple roles coming out of the database +o The wrong icon in the user display +o Alerting me to the fact that pgPropSource didn't actually have a + deleteUsers hook +o Providing a schema for automatically deleting properties in postgres + if you delete a user from the auth source (you have to be using both + pg sources for this to work, and they'd have to be in the same database) + I've put Andreas schema into the distribution, if you want to use + exUserFolder as a straight pgUserFolder, you'll also need to edit + exUserFolder.py and comment out the line indicated in deleteUsers() + +Changes for 0.1.0 + +Pretty embarassing really. + +M. Adam Kendall (DaJoker) found some stupid things in the 0.0.0 release +including the fact you couldn't edit user properties, or update them, +or actually change a user in anyway. + +I also discovered I was resetting the password to empty if you left it +empty.. diff --git a/ZopeProducts/exUserFolder/CryptoSources/__init__.py b/ZopeProducts/exUserFolder/CryptoSources/__init__.py new file mode 100644 index 00000000..49ebc6d4 --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/__init__.py @@ -0,0 +1,4 @@ +import pass_crypt +import pass_md5 +import pass_sha +import pass_plain diff --git a/ZopeProducts/exUserFolder/CryptoSources/fcrypt/.cvsignore b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/.cvsignore new file mode 100644 index 00000000..57c4bccb --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/.cvsignore @@ -0,0 +1,4 @@ +*.pyc +*.pyo +*~ +.*.swp diff --git a/ZopeProducts/exUserFolder/CryptoSources/fcrypt/ChangeLog b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/ChangeLog new file mode 100644 index 00000000..70299bed --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/ChangeLog @@ -0,0 +1,35 @@ +2001-05-05 Carey Evans + + * fcrypt.py: Add module doc string for pydoc, and other globals + for pydoc as well. Add __all__ for Python 2.1, and add + underscores to the front of private variables and functions. + (_set_key): Remove overly clever copying of globals into default + parameters, explicitly copying _shift2 and _skb before the loop. + (_body): Copy _SPtrans explicitly, as above. Remove CR_ENCRYPT + inline function, and reroll unrolled loop using the contents of + this function. Result: more readable code, and a 400% speedup! + (crypt): Add doc string for pydoc and doctest. + (_test): New function for doctest. + + * setup.py: Add fields for PKG-INFO metadata. + + * README: Add URL of distutils installation manual. + + * LICENSE: Add note about license on fcrypt.py being the union of + my license on the Python code and Eric Young's on the original C. + +2001-03-24 Carey Evans + + * setup.py: Move license to separate file. Change email address + to SpamCop forwardder. Update version to 1.1. + + * fcrypt.py: Update license text and email address. + (crypt): Fix bug where passwords longer than eight characters were + not truncated. + + * README: Update crypt module URL. Remove license text, and add + pointer to LICENSE file. Update email address. + + * MANIFEST.in: Add LICENSE, ChangeLog and MANIFEST.in. + + * LICENSE: New file. diff --git a/ZopeProducts/exUserFolder/CryptoSources/fcrypt/LICENSE b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/LICENSE new file mode 100644 index 00000000..c26e615d --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/LICENSE @@ -0,0 +1,77 @@ + fcrypt.py copyrights and license + -------------------------------- + + +The Python code by Carey Evans has the following license, which is the +original Python license with the serial numbers filed off, and the +restrictions on advertising removed. + + Copyright (C) 2001, 2001 Carey Evans + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose and without fee is hereby granted, + provided that the above copyright notice appear in all copies and that + both that copyright notice and this permission notice appear in + supporting documentation. + + CAREY EVANS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + EVENT SHALL CAREY EVANS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + + +The original C code on which this module was based has the following +more restrictive license, so the source for fcrypt.py should be +considered to be covered by the union of my license and Eric Young's. + + This library is free for commercial and non-commercial use as long as + the following conditions are aheared to. The following conditions + apply to all code found in this distribution, be it the RC4, RSA, + lhash, DES, etc., code; not just the SSL code. The SSL documentation + included with this distribution is covered by the same copyright terms + except that the holder is Tim Hudson (tjh@mincom.oz.au). + + Copyright remains Eric Young's, and as such any Copyright notices in + the code are not to be removed. + If this package is used in a product, Eric Young should be given attribution + as the author of the parts of the library used. + This can be in the form of a textual message at program startup or + in documentation (online or textual) provided with the package. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + "This product includes cryptographic software written by + Eric Young (eay@mincom.oz.au)" + The word 'cryptographic' can be left out if the rouines from the library + being used are not cryptographic related :-). + 4. If you include any Windows specific code (or a derivative thereof) from + the apps directory (application code) you must include an acknowledgement: + "This product includes software written by Tim Hudson (tjh@mincom.oz.au)" + + THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + The licence and distribution terms for any publically available version or + derivative of this code cannot be changed. i.e. this code cannot simply be + copied and put under another distribution licence + [including the GNU Public Licence.] diff --git a/ZopeProducts/exUserFolder/CryptoSources/fcrypt/MANIFEST.in b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/MANIFEST.in new file mode 100644 index 00000000..a42d250c --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/MANIFEST.in @@ -0,0 +1 @@ +include LICENSE ChangeLog MANIFEST.in diff --git a/ZopeProducts/exUserFolder/CryptoSources/fcrypt/PKG-INFO b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/PKG-INFO new file mode 100644 index 00000000..c35737e0 --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/PKG-INFO @@ -0,0 +1,13 @@ +Metadata-Version: 1.0 +Name: fcrypt +Version: 1.2 +Summary: The Unix password crypt function. +Home-page: http://home.clear.net.nz/pages/c.evans/sw/ +Author: Carey Evans +Author-email: careye@spamcop.net +License: BSD +Description: A pure Python implementation of the Unix DES password crypt function, + based on Eric Young's fcrypt.c. It works with any version of Python + from version 1.5 or higher, and because it's pure Python it doesn't + need a C compiler to install it. +Platform: UNKNOWN diff --git a/ZopeProducts/exUserFolder/CryptoSources/fcrypt/README b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/README new file mode 100644 index 00000000..168500ff --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/README @@ -0,0 +1,33 @@ + fcrypt.py + --------- + +This is a pure Python implementation of the Unix DES password crypt +function. It was ported from C code by Eric Young (eay@mincom.oz.au). +See the file LICENSE for copyright and license details. + +This module is packaged with Distutils. If you have this installed, +or it came with your version of Python, you can install it by typing: + + python setup.py install + +If not, you can just copy `fcrypt.py' into a directory on your Python +library path, or into the same directory as the program that wants to +use it. + +For more information, see the documentation for Python's built-in +crypt module at: + + http://www.python.org/doc/current/lib/module-crypt.html + +Eric Young's fcrypt.c is available from: + + ftp://ftp.psy.uq.oz.au/pub/Crypto/DES/ + +For more Distutils information, see: + + http://www.python.org/doc/current/inst/inst.html + http://www.python.org/sigs/distutils-sig/ + +-- +Carey Evans +5 May 2001 diff --git a/ZopeProducts/exUserFolder/CryptoSources/fcrypt/__init__.py b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/__init__.py new file mode 100644 index 00000000..97f6031c --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/__init__.py @@ -0,0 +1 @@ +import fcrypt diff --git a/ZopeProducts/exUserFolder/CryptoSources/fcrypt/fcrypt.py b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/fcrypt.py new file mode 100644 index 00000000..760347cb --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/fcrypt.py @@ -0,0 +1,602 @@ +# fcrypt.py + +"""Unix crypt(3) password hash algorithm. + +This is a port to Python of the standard Unix password crypt function. +It's a single self-contained source file that works with any version +of Python from version 1.5 or higher. The code is based on Eric +Young's optimised crypt in C. + +Python fcrypt is intended for users whose Python installation has not +had the crypt module enabled, or whose C library doesn't include the +crypt function. See the documentation for the Python crypt module for +more information: + + http://www.python.org/doc/current/lib/module-crypt.html + +The crypt() function is a one-way hash function, intended to hide a +password such that the only way to find out the original password is +to guess values until you get a match. If you need to encrypt and +decrypt data, this is not the module for you. + +There are at least two packages providing Python cryptography support: +M2Crypto at , and amkCrypto at +. + +Functions: + + crypt() -- return hashed password +""" + +__author__ = 'Carey Evans ' +__version__ = '1.2' +__date__ = '6 May 2001' +__credits__ = '''michal j wallace for inspiring me to write this. +Eric Young for the C code this module was copied from.''' + +__all__ = ['crypt'] + + +# Copyright (C) 2000, 2001 Carey Evans +# +# Permission to use, copy, modify, and distribute this software and +# its documentation for any purpose and without fee is hereby granted, +# provided that the above copyright notice appear in all copies and +# that both that copyright notice and this permission notice appear in +# supporting documentation. +# +# CAREY EVANS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +# EVENT SHALL CAREY EVANS BE LIABLE FOR ANY SPECIAL, INDIRECT OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF +# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +# Based on C code by Eric Young (eay@mincom.oz.au), which has the +# following copyright. Especially note condition 3, which imposes +# extra restrictions on top of the standard Python license used above. +# +# The fcrypt.c source is available from: +# ftp://ftp.psy.uq.oz.au/pub/Crypto/DES/ + +# ----- BEGIN fcrypt.c LICENSE ----- +# +# This library is free for commercial and non-commercial use as long as +# the following conditions are aheared to. The following conditions +# apply to all code found in this distribution, be it the RC4, RSA, +# lhash, DES, etc., code; not just the SSL code. The SSL documentation +# included with this distribution is covered by the same copyright terms +# except that the holder is Tim Hudson (tjh@mincom.oz.au). +# +# Copyright remains Eric Young's, and as such any Copyright notices in +# the code are not to be removed. +# If this package is used in a product, Eric Young should be given attribution +# as the author of the parts of the library used. +# This can be in the form of a textual message at program startup or +# in documentation (online or textual) provided with the package. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# "This product includes cryptographic software written by +# Eric Young (eay@mincom.oz.au)" +# The word 'cryptographic' can be left out if the rouines from the library +# being used are not cryptographic related :-). +# 4. If you include any Windows specific code (or a derivative thereof) from +# the apps directory (application code) you must include an acknowledgement: +# "This product includes software written by Tim Hudson (tjh@mincom.oz.au)" +# +# THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# The licence and distribution terms for any publically available version or +# derivative of this code cannot be changed. i.e. this code cannot simply be +# copied and put under another distribution licence +# [including the GNU Public Licence.] +# +# ----- END fcrypt.c LICENSE ----- + + +import string, struct + + +_ITERATIONS = 16 + +_SPtrans = ( + # nibble 0 + [ 0x00820200, 0x00020000, 0x80800000, 0x80820200, + 0x00800000, 0x80020200, 0x80020000, 0x80800000, + 0x80020200, 0x00820200, 0x00820000, 0x80000200, + 0x80800200, 0x00800000, 0x00000000, 0x80020000, + 0x00020000, 0x80000000, 0x00800200, 0x00020200, + 0x80820200, 0x00820000, 0x80000200, 0x00800200, + 0x80000000, 0x00000200, 0x00020200, 0x80820000, + 0x00000200, 0x80800200, 0x80820000, 0x00000000, + 0x00000000, 0x80820200, 0x00800200, 0x80020000, + 0x00820200, 0x00020000, 0x80000200, 0x00800200, + 0x80820000, 0x00000200, 0x00020200, 0x80800000, + 0x80020200, 0x80000000, 0x80800000, 0x00820000, + 0x80820200, 0x00020200, 0x00820000, 0x80800200, + 0x00800000, 0x80000200, 0x80020000, 0x00000000, + 0x00020000, 0x00800000, 0x80800200, 0x00820200, + 0x80000000, 0x80820000, 0x00000200, 0x80020200 ], + + # nibble 1 + [ 0x10042004, 0x00000000, 0x00042000, 0x10040000, + 0x10000004, 0x00002004, 0x10002000, 0x00042000, + 0x00002000, 0x10040004, 0x00000004, 0x10002000, + 0x00040004, 0x10042000, 0x10040000, 0x00000004, + 0x00040000, 0x10002004, 0x10040004, 0x00002000, + 0x00042004, 0x10000000, 0x00000000, 0x00040004, + 0x10002004, 0x00042004, 0x10042000, 0x10000004, + 0x10000000, 0x00040000, 0x00002004, 0x10042004, + 0x00040004, 0x10042000, 0x10002000, 0x00042004, + 0x10042004, 0x00040004, 0x10000004, 0x00000000, + 0x10000000, 0x00002004, 0x00040000, 0x10040004, + 0x00002000, 0x10000000, 0x00042004, 0x10002004, + 0x10042000, 0x00002000, 0x00000000, 0x10000004, + 0x00000004, 0x10042004, 0x00042000, 0x10040000, + 0x10040004, 0x00040000, 0x00002004, 0x10002000, + 0x10002004, 0x00000004, 0x10040000, 0x00042000 ], + + # nibble 2 + [ 0x41000000, 0x01010040, 0x00000040, 0x41000040, + 0x40010000, 0x01000000, 0x41000040, 0x00010040, + 0x01000040, 0x00010000, 0x01010000, 0x40000000, + 0x41010040, 0x40000040, 0x40000000, 0x41010000, + 0x00000000, 0x40010000, 0x01010040, 0x00000040, + 0x40000040, 0x41010040, 0x00010000, 0x41000000, + 0x41010000, 0x01000040, 0x40010040, 0x01010000, + 0x00010040, 0x00000000, 0x01000000, 0x40010040, + 0x01010040, 0x00000040, 0x40000000, 0x00010000, + 0x40000040, 0x40010000, 0x01010000, 0x41000040, + 0x00000000, 0x01010040, 0x00010040, 0x41010000, + 0x40010000, 0x01000000, 0x41010040, 0x40000000, + 0x40010040, 0x41000000, 0x01000000, 0x41010040, + 0x00010000, 0x01000040, 0x41000040, 0x00010040, + 0x01000040, 0x00000000, 0x41010000, 0x40000040, + 0x41000000, 0x40010040, 0x00000040, 0x01010000 ], + + # nibble 3 + [ 0x00100402, 0x04000400, 0x00000002, 0x04100402, + 0x00000000, 0x04100000, 0x04000402, 0x00100002, + 0x04100400, 0x04000002, 0x04000000, 0x00000402, + 0x04000002, 0x00100402, 0x00100000, 0x04000000, + 0x04100002, 0x00100400, 0x00000400, 0x00000002, + 0x00100400, 0x04000402, 0x04100000, 0x00000400, + 0x00000402, 0x00000000, 0x00100002, 0x04100400, + 0x04000400, 0x04100002, 0x04100402, 0x00100000, + 0x04100002, 0x00000402, 0x00100000, 0x04000002, + 0x00100400, 0x04000400, 0x00000002, 0x04100000, + 0x04000402, 0x00000000, 0x00000400, 0x00100002, + 0x00000000, 0x04100002, 0x04100400, 0x00000400, + 0x04000000, 0x04100402, 0x00100402, 0x00100000, + 0x04100402, 0x00000002, 0x04000400, 0x00100402, + 0x00100002, 0x00100400, 0x04100000, 0x04000402, + 0x00000402, 0x04000000, 0x04000002, 0x04100400 ], + + # nibble 4 + [ 0x02000000, 0x00004000, 0x00000100, 0x02004108, + 0x02004008, 0x02000100, 0x00004108, 0x02004000, + 0x00004000, 0x00000008, 0x02000008, 0x00004100, + 0x02000108, 0x02004008, 0x02004100, 0x00000000, + 0x00004100, 0x02000000, 0x00004008, 0x00000108, + 0x02000100, 0x00004108, 0x00000000, 0x02000008, + 0x00000008, 0x02000108, 0x02004108, 0x00004008, + 0x02004000, 0x00000100, 0x00000108, 0x02004100, + 0x02004100, 0x02000108, 0x00004008, 0x02004000, + 0x00004000, 0x00000008, 0x02000008, 0x02000100, + 0x02000000, 0x00004100, 0x02004108, 0x00000000, + 0x00004108, 0x02000000, 0x00000100, 0x00004008, + 0x02000108, 0x00000100, 0x00000000, 0x02004108, + 0x02004008, 0x02004100, 0x00000108, 0x00004000, + 0x00004100, 0x02004008, 0x02000100, 0x00000108, + 0x00000008, 0x00004108, 0x02004000, 0x02000008 ], + + # nibble 5 + [ 0x20000010, 0x00080010, 0x00000000, 0x20080800, + 0x00080010, 0x00000800, 0x20000810, 0x00080000, + 0x00000810, 0x20080810, 0x00080800, 0x20000000, + 0x20000800, 0x20000010, 0x20080000, 0x00080810, + 0x00080000, 0x20000810, 0x20080010, 0x00000000, + 0x00000800, 0x00000010, 0x20080800, 0x20080010, + 0x20080810, 0x20080000, 0x20000000, 0x00000810, + 0x00000010, 0x00080800, 0x00080810, 0x20000800, + 0x00000810, 0x20000000, 0x20000800, 0x00080810, + 0x20080800, 0x00080010, 0x00000000, 0x20000800, + 0x20000000, 0x00000800, 0x20080010, 0x00080000, + 0x00080010, 0x20080810, 0x00080800, 0x00000010, + 0x20080810, 0x00080800, 0x00080000, 0x20000810, + 0x20000010, 0x20080000, 0x00080810, 0x00000000, + 0x00000800, 0x20000010, 0x20000810, 0x20080800, + 0x20080000, 0x00000810, 0x00000010, 0x20080010 ], + + # nibble 6 + [ 0x00001000, 0x00000080, 0x00400080, 0x00400001, + 0x00401081, 0x00001001, 0x00001080, 0x00000000, + 0x00400000, 0x00400081, 0x00000081, 0x00401000, + 0x00000001, 0x00401080, 0x00401000, 0x00000081, + 0x00400081, 0x00001000, 0x00001001, 0x00401081, + 0x00000000, 0x00400080, 0x00400001, 0x00001080, + 0x00401001, 0x00001081, 0x00401080, 0x00000001, + 0x00001081, 0x00401001, 0x00000080, 0x00400000, + 0x00001081, 0x00401000, 0x00401001, 0x00000081, + 0x00001000, 0x00000080, 0x00400000, 0x00401001, + 0x00400081, 0x00001081, 0x00001080, 0x00000000, + 0x00000080, 0x00400001, 0x00000001, 0x00400080, + 0x00000000, 0x00400081, 0x00400080, 0x00001080, + 0x00000081, 0x00001000, 0x00401081, 0x00400000, + 0x00401080, 0x00000001, 0x00001001, 0x00401081, + 0x00400001, 0x00401080, 0x00401000, 0x00001001 ], + + # nibble 7 + [ 0x08200020, 0x08208000, 0x00008020, 0x00000000, + 0x08008000, 0x00200020, 0x08200000, 0x08208020, + 0x00000020, 0x08000000, 0x00208000, 0x00008020, + 0x00208020, 0x08008020, 0x08000020, 0x08200000, + 0x00008000, 0x00208020, 0x00200020, 0x08008000, + 0x08208020, 0x08000020, 0x00000000, 0x00208000, + 0x08000000, 0x00200000, 0x08008020, 0x08200020, + 0x00200000, 0x00008000, 0x08208000, 0x00000020, + 0x00200000, 0x00008000, 0x08000020, 0x08208020, + 0x00008020, 0x08000000, 0x00000000, 0x00208000, + 0x08200020, 0x08008020, 0x08008000, 0x00200020, + 0x08208000, 0x00000020, 0x00200020, 0x08008000, + 0x08208020, 0x00200000, 0x08200000, 0x08000020, + 0x00208000, 0x00008020, 0x08008020, 0x08200000, + 0x00000020, 0x08208000, 0x00208020, 0x00000000, + 0x08000000, 0x08200020, 0x00008000, 0x00208020 ] ) + +_skb = ( + # for C bits (numbered as per FIPS 46) 1 2 3 4 5 6 + [ 0x00000000, 0x00000010, 0x20000000, 0x20000010, + 0x00010000, 0x00010010, 0x20010000, 0x20010010, + 0x00000800, 0x00000810, 0x20000800, 0x20000810, + 0x00010800, 0x00010810, 0x20010800, 0x20010810, + 0x00000020, 0x00000030, 0x20000020, 0x20000030, + 0x00010020, 0x00010030, 0x20010020, 0x20010030, + 0x00000820, 0x00000830, 0x20000820, 0x20000830, + 0x00010820, 0x00010830, 0x20010820, 0x20010830, + 0x00080000, 0x00080010, 0x20080000, 0x20080010, + 0x00090000, 0x00090010, 0x20090000, 0x20090010, + 0x00080800, 0x00080810, 0x20080800, 0x20080810, + 0x00090800, 0x00090810, 0x20090800, 0x20090810, + 0x00080020, 0x00080030, 0x20080020, 0x20080030, + 0x00090020, 0x00090030, 0x20090020, 0x20090030, + 0x00080820, 0x00080830, 0x20080820, 0x20080830, + 0x00090820, 0x00090830, 0x20090820, 0x20090830 ], + + # for C bits (numbered as per FIPS 46) 7 8 10 11 12 13 + [ 0x00000000, 0x02000000, 0x00002000, 0x02002000, + 0x00200000, 0x02200000, 0x00202000, 0x02202000, + 0x00000004, 0x02000004, 0x00002004, 0x02002004, + 0x00200004, 0x02200004, 0x00202004, 0x02202004, + 0x00000400, 0x02000400, 0x00002400, 0x02002400, + 0x00200400, 0x02200400, 0x00202400, 0x02202400, + 0x00000404, 0x02000404, 0x00002404, 0x02002404, + 0x00200404, 0x02200404, 0x00202404, 0x02202404, + 0x10000000, 0x12000000, 0x10002000, 0x12002000, + 0x10200000, 0x12200000, 0x10202000, 0x12202000, + 0x10000004, 0x12000004, 0x10002004, 0x12002004, + 0x10200004, 0x12200004, 0x10202004, 0x12202004, + 0x10000400, 0x12000400, 0x10002400, 0x12002400, + 0x10200400, 0x12200400, 0x10202400, 0x12202400, + 0x10000404, 0x12000404, 0x10002404, 0x12002404, + 0x10200404, 0x12200404, 0x10202404, 0x12202404 ], + + # for C bits (numbered as per FIPS 46) 14 15 16 17 19 20 + [ 0x00000000, 0x00000001, 0x00040000, 0x00040001, + 0x01000000, 0x01000001, 0x01040000, 0x01040001, + 0x00000002, 0x00000003, 0x00040002, 0x00040003, + 0x01000002, 0x01000003, 0x01040002, 0x01040003, + 0x00000200, 0x00000201, 0x00040200, 0x00040201, + 0x01000200, 0x01000201, 0x01040200, 0x01040201, + 0x00000202, 0x00000203, 0x00040202, 0x00040203, + 0x01000202, 0x01000203, 0x01040202, 0x01040203, + 0x08000000, 0x08000001, 0x08040000, 0x08040001, + 0x09000000, 0x09000001, 0x09040000, 0x09040001, + 0x08000002, 0x08000003, 0x08040002, 0x08040003, + 0x09000002, 0x09000003, 0x09040002, 0x09040003, + 0x08000200, 0x08000201, 0x08040200, 0x08040201, + 0x09000200, 0x09000201, 0x09040200, 0x09040201, + 0x08000202, 0x08000203, 0x08040202, 0x08040203, + 0x09000202, 0x09000203, 0x09040202, 0x09040203 ], + + # for C bits (numbered as per FIPS 46) 21 23 24 26 27 28 + [ 0x00000000, 0x00100000, 0x00000100, 0x00100100, + 0x00000008, 0x00100008, 0x00000108, 0x00100108, + 0x00001000, 0x00101000, 0x00001100, 0x00101100, + 0x00001008, 0x00101008, 0x00001108, 0x00101108, + 0x04000000, 0x04100000, 0x04000100, 0x04100100, + 0x04000008, 0x04100008, 0x04000108, 0x04100108, + 0x04001000, 0x04101000, 0x04001100, 0x04101100, + 0x04001008, 0x04101008, 0x04001108, 0x04101108, + 0x00020000, 0x00120000, 0x00020100, 0x00120100, + 0x00020008, 0x00120008, 0x00020108, 0x00120108, + 0x00021000, 0x00121000, 0x00021100, 0x00121100, + 0x00021008, 0x00121008, 0x00021108, 0x00121108, + 0x04020000, 0x04120000, 0x04020100, 0x04120100, + 0x04020008, 0x04120008, 0x04020108, 0x04120108, + 0x04021000, 0x04121000, 0x04021100, 0x04121100, + 0x04021008, 0x04121008, 0x04021108, 0x04121108 ], + + # for D bits (numbered as per FIPS 46) 1 2 3 4 5 6 + [ 0x00000000, 0x10000000, 0x00010000, 0x10010000, + 0x00000004, 0x10000004, 0x00010004, 0x10010004, + 0x20000000, 0x30000000, 0x20010000, 0x30010000, + 0x20000004, 0x30000004, 0x20010004, 0x30010004, + 0x00100000, 0x10100000, 0x00110000, 0x10110000, + 0x00100004, 0x10100004, 0x00110004, 0x10110004, + 0x20100000, 0x30100000, 0x20110000, 0x30110000, + 0x20100004, 0x30100004, 0x20110004, 0x30110004, + 0x00001000, 0x10001000, 0x00011000, 0x10011000, + 0x00001004, 0x10001004, 0x00011004, 0x10011004, + 0x20001000, 0x30001000, 0x20011000, 0x30011000, + 0x20001004, 0x30001004, 0x20011004, 0x30011004, + 0x00101000, 0x10101000, 0x00111000, 0x10111000, + 0x00101004, 0x10101004, 0x00111004, 0x10111004, + 0x20101000, 0x30101000, 0x20111000, 0x30111000, + 0x20101004, 0x30101004, 0x20111004, 0x30111004 ], + + # for D bits (numbered as per FIPS 46) 8 9 11 12 13 14 + [ 0x00000000, 0x08000000, 0x00000008, 0x08000008, + 0x00000400, 0x08000400, 0x00000408, 0x08000408, + 0x00020000, 0x08020000, 0x00020008, 0x08020008, + 0x00020400, 0x08020400, 0x00020408, 0x08020408, + 0x00000001, 0x08000001, 0x00000009, 0x08000009, + 0x00000401, 0x08000401, 0x00000409, 0x08000409, + 0x00020001, 0x08020001, 0x00020009, 0x08020009, + 0x00020401, 0x08020401, 0x00020409, 0x08020409, + 0x02000000, 0x0A000000, 0x02000008, 0x0A000008, + 0x02000400, 0x0A000400, 0x02000408, 0x0A000408, + 0x02020000, 0x0A020000, 0x02020008, 0x0A020008, + 0x02020400, 0x0A020400, 0x02020408, 0x0A020408, + 0x02000001, 0x0A000001, 0x02000009, 0x0A000009, + 0x02000401, 0x0A000401, 0x02000409, 0x0A000409, + 0x02020001, 0x0A020001, 0x02020009, 0x0A020009, + 0x02020401, 0x0A020401, 0x02020409, 0x0A020409 ], + + # for D bits (numbered as per FIPS 46) 16 17 18 19 20 21 + [ 0x00000000, 0x00000100, 0x00080000, 0x00080100, + 0x01000000, 0x01000100, 0x01080000, 0x01080100, + 0x00000010, 0x00000110, 0x00080010, 0x00080110, + 0x01000010, 0x01000110, 0x01080010, 0x01080110, + 0x00200000, 0x00200100, 0x00280000, 0x00280100, + 0x01200000, 0x01200100, 0x01280000, 0x01280100, + 0x00200010, 0x00200110, 0x00280010, 0x00280110, + 0x01200010, 0x01200110, 0x01280010, 0x01280110, + 0x00000200, 0x00000300, 0x00080200, 0x00080300, + 0x01000200, 0x01000300, 0x01080200, 0x01080300, + 0x00000210, 0x00000310, 0x00080210, 0x00080310, + 0x01000210, 0x01000310, 0x01080210, 0x01080310, + 0x00200200, 0x00200300, 0x00280200, 0x00280300, + 0x01200200, 0x01200300, 0x01280200, 0x01280300, + 0x00200210, 0x00200310, 0x00280210, 0x00280310, + 0x01200210, 0x01200310, 0x01280210, 0x01280310 ], + + # for D bits (numbered as per FIPS 46) 22 23 24 25 27 28 + [ 0x00000000, 0x04000000, 0x00040000, 0x04040000, + 0x00000002, 0x04000002, 0x00040002, 0x04040002, + 0x00002000, 0x04002000, 0x00042000, 0x04042000, + 0x00002002, 0x04002002, 0x00042002, 0x04042002, + 0x00000020, 0x04000020, 0x00040020, 0x04040020, + 0x00000022, 0x04000022, 0x00040022, 0x04040022, + 0x00002020, 0x04002020, 0x00042020, 0x04042020, + 0x00002022, 0x04002022, 0x00042022, 0x04042022, + 0x00000800, 0x04000800, 0x00040800, 0x04040800, + 0x00000802, 0x04000802, 0x00040802, 0x04040802, + 0x00002800, 0x04002800, 0x00042800, 0x04042800, + 0x00002802, 0x04002802, 0x00042802, 0x04042802, + 0x00000820, 0x04000820, 0x00040820, 0x04040820, + 0x00000822, 0x04000822, 0x00040822, 0x04040822, + 0x00002820, 0x04002820, 0x00042820, 0x04042820, + 0x00002822, 0x04002822, 0x00042822, 0x04042822 ] ) + +_shifts2 = (0,0,1,1,1,1,1,1,0,1,1,1,1,1,1,0) + +_con_salt = [ + 0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9, + 0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,0xE0,0xE1, + 0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9, + 0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1, + 0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9, + 0xFA,0xFB,0xFC,0xFD,0xFE,0xFF,0x00,0x01, + 0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09, + 0x0A,0x0B,0x05,0x06,0x07,0x08,0x09,0x0A, + 0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12, + 0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A, + 0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22, + 0x23,0x24,0x25,0x20,0x21,0x22,0x23,0x24, + 0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C, + 0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0x34, + 0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C, + 0x3D,0x3E,0x3F,0x40,0x41,0x42,0x43,0x44 ] + +_cov_2char = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + + +def _HPERM_OP(a): + """Clever bit manipulation.""" + t = ((a << 18) ^ a) & 0xcccc0000 + return a ^ t ^ ((t >> 18) & 0x3fff) + +def _PERM_OP(a,b,n,m): + """Cleverer bit manipulation.""" + t = ((a >> n) ^ b) & m + b = b ^ t + a = a ^ (t << n) + return a,b + + +def _set_key(password): + """Generate DES key schedule from ASCII password.""" + + c,d = struct.unpack('> 16) | ((c >> 4) & 0x0f000000)) + c = c & 0x0fffffff + + # Copy globals into local variables for loop. + shifts2 = _shifts2 + skbc0, skbc1, skbc2, skbc3, skbd0, skbd1, skbd2, skbd3 = _skb + + k = [0] * (_ITERATIONS * 2) + + for i in range(_ITERATIONS): + # Only operates on top 28 bits. + if shifts2[i]: + c = (c >> 2) | (c << 26) + d = (d >> 2) | (d << 26) + else: + c = (c >> 1) | (c << 27) + d = (d >> 1) | (d << 27) + c = c & 0x0fffffff + d = d & 0x0fffffff + + s = ( skbc0[ c & 0x3f ] | + skbc1[((c>> 6) & 0x03) | ((c>> 7) & 0x3c)] | + skbc2[((c>>13) & 0x0f) | ((c>>14) & 0x30)] | + skbc3[((c>>20) & 0x01) | + ((c>>21) & 0x06) | ((c>>22) & 0x38)] ) + + t = ( skbd0[ d & 0x3f ] | + skbd1[((d>> 7) & 0x03) | ((d>> 8) & 0x3c)] | + skbd2[((d>>15) & 0x3f) ] | + skbd3[((d>>21) & 0x0f) | ((d>>22) & 0x30)] ) + + k[2*i] = ((t << 16) | (s & 0x0000ffff)) & 0xffffffff + s = (s >> 16) | (t & 0xffff0000) + + # Top bit of s may be 1. + s = (s << 4) | ((s >> 28) & 0x0f) + k[2*i + 1] = s & 0xffffffff + + return k + + +def _body(ks, E0, E1): + """Use the key schedule ks and salt E0, E1 to create the password hash.""" + + # Copy global variable into locals for loop. + SP0, SP1, SP2, SP3, SP4, SP5, SP6, SP7 = _SPtrans + + inner = range(0, _ITERATIONS*2, 2) + l = r = 0 + for j in range(25): + l,r = r,l + for i in inner: + t = r ^ ((r >> 16) & 0xffff) + u = t & E0 + t = t & E1 + u = u ^ (u << 16) ^ r ^ ks[i] + t = t ^ (t << 16) ^ r ^ ks[i+1] + t = ((t >> 4) & 0x0fffffff) | (t << 28) + + l,r = r,(SP1[(t ) & 0x3f] ^ SP3[(t>> 8) & 0x3f] ^ + SP5[(t>>16) & 0x3f] ^ SP7[(t>>24) & 0x3f] ^ + SP0[(u ) & 0x3f] ^ SP2[(u>> 8) & 0x3f] ^ + SP4[(u>>16) & 0x3f] ^ SP6[(u>>24) & 0x3f] ^ l) + + l = ((l >> 1) & 0x7fffffff) | ((l & 0x1) << 31) + r = ((r >> 1) & 0x7fffffff) | ((r & 0x1) << 31) + + r,l = _PERM_OP(r, l, 1, 0x55555555) + l,r = _PERM_OP(l, r, 8, 0x00ff00ff) + r,l = _PERM_OP(r, l, 2, 0x33333333) + l,r = _PERM_OP(l, r, 16, 0x0000ffff) + r,l = _PERM_OP(r, l, 4, 0x0f0f0f0f) + + return l,r + + +def crypt(password, salt): + """Generate an encrypted hash from the passed password. If the password +is longer than eight characters, only the first eight will be used. + +The first two characters of the salt are used to modify the encryption +algorithm used to generate in the hash in one of 4096 different ways. +The characters for the salt must be alphanumeric, '.' or '/'. + +The returned hash begins with the two characters of the salt, and +should be passed as the salt to verify the password. + +Example: + + >>> from fcrypt import crypt + >>> password = 'AlOtBsOl' + >>> salt = 'cE' + >>> hash = crypt(password, salt) + >>> hash + 'cEpWz5IUCShqM' + >>> crypt(password, hash) == hash + 1 + >>> crypt('IaLaIoK', hash) == hash + 0 + +In practice, you would read the password using something like the +getpass module, and generate the salt randomly: + + >>> import random, string + >>> saltchars = string.letters + string.digits + './' + >>> salt = random.choice(saltchars) + random.choice(saltchars) +""" + + if len(salt) < 2: + salt = salt + 'AA' + + Eswap0 = _con_salt[ord(salt[0])] + Eswap1 = _con_salt[ord(salt[1])] << 4 + + ks = _set_key((password + '\0\0\0\0\0\0\0\0')[:8]) + out1,out2 = _body(ks, Eswap0, Eswap1) + + # Convert numbers to big-endian... + be1, be2 = struct.unpack('>ii', struct.pack('> 8) & 0xffffff, + ((be1 << 16) & 0xff0000) | ((be2 >> 16) & 0xffff), + (be2 << 8) & 0xffff00] + + # Convert to ASCII encoding, 4 characters for each 24 bits. + res = [salt[0], salt[1]] + for b in b24: + for i in range(18, -6, -6): + res.append(_cov_2char[(b >> i) & 0x3f]) + + return string.join(res[:13], '') + +def _test(): + """Run doctest on fcrypt module.""" + import doctest, fcrypt + return doctest.testmod(fcrypt) + +if __name__ == '__main__': + _test() diff --git a/ZopeProducts/exUserFolder/CryptoSources/fcrypt/setup.py b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/setup.py new file mode 100644 index 00000000..33ccb6d3 --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/fcrypt/setup.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +# distutils setup script for fcrypt. +# +# Copyright (C) 2000, 2001 Carey Evans + +from distutils.core import setup + +setup( name = 'fcrypt', + version = '1.2', + description = 'The Unix password crypt function.', + author = 'Carey Evans', + author_email = 'careye@spamcop.net', + url = 'http://home.clear.net.nz/pages/c.evans/sw/', + licence = 'BSD', + long_description = """\ +A pure Python implementation of the Unix DES password crypt function, +based on Eric Young's fcrypt.c. It works with any version of Python +from version 1.5 or higher, and because it's pure Python it doesn't +need a C compiler to install it.""", + + py_modules = ['fcrypt'] ) diff --git a/ZopeProducts/exUserFolder/CryptoSources/pass_crypt.py b/ZopeProducts/exUserFolder/CryptoSources/pass_crypt.py new file mode 100644 index 00000000..ef1d2954 --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/pass_crypt.py @@ -0,0 +1,44 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: pass_crypt.py,v 1.3 2004/11/18 09:24:46 akm Exp $ + +from Products.exUserFolder.exUserFolder import exUserFolder +from Products.exUserFolder.Plugins import CryptoPluginRegister + +try: + from crypt import crypt +except: + from fcrypt.fcrypt import crypt + + +def cryptPassword(authSource, username, password): + u = authSource.listOneUser(username) + if not u: + salt = username[:2] + else: + salt=u[0]['password'][:2] + + secret = crypt(password, salt) + return secret + + +CryptPlugin=CryptoPluginRegister('Crypt', 'crypt', 'Crypt', cryptPassword) +exUserFolder.cryptoSources['Crypt']=CryptPlugin diff --git a/ZopeProducts/exUserFolder/CryptoSources/pass_md5.py b/ZopeProducts/exUserFolder/CryptoSources/pass_md5.py new file mode 100644 index 00000000..c67af140 --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/pass_md5.py @@ -0,0 +1,47 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: pass_md5.py,v 1.1 2004/11/10 14:15:52 akm Exp $ + +import md5, base64, string + +from Products.exUserFolder.exUserFolder import exUserFolder +from Products.exUserFolder.Plugins import CryptoPluginRegister + +# Simple digest +def cryptPassword(authSource, username, password): + digest = md5.new() + digest.update(password) + digest = digest.digest() + secret = string.strip(base64.encodestring(digest)) + return secret + +# Digest includes username +# So two passwords for different users hash differently +def cryptPassword2(authSource, username, password): + newPass = username+':'+password + return cryptPassword(authSource, username, newPass) + + +MD5Plugin1=CryptoPluginRegister('MD51', 'MD5', 'MD5 Password Only', cryptPassword) +exUserFolder.cryptoSources['MD51']=MD5Plugin1 + +MD5Plugin2=CryptoPluginRegister('MD52', 'MD5', 'MD5 Username + Password', cryptPassword2) +exUserFolder.cryptoSources['MD52']=MD5Plugin2 diff --git a/ZopeProducts/exUserFolder/CryptoSources/pass_plain.py b/ZopeProducts/exUserFolder/CryptoSources/pass_plain.py new file mode 100644 index 00000000..2d29c78d --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/pass_plain.py @@ -0,0 +1,31 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: pass_plain.py,v 1.1 2004/11/10 14:15:52 akm Exp $ + +from Products.exUserFolder.exUserFolder import exUserFolder +from Products.exUserFolder.Plugins import CryptoPluginRegister + +# Simple digest +def cryptPassword(authSource, username, password): + return password + +PlainPlugin=CryptoPluginRegister('Plaintext', 'Plaintext', 'No Encryption', cryptPassword) +exUserFolder.cryptoSources['Plaintext']=PlainPlugin diff --git a/ZopeProducts/exUserFolder/CryptoSources/pass_sha.py b/ZopeProducts/exUserFolder/CryptoSources/pass_sha.py new file mode 100644 index 00000000..a4bc8fd3 --- /dev/null +++ b/ZopeProducts/exUserFolder/CryptoSources/pass_sha.py @@ -0,0 +1,41 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: pass_sha.py,v 1.1 2004/11/10 14:15:52 akm Exp $ + +import sha +from base64 import encodestring + +from Products.exUserFolder.exUserFolder import exUserFolder +from Products.exUserFolder.Plugins import CryptoPluginRegister + + +def cryptPassword(authSource, username, password): + return encodestring(sha.new(password).digest()) + +def cryptPassword2(authSource, username, password): + newPass = username+':'+password + return cryptPassword(authSource, username, newPass) + +SHAPlugin1=CryptoPluginRegister('SHA1', 'SHA', 'SHA Password Only', cryptPassword) +exUserFolder.cryptoSources['SHA1']=SHAPlugin1 + +SHAPlugin2=CryptoPluginRegister('SHA2', 'SHA', 'SHA Username + Password', cryptPassword2) +exUserFolder.cryptoSources['SHA2']=SHAPlugin2 diff --git a/ZopeProducts/exUserFolder/Extensions/.cvsignore b/ZopeProducts/exUserFolder/Extensions/.cvsignore new file mode 100644 index 00000000..57c4bccb --- /dev/null +++ b/ZopeProducts/exUserFolder/Extensions/.cvsignore @@ -0,0 +1,4 @@ +*.pyc +*.pyo +*~ +.*.swp diff --git a/ZopeProducts/exUserFolder/Extensions/getOldGroups.py b/ZopeProducts/exUserFolder/Extensions/getOldGroups.py new file mode 100644 index 00000000..9fa8088e --- /dev/null +++ b/ZopeProducts/exUserFolder/Extensions/getOldGroups.py @@ -0,0 +1,20 @@ +# This script interrogates the old-skool NuxUserGroups_support_branch +# group structure and outputs a tab-delimited file you can send to +# loadOldGroups. Just in case anyone is using it. :-) +# +# Matt Behrens + +def getOldGroups(self): + "Reconstruct a group list from the old-style _groups property" + from string import join + props = self.currentPropSource.userProperties + groups = {} + for username in props.keys(): + for groupname in props[username].getProperty('_groups', ()): + if not groups.has_key(groupname): + groups[groupname] = [] + groups[groupname].append(username) + out = '' + for groupname in groups.keys(): + out = out + '%s %s\n' % (groupname, join(groups[groupname], ' ')) + return out diff --git a/ZopeProducts/exUserFolder/Extensions/loadOldGroups.py b/ZopeProducts/exUserFolder/Extensions/loadOldGroups.py new file mode 100644 index 00000000..b7f2f702 --- /dev/null +++ b/ZopeProducts/exUserFolder/Extensions/loadOldGroups.py @@ -0,0 +1,26 @@ +# This takes 'old_groups.txt' from var (create it using getOldGroups) +# and sets up all the groups therein using NuxUserGroups calls. This +# will load a group source if you need to do such a thing. +# +# Matt Behrens + +def loadOldGroups(self): + from os.path import join as pathJoin + from string import split, strip + + groups_file = open(pathJoin(CLIENT_HOME, 'old_groups.txt'), 'r') + out = '' + for group_line in groups_file.readlines(): + group_line_elements = split(strip(group_line), ' ') + group_name = group_line_elements[0] + group_members = group_line_elements[1:] + + if self.getGroupById(group_name, default=None) is None: + out = out + 'adding group %s\n' % group_name + self.userFolderAddGroup(group_name) + + out = out + 'setting group %s membership to %s\n' % (group_name, group_members) + self.setUsersOfGroup(group_members, group_name) + + return out + diff --git a/ZopeProducts/exUserFolder/Extensions/usAuthSourceMethods.py b/ZopeProducts/exUserFolder/Extensions/usAuthSourceMethods.py new file mode 100644 index 00000000..bb43162d --- /dev/null +++ b/ZopeProducts/exUserFolder/Extensions/usAuthSourceMethods.py @@ -0,0 +1,140 @@ +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: usAuthSourceMethods.py,v 1.3 2001/12/01 08:40:04 akm Exp $ +# +######################################################################## +# +# This is an example of an Extension Module to provide User Supplied +# Authentication Methods. +# +# It mimics the behaviour of the pgAuthSource Module, and the sql queries +# Used here would be added as ZSQLMethods in the usAuthSource Folder. +# (you can basically cut and paste them from the bottom of this .py file +# into the ZSQL Method Template Area +# +# It's not complete, but, you do get the idea... +# +# Each function becomes usFunctionName +# +# e.g. listOneUser -> usListOneUser +# +import string +from crypt import crypt + +def listOneUser(self,username): + users = [] + result=self.sqlListOneUser(username=username) + for n in result: + username=sqlattr(n,'username') + password=sqlattr(n,'password') + roles=string.split(sqlattr(n,'roles')) + N={'username':username, 'password':password, 'roles':roles} + users.append(N) + return users + +def listUsers(self): + """Returns a list of user names or [] if no users exist""" + users = [] + result=self.sqlListUsers() + for n in result: + username=sqlattr(n,'username') + N={'username':username} + users.append(N) + return users + +def getUsers(self): + """Return a list of user objects or [] if no users exist""" + data=[] + try: items=self.listusers() + except: return data + for people in items: + roles=string.split(people['roles'],',') + user=User(people['username'], roles, '') + data.append(user) + return data + +def cryptPassword(self, username, password): + salt =username[:2] + secret = crypt(password, salt) + return secret + +def deleteUsers(self, userids): + for uid in userids: + self.sqlDeleteOneUser(userid=uid) + + +# Helper Functions... +from string import upper, lower +import Missing +mt=type(Missing.Value) + +def typeconv(val): + if type(val)==mt: + return '' + return val + +def sqlattr(ob, attr): + name=attr + if hasattr(ob, attr): + return typeconv(getattr(ob, attr)) + attr=upper(attr) + if hasattr(ob, attr): + return typeconv(getattr(ob, attr)) + attr=lower(attr) + if hasattr(ob, attr): + return typeconv(getattr(ob, attr)) + raise NameError, name + + +######################################################################## +# SQL METHODS USED ABOVE +# PASTE INTO ZSQL METHODS +# take note of what parameters are used in each query +######################################################################## + +_sqlListUsers=""" +SELECT * FROM passwd +""" + +_sqlListOneUser=""" +SELECT * FROM passwd +where username= +""" + +_sqlDeleteOneUser=""" +DELETE FROM passwd +where uid= +""" + +_sqlInsertUser=""" +INSERT INTO passwd (username, password, roles) +VALUES (, + , + ) +""" + +_sqlUpdateUserPassword=""" +UPDATE passwd set password= +WHERE username= +""" + +_sqlUpdateUser=""" +UPDATE passwd set roles= +WHERE username= +""" + diff --git a/ZopeProducts/exUserFolder/GroupSource/.cvsignore b/ZopeProducts/exUserFolder/GroupSource/.cvsignore new file mode 100644 index 00000000..57c4bccb --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSource/.cvsignore @@ -0,0 +1,4 @@ +*.pyc +*.pyo +*~ +.*.swp diff --git a/ZopeProducts/exUserFolder/GroupSource/GroupSource.py b/ZopeProducts/exUserFolder/GroupSource/GroupSource.py new file mode 100644 index 00000000..478ab0b3 --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSource/GroupSource.py @@ -0,0 +1,32 @@ +# +# Extensible User Folder +# +# Null Group Source for exUserFolder +# +# Author: Brent Hendricks +# $Id: GroupSource.py,v 1.1 2002/12/02 23:20:49 bmh Exp $ +from Globals import DTMLFile + + +manage_addGroupSourceForm=DTMLFile('manage_addGroupSourceForm', globals(), __name__='manage_addGroupSourceForm') + + +def manage_addGroupSource(dispatcher, REQUEST): + """ Add a Group Source """ + + # Get the XUF object we're being added to + xuf = dispatcher.Destination() + + groupId = REQUEST.get('groupId', None) + if groupId: + # Invoke the add method for this plugin + xuf.groupSources[groupId].manage_addMethod(xuf, REQUEST) + else: + raise "BadRequest", "Required parameter 'groupId' omitted" + + dispatcher.manage_main(dispatcher, REQUEST) + + +class GroupSource: + pass + diff --git a/ZopeProducts/exUserFolder/GroupSource/__init__.py b/ZopeProducts/exUserFolder/GroupSource/__init__.py new file mode 100644 index 00000000..dc5d3a81 --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSource/__init__.py @@ -0,0 +1,2 @@ +# $Id: __init__.py,v 1.1 2002/12/02 23:20:49 bmh Exp $ +import GroupSource diff --git a/ZopeProducts/exUserFolder/GroupSource/manage_addGroupSourceForm.dtml b/ZopeProducts/exUserFolder/GroupSource/manage_addGroupSourceForm.dtml new file mode 100644 index 00000000..3e6605a0 --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSource/manage_addGroupSourceForm.dtml @@ -0,0 +1,33 @@ + + + + + + + + + + +
      + + + + + + + + + +
      + Group Source + + +

      Add ">
      +
      + +
      + \ No newline at end of file diff --git a/ZopeProducts/exUserFolder/GroupSources/__init__.py b/ZopeProducts/exUserFolder/GroupSources/__init__.py new file mode 100644 index 00000000..4f36cdc5 --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSources/__init__.py @@ -0,0 +1,31 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: __init__.py,v 1.1 2004/11/10 14:15:53 akm Exp $ + +import nullGroupSource + +# If this fails due to NUG being absent, just skip it +try: + import zodbGroupSource +except ImportError: + pass + + diff --git a/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/.cvsignore b/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/.cvsignore new file mode 100644 index 00000000..57c4bccb --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/.cvsignore @@ -0,0 +1,4 @@ +*.pyc +*.pyo +*~ +.*.swp diff --git a/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/__init__.py b/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/__init__.py new file mode 100644 index 00000000..b8943369 --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/__init__.py @@ -0,0 +1,2 @@ +# $Id: __init__.py,v 1.1 2004/11/10 14:15:53 akm Exp $ +import nullGroupSource diff --git a/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/manage_addNullPluginSourceForm.dtml b/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/manage_addNullPluginSourceForm.dtml new file mode 100644 index 00000000..f57a2c3a --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/manage_addNullPluginSourceForm.dtml @@ -0,0 +1,21 @@ + +
      + + + + + + + + + "> + + + + + +This Group Source has no configuration Items
      +
      +Add"> +
      + diff --git a/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/nullGroupSource.py b/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/nullGroupSource.py new file mode 100644 index 00000000..cee50ff9 --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSources/nullGroupSource/nullGroupSource.py @@ -0,0 +1,34 @@ +# +# Extensible User Folder +# +# Null Group Source for exUserFolder +# +# Author: Brent Hendricks +# $Id: nullGroupSource.py,v 1.1 2004/11/10 14:15:53 akm Exp $ +from Globals import HTMLFile, INSTANCE_HOME + +from OFS.Folder import Folder + +from Products.exUserFolder.exUserFolder import exUserFolder +from Products.exUserFolder.Plugins import PluginRegister +from Products.exUserFolder.nullPlugin import nullPlugin + +def manage_addNullGroupSource(self, REQUEST): + """ Add a Group Source """ + self.currentGroupSource=None + return '' + + +manage_addNullGroupSourceForm=HTMLFile('manage_addNullPluginSourceForm',globals()) +manage_editNullGroupSourceForm=None + + +nullGroupReg=PluginRegister('nullGroupSource', + 'Null Group Source', + nullPlugin, + manage_addNullGroupSourceForm, + manage_addNullGroupSource, + manage_editNullGroupSourceForm) + +exUserFolder.groupSources['nullGroupSource']=nullGroupReg + diff --git a/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/.cvsignore b/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/.cvsignore new file mode 100644 index 00000000..57c4bccb --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/.cvsignore @@ -0,0 +1,4 @@ +*.pyc +*.pyo +*~ +.*.swp diff --git a/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/__init__.py b/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/__init__.py new file mode 100644 index 00000000..c2e979ba --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/__init__.py @@ -0,0 +1,2 @@ +# $Id: __init__.py,v 1.1 2004/11/10 14:15:54 akm Exp $ +import zodbGroupSource diff --git a/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/manage_addzodbGroupSourceForm.dtml b/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/manage_addzodbGroupSourceForm.dtml new file mode 100644 index 00000000..2d618cf5 --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/manage_addzodbGroupSourceForm.dtml @@ -0,0 +1,21 @@ + + +
      + + + + + + + + + "> + + + + + +This group source requires no user configuration items at this time.
      +NEXT "> +
      + diff --git a/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/manage_editzodbGroupSourceForm.dtml b/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/manage_editzodbGroupSourceForm.dtml new file mode 100644 index 00000000..3c04422e --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/manage_editzodbGroupSourceForm.dtml @@ -0,0 +1,7 @@ + + +
      +This group source requires no user configuration items at this time.
      +OK "> +
      + diff --git a/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/zodbGroupSource.py b/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/zodbGroupSource.py new file mode 100644 index 00000000..78b441b6 --- /dev/null +++ b/ZopeProducts/exUserFolder/GroupSources/zodbGroupSource/zodbGroupSource.py @@ -0,0 +1,177 @@ +# +# Extensible User Folder +# +# ZODB Group Source for exUserFolder +# +# Author: Brent Hendricks +# $Id: zodbGroupSource.py,v 1.1 2004/11/10 14:15:54 akm Exp $ +from Globals import HTMLFile, MessageDialog, INSTANCE_HOME,Acquisition, PersistentMapping + +from OFS.Folder import Folder + +from Products.ZSQLMethods.SQL import SQL + +from Products.exUserFolder.exUserFolder import exUserFolder +from Products.exUserFolder.Plugins import PluginRegister +from Products.NuxUserGroups.UserFolderWithGroups import Group, _marker + +import time +import zLOG +import sys + +manage_addGroupSourceForm=HTMLFile('manage_addzodbGroupSourceForm', globals()) + +def manage_addzodbGroupSource(self, REQUEST): + """ Add a ZODB Group Source """ + + o = zodbGroupSource() + self._setObject('zodbGroupSource', o, None, None, 0) + o = getattr(self, 'zodbGroupSource') + + # Allow Prop Source to setup default users... + if hasattr(o, 'postInitialisation'): + o.postInitialisation(REQUEST) + self.currentGroupSource=o + +manage_addzodbGroupSourceForm=HTMLFile('manage_addzodbGroupSourceForm', globals()) +manage_editzodbGroupSourceForm=HTMLFile('manage_editzodbGroupSourceForm', globals()) + +# +# Very very simple thing, used as an example of how to write a property source +# Not recommended for large scale production sites... +# + +class zodbGroupSource(Folder): + """ Store Group Data inside ZODB, the simplistic way """ + + meta_type='Group Source' + title='Simplistic ZODB Groups' + icon ='misc_/exUserFolder/exUserFolderPlugin.gif' + manage_editForm=manage_editzodbGroupSourceForm + manage_tabs=Acquisition.Acquired + + def __init__(self): + self.id='zodbGroupSource' + self.groups=PersistentMapping() + + + def addGroup(self, groupname, title='', users=(), **kw): + """Creates a group""" + if self.groups.has_key(groupname): + raise ValueError, 'Group "%s" already exists' % groupname + a = 'before: groupname %s groups %s' % (groupname, self.groups) + group = apply(Group, (groupname,), kw) + group.setTitle(title) + group._setUsers(users) + self.groups[groupname] = group + + + def getGroup(self, groupname, default=_marker): + """Returns the given group""" + try: + group = self.groups[groupname] + except KeyError: + if default is _marker: raise + return default + return group + + + def delGroup(self, groupname): + """Deletes the given group""" + usernames = self.groups[groupname].getUsers() + #self.delUsersFromGroup(usernames, groupname) + del self.groups[groupname] + + + def listGroups(self): + """Returns a list of group names""" + return tuple(self.groups.keys()) + + + def getGroupsOfUser(self, username): + "Get a user's groups" + groupnames = [] + allnames = self.listGroups() + groupnames = filter(lambda g, u=username, self=self: u in self.groups[g].getUsers(), allnames) + return tuple(groupnames) + + + def setGroupsOfUser(self, groupnames, username): + "Set a user's groups" + oldGroups = self.getGroupsOfUser(username) + self.delGroupsFromUser(oldGroups, username) + self.addGroupsToUser(groupnames, username) + + + def addGroupsToUser(self, groupnames, username): + "Add groups to a user" + for name in groupnames: + group = self.groups[name] + if not username in group.getUsers(): + group._addUsers([username]) + + + def delGroupsFromUser(self, groupnames, username): + "Delete groups from a user" + for name in groupnames: + group = self.groups[name] + if username in group.getUsers(): + group._delUsers([username]) + + + def getUsersOfGroup(self, groupname): + "Get the users in a group" + return self.groups[groupname].getUsers() + + + def setUsersOfGroup(self, usernames, groupname): + "Set the users in a group" + # uniquify + dict = {} + for u in usernames: dict[u] = None + usernames = dict.keys() + + self.groups[groupname]._setUsers(usernames) + + + def addUsersToGroup(self, usernames, groupname): + "Add users to a group" + # uniquify + dict = {} + for u in usernames: dict[u] = None + usernames = dict.keys() + + self.groups[groupname]._addUsers(usernames) + + + def delUsersFromGroup(self, usernames, groupname): + "Delete users from a group" + # uniquify + dict = {} + for u in usernames: dict[u] = None + usernames = dict.keys() + + self.groups[groupname]._delUsers(usernames) + + + def deleteUsers(self, usernames): + "Delete a list of users" + for user in usernames: + groups = self.getGroupsOfUser(user) + self.delGroupsFromUser(groups, user) + + + def postInitialisation(self, REQUEST): + pass + + + def manage_beforeDelete(self, item, container): + # Notify the exUserFolder that it doesn't have a group source anymore + container.currentGroupSource=None + + +zodbGroupReg=PluginRegister('zodbGroupSource','Simplistic ZODB Group Source', + zodbGroupSource, manage_addzodbGroupSourceForm, + manage_addzodbGroupSource, + manage_editzodbGroupSourceForm) +exUserFolder.groupSources['zodbGroupSource']=zodbGroupReg diff --git a/ZopeProducts/exUserFolder/I18N/ZBabelDictionary_txt.zexp b/ZopeProducts/exUserFolder/I18N/ZBabelDictionary_txt.zexp new file mode 100644 index 00000000..be6e17c2 Binary files /dev/null and b/ZopeProducts/exUserFolder/I18N/ZBabelDictionary_txt.zexp differ diff --git a/ZopeProducts/exUserFolder/LICENSE b/ZopeProducts/exUserFolder/LICENSE new file mode 100644 index 00000000..3a969486 --- /dev/null +++ b/ZopeProducts/exUserFolder/LICENSE @@ -0,0 +1,91 @@ +XUF as a whole is covered by the BSD License, however it uses software +covered by other compatible licenses (see below) + +------------------------------------------------------------------------ + +All of the documentation and software included in the exUserFolder +Releases is copyrighted by The Internet (Aust) Pty Ltd and contributors +ACN: 082 081 472 ABN: 83 082 081 472 + +Copyright 2001, 2002 The Internet (Aust) Pty Ltd + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +------------------------------------------------------------------------ + +This product includes software developed by Digital Creations for use in +the Z Object Publishing Environment (http://www.zope.org/) + +Portions of smbAuthSource Copyright (C) 2001 Michael Teo + +Portions of radiusAuthSource Copyright (C) 1999 Stuart Bishop + +fcrypt is Copyright (C) 2001, 2001 Carey Evans + +This product includes cryptographic software written by Eric Young +(eay@mincom.oz.au) + +------------------------------------------------------------------------ + +Brief discussion of what the license means to you, not meant to be +all encompassing, but, to give you the general idea. This editorial does +not need to be distributed d8) + +If you want to incorporate this product (or parts of it) into a commercial +product that's fine. + +If you want to modify this product that's fine. + +If you want to modify and distribute this product that's fine (even in +commercial products). + +If you want to incorporate this into a larger work that's fine (even +if that work has a different license). + +None of the previous items place any obligation of notification, compensation, +or return of code to us. In fact we don't care if you do these things. Go +forth and prosper. Basically as long as you recognise that this doesn't +belong to you, you can do what you want with it even charge money for it. + +Note: If you do distribute this as source, then the XUF components are +removable and distributable independently of your license as a whole +(although that's a lot of trouble to go to when they could just download it +from the same place you did). + +What you can't do, is claim it's yours, and this one thing encompasses a lot +of things, here's a few. + +If it's not yours you can't; + +Change the license even if you change the code since the copyright +of the modified files remains with the original copyright holders. + +Use bits of it inside products that require the license to change, because +only the copyright holders have the right to modify the license (not a +concern for commercial projects, only some other Free/Open Source licenses). + +Assign the copyright or other IP to any other party of the whole or any +part (even if you change the code), because it's not yours to give away or +sell to a 3rd party. + +If the fact you can almost do whatever you want with this code isn't +liberal enough for you, contact us and we'll see what we can arrange. diff --git a/ZopeProducts/exUserFolder/LoginRequiredMessages.py b/ZopeProducts/exUserFolder/LoginRequiredMessages.py new file mode 100644 index 00000000..1bee9173 --- /dev/null +++ b/ZopeProducts/exUserFolder/LoginRequiredMessages.py @@ -0,0 +1,27 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: LoginRequiredMessages.py,v 1.2 2001/12/01 08:40:03 akm Exp $ + +LoginRequiredMessages={ + 'session_expired':'Your Session has Expired', + 'unauthorized':'Please Login', + 'login_failed':'Login Failed', + } diff --git a/ZopeProducts/exUserFolder/MembershipSources/__init__.py b/ZopeProducts/exUserFolder/MembershipSources/__init__.py new file mode 100644 index 00000000..9dc1d5ff --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/__init__.py @@ -0,0 +1,25 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000-2005 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: + +import basicMemberSource +import nullMemberSource + diff --git a/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/.cvsignore b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/.cvsignore new file mode 100644 index 00000000..57c4bccb --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/.cvsignore @@ -0,0 +1,4 @@ +*.pyc +*.pyo +*~ +.*.swp diff --git a/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/PasswordForm.dtml b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/PasswordForm.dtml new file mode 100644 index 00000000..98bf3777 --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/PasswordForm.dtml @@ -0,0 +1,22 @@ + +
      + + + + + + + + + + + + + + + + +
      Old Password
      Password
      Confirm Password
      Password Hint
      +Change Password "> +
      + diff --git a/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/SignupForm.dtml b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/SignupForm.dtml new file mode 100644 index 00000000..98ae04ef --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/SignupForm.dtml @@ -0,0 +1,31 @@ + +
      + + + + + + + + + + + + + + + + + + + + + + + + + +
      Username
      Password
      Confirm Password
      Password Hint
      Real Name
      Email
      +Signup "> +
      + diff --git a/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/__init__.py b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/__init__.py new file mode 100644 index 00000000..a774af80 --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/__init__.py @@ -0,0 +1,2 @@ +# $Id: __init__.py,v 1.1 2004/11/10 14:15:55 akm Exp $ +import basicMemberSource diff --git a/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/basicMemberSource.py b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/basicMemberSource.py new file mode 100644 index 00000000..7be098a5 --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/basicMemberSource.py @@ -0,0 +1,629 @@ +# +# Extensible User Folder +# +# Basic Membership Source for exUserFolder +# +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: basicMemberSource.py,v 1.1 2004/11/10 14:15:55 akm Exp $ + +# +# Basically membership is a layer between the signup/login form, and +# the authentication layer, it uses the prop source of the users to +# store additional information about a user i.e. doesn't impact on the +# authentication source. +# +# Some membership features imply some extra properties for the user will +# be available; specifically at this time an email property. +# +# You also need a MailHost setup and ready to go for emailing stuff to users +# + +import string,Acquisition +from random import choice + + +from Globals import HTMLFile, INSTANCE_HOME + +from OFS.Folder import Folder +from OFS.DTMLMethod import DTMLMethod + +from Products.exUserFolder.exUserFolder import exUserFolder +from Products.exUserFolder.Plugins import PluginRegister + +from base64 import encodestring +from urllib import quote + +import zLOG + +""" +Password Policy enforcement (min/max length, caps etc) +Create Password, or User Chooses. +Timing out of passwords... +Empty Password force change on login... +Create Home Directory +Copy files from Skelton Directory +EMail password hint to user (forgot my password) +Reset password and email user (needs plugin?) +Redirect on login to fixed or varying per username location. +Automatically add users, or manually approve of users. +""" + +# Stupid little things for making a password +# Don't hassle me, it's supposed to be basic. + +nouns=['ace', 'ant', 'arc', 'arm', 'axe', + 'bar', 'bat', 'bee', 'bib', 'bin', + 'can', 'cap', 'car', 'cat', 'cob', + 'day', 'den', 'dog', 'dot', 'dux', + 'ear', 'eel', 'egg', 'elf', 'elk', + 'fad', 'fan', 'fat', 'fig', 'fez', + 'gag', 'gas', 'gin', 'git', 'gum', + 'hag', 'hat', 'hay', 'hex', 'hub'] + +pastConjs = [ 'did', 'has', 'was' ] +suffixes = [ 'ing', 'es', 'ed', 'ious', 'ily'] + +def manage_addBasicMemberSource(self, REQUEST): + """ Add a Membership Source """ + + pvfeatures=[] + minLength=0 + passwordPolicy='' + createHomedir=0 + homeRoot='' + copyFilesFrom='' + postLogin='' + postSignup='' + forgottenPasswords='' + defaultRoles=[] + usersCanChangePasswords=0 + baseURL='' + loginPage='' + signupPage='' + passwordPage='' + mailHost='' + fixedDest='' + + if REQUEST.has_key('basicmember_pvfeatures'): + pvfeatures=REQUEST['basicmember_pvfeatures'] + + if REQUEST.has_key('basicmember_roles'): + defaultRoles=REQUEST['basicmember_roles'] + + if not defaultRoles: + defaultRoles=['Member'] + + if 'minlength' in pvfeatures: + minLength=REQUEST['basicmember_minpasslen'] + + if REQUEST.has_key('basicmember_passwordpolicy'): + passwordPolicy=REQUEST['basicmember_passwordpolicy'] + + if REQUEST.has_key('basicmember_createhomedir'): + homeRoot=REQUEST['basicmember_homeroot'] + createHomedir=1 + + if REQUEST.has_key('basicmember_copyfiles'): + copyFilesFrom=REQUEST['basicmember_copyfiles'] + + if REQUEST.has_key('basicmember_changepasswords'): + usersCanChangePasswords=1 + + if REQUEST.has_key('basicmember_fixeddest'): + fixedDest='' + + forgottenPasswords=REQUEST['basicmember_forgottenpasswords'] + postLogin=REQUEST['basicmember_postlogin'] + + baseURL=REQUEST['basicmember_baseurl'] + loginPage=REQUEST['basicmember_loginpage'] + signupPage=REQUEST['basicmember_signuppage'] + passwordPage=REQUEST['basicmember_passwordpage'] + siteEmail=REQUEST['basicmember_siteemail'] + siteName=REQUEST['basicmember_sitename'] + + mailHost=REQUEST['basicmember_mailhost'] + + # postSignup=REQUEST['basicmember_postsignup'] + + # + # Yep this is obscene + # + o = BasicMemberSource(pvfeatures, minLength, passwordPolicy, + createHomedir, copyFilesFrom, postLogin, + homeRoot, forgottenPasswords, defaultRoles, + usersCanChangePasswords, baseURL, loginPage, + signupPage, passwordPage, mailHost, + siteName, siteEmail, fixedDest) + + self._setObject('basicMemberSource', o, None, None, 0) + o = getattr(self, 'basicMemberSource') + + if hasattr(o, 'postInitialisation'): + o.postInitialisation(REQUEST) + + self.currentMembershipSource=o + return '' + + +manage_addBasicMemberSourceForm=HTMLFile('manage_addBasicMemberSourceForm', + globals()) +manage_editBasicMemberSourceForm=HTMLFile('manage_editBasicMemberSourceForm', + globals()) + +# +# Crap, I don't know why I called this basic, I'd hate to see a +# complicated one. +# +class BasicMemberSource(Folder): + """ Provide High Level User Management """ + meta_type="Membership Source" + title="Basic Membership Source" + icon ='misc_/exUserFolder/exUserFolderPlugin.gif' + manage_tabs=Acquisition.Acquired + manage_editForm=manage_editBasicMemberSourceForm + + # Ugh... + def __init__(self, pvFeatures=[], minLength=0, passwordPolicy='', + createHomeDir=0, copyFilesFrom='', postLogin='', homeRoot='', + forgottenPasswords='', defaultRoles=[], usersCanChangePasswords=0, + baseURL='', loginPage='', signupPage='', passwordPage='', + mailHost='', siteName='', siteEmail='', fixedDest=''): + + self.id='basicMemberSource' + self.pvFeatures=pvFeatures + self.minLength=int(minLength) + self.passwordPolicy=passwordPolicy + self.createHomeDir=createHomeDir + self.copyFilesFrom=copyFilesFrom + self.postLogin=postLogin + self.homeRoot=homeRoot + self.forgottenPasswords=forgottenPasswords + self.defaultRoles=defaultRoles + self.usersCanChangePasswords=usersCanChangePasswords + self.baseURL=baseURL + self.loginPage=loginPage + self.signupPage=signupPage + self.passwordPage=passwordPage + self.siteName=siteName + self.siteEmail=siteEmail + self.fixedDest=fixedDest + + _SignupForm=HTMLFile('SignupForm', globals()) + SignupForm=DTMLMethod() + SignupForm.manage_edit(data=_SignupForm, title='Signup Form') + self._setObject('SignupForm', SignupForm) + + _PasswordForm=HTMLFile('PasswordForm', globals()) + PasswordForm=DTMLMethod() + PasswordForm.manage_edit(data=_PasswordForm, + title='Change Password') + self._setObject('PasswordForm', PasswordForm) + + self.mailHost=mailHost + + _newPasswordEmail=HTMLFile('newPasswordEmail', globals()) + newPasswordEmail=DTMLMethod() + newPasswordEmail.manage_edit(data=_newPasswordEmail, + title='Send New Password') + self._setObject('newPasswordEmail', newPasswordEmail) + + _forgotPasswordEmail=HTMLFile('forgotPasswordEmail', globals()) + forgotPasswordEmail=DTMLMethod() + forgotPasswordEmail.manage_edit(data=_forgotPasswordEmail, + title='Send Forgotten Password') + self._setObject('forgotPasswordEmail', forgotPasswordEmail) + + _passwordHintEmail=HTMLFile('passwordHintEmail', globals()) + passwordHintEmail=DTMLMethod() + passwordHintEmail.manage_edit(data=_passwordHintEmail, + title='Send Forgotten Password Hint') + self._setObject('passwordHintEmail', passwordHintEmail) + + def postInitialisation(self, REQUEST): + if self.createHomeDir and self.homeRoot: + self.findHomeRootObject() + else: + self.homeRootObj=None + + if self.copyFilesFrom: + self.findSkelRootObject() + else: + self.homeSkelObj=None + + # The nice sendmail tag doesn't allow expressions for + # the mailhost + self.mailHostObject=getattr(self, self.mailHost) + + def manage_editMembershipSource(self, REQUEST): + """ Edit a basic Membership Source """ + if REQUEST.has_key('pvfeatures'): + self.pvFeatures=REQUEST['pvfeatures'] + else: + self.pvFeatures=[] + + if REQUEST.has_key('minpasslength'): + self.minLength=REQUEST['minpasslength'] + + if REQUEST.has_key('createhomedir'): + createHomeDir=1 + else: + createHomeDir=0 + + if createHomeDir: + self.copyFilesFrom=REQUEST['copyfiles'] + if self.copyFilesFrom: + self.findSkelRootObject() + else: + self.homeRoot=REQUEST['homeroot'] + self.findHomeRootObject() + + if REQUEST.has_key('memberroles'): + self.defaultRoles=REQUEST['memberroles'] + if REQUEST.has_key('changepasswords'): + self.usersCanChangePasswords=1 + else: + self.usersCanChangePasswords=0 + + self.postLogin=REQUEST['postlogin'] + if REQUEST.has_key('fixeddest'): + self.fixedDest=REQUEST['fixeddest'] + + self.baseURL=REQUEST['baseurl'] + self.loginPage=REQUEST['loginpage'] + self.signupPage=REQUEST['signuppage'] + self.passwordPage=REQUEST['passwordpage'] + self.siteName=REQUEST['sitename'] + self.siteEmail=REQUEST['siteemail'] + return self.MessageDialog(self, + title ='Updated!', + message="Membership was Updated", + action ='manage_editMembershipSourceForm', + REQUEST=REQUEST) + + + + def forgotPassword(self, REQUEST): + username=REQUEST['username'] + curUser=self.getUser(username) + if not curUser: + return self.MessageDialog(self, + title ='No such user', + message="No users matching that username were found.", + action ='%s/%s'%(self.baseURL, self.loginPage), + REQUEST=REQUEST) + + + userEmail=curUser.getProperty('email') + userName=curUser.getProperty('realname') + if self.forgottenPasswords == "hint": + passwordHint=curUser.getProperty('passwordhint') + self.passwordHintEmail(self, + REQUEST=REQUEST, + username=username, + hint=passwordHint, + realname=userName, + email=userEmail) + else: + # make a new password, and mail it to the user + password = self.generatePassword() + curCrypt=self.currentAuthSource.cryptPassword(username,password) + + # Update the user + bogusREQUEST={} + #bogusREQUEST['username']=username + bogusREQUEST['password']=password + bogusREQUEST['password_confirm']=password + bogusREQUEST['roles']=curUser.roles + self.manage_editUser(username, bogusREQUEST) + + self.forgotPasswordEmail(self, + REQUEST=REQUEST, + username=username, + password=password, + realname=userName, + email=userEmail) + return self.MessageDialog(self, + title ='Sent!', + message="Password details have been emailed to you", + action ='%s/%s'%(self.baseURL, self.loginPage), + REQUEST=REQUEST) + + + def changeProperties(self, REQUEST): + + curUser=self.listOneUser(REQUEST['AUTHENTICATED_USER'].getUserName()) + curUser=curUser[0] + if not curUser: + return self.MessageDialog(self, + title ='Erm!', + message="You don't seem to be logged in", + action ='%s/%s'%(self.baseURL, self.passwordPage), + REQUEST=REQUEST) + + + + self.currentPropSource.updateUser(curUser['username'],REQUEST) + + return self.MessageDialog(self, + title ='Properties updated', + message="Your properties have been updated", + action =self.baseURL, + REQUEST=REQUEST, + ) + + + def changePassword(self, REQUEST): + if not self.usersCanChangePasswords: + return '' + + curUser=self.listOneUser(REQUEST['AUTHENTICATED_USER'].getUserName()) + curUser=curUser[0] + if not curUser: + return self.MessageDialog( + title ='Erm!', + message="You don't seem to be logged in", + action ='%s/%s'%(self.baseURL, self.passwordPage), + REQUEST=REQUEST) + + curCrypt=self.currentAuthSource.cryptPassword(curUser['username'],REQUEST['current_password']) + if curCrypt != curUser['password']: + return self.MessageDialog(self, + title ='Password Mismatch', + message="Password is incorrect", + action ='%s/%s'%(self.baseURL, self.passwordPage), + REQUEST=REQUEST) + + if REQUEST['password'] != REQUEST['password_confirm']: + return self.MessageDialog(self, + title ='Password Mismatch', + message="Passwords do not match", + action ='%s/%s'%(self.baseURL, self.passwordPage), + REQUEST=REQUEST) + + # OK the old password matches the one the user provided + # Both new passwords match... + # Time to validate against our normal set of rules... + # + if not self.validatePassword(REQUEST['password'], curUser['username']): + return self.MessageDialog(self, + title ='Password problem', + message="Your password is invalid, please choose another", + action ='%s/%s'%(self.baseURL, self.passwordPage), + REQUEST=REQUEST) + + if self.passwordPolicy=='hint': + if not hasattr(REQUEST,'user_passwordhint'): + return self.MessageDialog(self, + title ='Password requires hint', + message='You must choose a password hint', + action ='%s/%s'%(self.baseURL, self.passwordPage), + REQUEST=REQUEST) + + bogusREQUEST={} + + bogusREQUEST['password']=REQUEST['password'] + bogusREQUEST['password_confirm']=REQUEST['password'] + bogusREQUEST['roles']=curUser['roles'] + self.manage_editUser(curUser['username'],bogusREQUEST) + # update the cookie so he doesnt have to re-login: + if self.cookie_mode: + token='%s:%s' %(curUser['username'], REQUEST['password']) + token=encodestring(token) + token=quote(token) + REQUEST.response.setCookie('__ac', token, path='/') + REQUEST['__ac']=token + + return self.MessageDialog(self, + title ='Password updated', + message="Your password has been updated", + action =self.baseURL, + REQUEST=REQUEST) + + + def goHome(self, REQUEST, RESPONSE): + redirectstring="%s/%s/%s/manage_main"%(self.baseURL, self.homeRoot, REQUEST.AUTHENTICATED_USER.getUserName()) + RESPONSE.redirect(redirectstring) + return '' + + # Tell exUserFolder where we want to go... + def getLoginDestination(self, REQUEST): + script='' + pathinfo='' + querystring='' + redirectstring='' + if self.postLogin=="destination": + script=REQUEST['SCRIPT_NAME'] + pathinfo=REQUEST['PATH_INFO'] + elif self.postLogin=="varied": + script=self.baseURL + pathinfo="/acl_users/goHome" + + elif self.postLogin=="fixed": + pathinfo="%s"%(self.fixedDest) + + if REQUEST.has_key('QUERY_STRING'): + querystring='?'+REQUEST['QUERY_STRING'] + + redirectstring=script+pathinfo + if querystring: + redirectstring=redirectstring+querystring + + return redirectstring + + def validatePassword(self, password, username): + if 'minlength' in self.pvFeatures: + if len(password) < self.minLength: + return 0 + + if 'mixedcase' in self.pvFeatures: + lower = 0 + upper = 0 + for c in password: + if c in string.lowercase: + lower = 1 + if c in string.uppercase: + upper = 1 + if not upper and lower: + return 0 + + if 'specialchar' in self.pvFeatures: + special = 0 + for c in password: + if c in string.punctuation: + special = 1 + break + elif c in string.digits: + special = 1 + break + if not special: + return 0 + + # + # XXX Move this somewhere else + # + + if 'notstupid' in self.pvFeatures: + email='' + # We try some permutations here... + curUser=self.getUser(username) + if curUser: + email = curUser.getProperty('email') + elif hasattr(self, 'REQUEST'): + if self.REQUEST.has_key('user_email'): # new signup + email=self.REQUEST['user_email'] + elif self.REQUEST.has_key('email'): + email=self.REQUEST['email'] + + if ((string.find(password, username)>=0) or + ( email and + (string.find(password, + string.split(email,'@')[0]) >=0))): + return 0 + return 1 + + # These next two look the same (and they are for now), but, the reason I + # Don't use one single method, is I think that SkelObj might migrate to + # using full paths, not relative paths. + + def findSkelRootObject(self): + # Parent should be acl_users + parent = getattr(self, 'aq_parent') + + # This should be the root... + root = getattr(parent, 'aq_parent') + searchPaths = string.split(self.copyFilesFrom, '/') + for o in searchPaths: + if not getattr(root, o): + break + root = getattr(root, o) + + self.homeSkelObj=root + + def findHomeRootObject(self): + # Parent should be acl_users + parent = getattr(self, 'aq_parent') + + # This should be the root... + root = getattr(parent, 'aq_parent') + + searchPaths = string.split(self.homeRoot, '/') + for o in searchPaths: + if o not in root.objectIds(): + root.manage_addFolder(id=o, title=o, createPublic=0, createUserF=0) + root = getattr(root, o) + + self.homeRootObj=root + + def makeHomeDir(self, username): + if not self.homeRootObj: + return + + self.homeRootObj.manage_addFolder(id=username, title=username, createPublic=0, createUserF=0) + home = getattr(self.homeRootObj, username) + + + # Allow user to be in charge of their own destiny + # XXXX WARNING THIS IS A NORMAL FOLDER *SO USERS CAN ADD ANYTHING* + # YOU NEED TO CHANGE THE TYPE OF OBJECT ADDED FOR A USER UNLESS + # THIS IS WHAT YOU WANT TO HAPPEN + home.manage_addLocalRoles(userid=username, roles=['Manager']) + + if self.copyFilesFrom and self.homeSkelObj and self.homeSkelObj.objectIds(): + cp=self.homeSkelObj.manage_copyObjects( + self.homeSkelObj.objectIds()) + home.manage_pasteObjects(cp) + + # Fix it so the user owns their stuff + curUser=self.getUser(username).__of__(self.aq_parent) + home.changeOwnership(curUser, recursive=1) + + def generatePassword(self): + password = (choice(nouns) + choice(pastConjs) + + choice(nouns) + choice(suffixes)) + return password + + def createUser(self, REQUEST): + if self.passwordPolicy == 'user': + if not self.validatePassword(REQUEST['password'], REQUEST['username']): + return self.MessageDialog(self, + title ='Password problem', + message='Your password is invalid, please choose another', + action ='%s/%s'%(self.baseURL, self.signupPage), + REQUEST=REQUEST) + + if self.passwordPolicy=='hint': + if not hasattr(REQUEST,'user_passwordhint'): + return self.MessageDialog(self, + title ='Password requires hint', + message='You must choose a password hint', + action ='%s/%s'%(self.baseURL, self.signupPage), + REQUEST=REQUEST) + + elif self.passwordPolicy == 'system': + REQUEST['password']=self.generatePassword() + REQUEST['password_confirm']=REQUEST['password'] + + # Email the password. + self.newPasswordEmail(self, REQUEST) + + zLOG.LOG("exUserFolder.basicMemberSource", zLOG.BLATHER, + "Creating user", + "Passed all tests -- creating [%s]" % REQUEST['username']) + REQUEST['roles']=self.defaultRoles + self.manage_addUser(REQUEST) # Create the User... + if self.createHomeDir: + self.makeHomeDir(REQUEST['username']) + + return self.MessageDialog(self, + title ='You have signed up', + message='You have been signed up succesfully', + action ='%s'%(self.baseURL), + REQUEST=REQUEST) + + + +basicMemberReg=PluginRegister('basicMemberSource', + 'Basic Membership Source', + BasicMemberSource, + manage_addBasicMemberSourceForm, + manage_addBasicMemberSource, + manage_editBasicMemberSourceForm) +exUserFolder.membershipSources['basicMemberSource']=basicMemberReg + diff --git a/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/forgotPasswordEmail.dtml b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/forgotPasswordEmail.dtml new file mode 100644 index 00000000..1dc67b2b --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/forgotPasswordEmail.dtml @@ -0,0 +1,15 @@ + +To: <> +From: <> +Subject: You forgot your password for + +Dear , + +Your username is and your password is now +. + +You should have tested this first, and now that you've tested it, you'll +see you need to customise this method. + + + diff --git a/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/manage_addBasicMemberSourceForm.dtml b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/manage_addBasicMemberSourceForm.dtml new file mode 100644 index 00000000..434e1f09 --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/manage_addBasicMemberSourceForm.dtml @@ -0,0 +1,143 @@ + +Membership requires a valid property source, you cannot use this with NULL Property Source +
      + + + + + + + + + "> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Site Name (used in emails) +
      Site Email (used for emails) +
      Mail Host + +
      Site Base"> +
      Relative Path (from base) of Login Page +
      Relative Path (from base) of Signup Page +
      Relative Path (from base) of Change Password Page +
      Password Validation Features + +
      Minimum Length (0 if not required) + +
      Password Policy + +
      Forgotten Passwords + +
      Allow users to change passwords + Yes +
      Create 'Home Directory' + Yes +
      Relative Path to 'Home Directory' Root + +
      Copy initial 'Home Directory' files from...(empty=No Copy) + "> +
      After login.... + +
      Fixed Destination + +
      Default Roles + +
      +Add"> +
      + diff --git a/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/manage_editBasicMemberSourceForm.dtml b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/manage_editBasicMemberSourceForm.dtml new file mode 100644 index 00000000..c7092b32 --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/manage_editBasicMemberSourceForm.dtml @@ -0,0 +1,116 @@ + + +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Site Name (used in emails) +
      Site Email (used for emails) +
      Mail Host + +
      Site Base +
      Relative Path (from base) of Login Page +
      Relative Path (from base) of Signup Page +
      Relative Path (from base) of Change Password Page +
      Password Validation Features + +
      Minimum Length (if required) + +
      Allow users to change passwords + checked>Yes +
      Create 'Home Directory' + checked>Yes +
      Path to 'Home Directory' Root + +
      Copy initial 'Home Directory' files from...(empty=No Copy) + +
      After login.... + +
      Fixed Destination + +
      Default Roles + +
      +Update "> +
      +
      + + diff --git a/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/newPasswordEmail.dtml b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/newPasswordEmail.dtml new file mode 100644 index 00000000..4de7dc6b --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/newPasswordEmail.dtml @@ -0,0 +1,16 @@ + +To: <> +From: <> +Subject: Welcome to + +Dear , + +Welcome to . + +Your username is and your password is . + +You should have tested this first, and now that you've tested it, you'll +see you need to customise this method. + + + diff --git a/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/passwordHintEmail.dtml b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/passwordHintEmail.dtml new file mode 100644 index 00000000..75c7cca9 --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/basicMemberSource/passwordHintEmail.dtml @@ -0,0 +1,15 @@ + +To: <> +From: <> +Subject: Hint for + +Dear , + +Your username is and your password hint was; +. + +You should have tested this first, and now that you've tested it, you'll +see you need to customise this method. + + + diff --git a/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/.cvsignore b/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/.cvsignore new file mode 100644 index 00000000..57c4bccb --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/.cvsignore @@ -0,0 +1,4 @@ +*.pyc +*.pyo +*~ +.*.swp diff --git a/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/__init__.py b/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/__init__.py new file mode 100644 index 00000000..9eb51622 --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/__init__.py @@ -0,0 +1,2 @@ +# $Id: __init__.py,v 1.1 2004/11/10 14:15:55 akm Exp $ +import nullMemberSource diff --git a/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/manage_addNullPluginSourceForm.dtml b/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/manage_addNullPluginSourceForm.dtml new file mode 100644 index 00000000..4ae809bf --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/manage_addNullPluginSourceForm.dtml @@ -0,0 +1,21 @@ + +
      + + + + + + + + + "> + + + + + +This Membership Source has no configuration Items
      +
      +Add"> +
      + diff --git a/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/nullMemberSource.py b/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/nullMemberSource.py new file mode 100644 index 00000000..49f26a50 --- /dev/null +++ b/ZopeProducts/exUserFolder/MembershipSources/nullMemberSource/nullMemberSource.py @@ -0,0 +1,49 @@ +# +# Extensible User Folder +# +# Null Membership Source for exUserFolder +# +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: nullMemberSource.py,v 1.1 2004/11/10 14:15:55 akm Exp $ +from Globals import HTMLFile, INSTANCE_HOME + +from OFS.Folder import Folder + +from Products.exUserFolder.exUserFolder import exUserFolder +from Products.exUserFolder.Plugins import PluginRegister +from Products.exUserFolder.nullPlugin import nullPlugin + +def manage_addNullMemberSource(self, REQUEST): + """ Add a Membership Source """ + self.currentMembershipSource=None + return '' + + +manage_addNullMemberSourceForm=HTMLFile('manage_addNullPluginSourceForm',globals()) +manage_editNullMemberSourceForm=None + + +nullMemberReg=PluginRegister('nullMemberSource', + 'Null Membership Source', + nullPlugin, + manage_addNullMemberSourceForm, + manage_addNullMemberSource, + manage_editNullMemberSourceForm) +exUserFolder.membershipSources['nullMemberSource']=nullMemberReg + diff --git a/ZopeProducts/exUserFolder/Plugins.py b/ZopeProducts/exUserFolder/Plugins.py new file mode 100644 index 00000000..58cdc2ef --- /dev/null +++ b/ZopeProducts/exUserFolder/Plugins.py @@ -0,0 +1,46 @@ +# +# +# (C) Copyright 2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: Plugins.py,v 1.5 2004/11/10 14:15:33 akm Exp $ + +import App, Globals, OFS +import string +import time + +from Globals import ImageFile, HTMLFile, HTML, MessageDialog, package_home +from OFS.Folder import Folder + +class PluginRegister: + def __init__(self, name, description, pluginClass, + pluginStartForm, pluginStartMethod, + pluginEditForm=None, pluginEditMethod=None): + self.name=name #No Spaces please... + self.description=description + self.plugin=pluginClass + self.manage_addForm=pluginStartForm + self.manage_addMethod=pluginStartMethod + self.manage_editForm=pluginEditForm + self.manage_editMethod=pluginEditMethod + +class CryptoPluginRegister: + def __init__(self, name, crypto, description, pluginMethod): + self.name = name #No Spaces please... + self.cryptoMethod = crypto + self.description = description + self.plugin = pluginMethod diff --git a/ZopeProducts/exUserFolder/PropSources/__init__.py b/ZopeProducts/exUserFolder/PropSources/__init__.py new file mode 100644 index 00000000..e11388e4 --- /dev/null +++ b/ZopeProducts/exUserFolder/PropSources/__init__.py @@ -0,0 +1,28 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000-2004 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: __init__.py,v 1.1 2004/11/10 14:15:55 akm Exp $ + + +import nullPropSource +# aucune autre prop source pour ScoDoc + + + diff --git a/ZopeProducts/exUserFolder/PropSources/nullPropSource/.cvsignore b/ZopeProducts/exUserFolder/PropSources/nullPropSource/.cvsignore new file mode 100644 index 00000000..57c4bccb --- /dev/null +++ b/ZopeProducts/exUserFolder/PropSources/nullPropSource/.cvsignore @@ -0,0 +1,4 @@ +*.pyc +*.pyo +*~ +.*.swp diff --git a/ZopeProducts/exUserFolder/PropSources/nullPropSource/__init__.py b/ZopeProducts/exUserFolder/PropSources/nullPropSource/__init__.py new file mode 100644 index 00000000..14ecc014 --- /dev/null +++ b/ZopeProducts/exUserFolder/PropSources/nullPropSource/__init__.py @@ -0,0 +1,2 @@ +# $Id: __init__.py,v 1.1 2004/11/10 14:15:56 akm Exp $ +import nullPropSource diff --git a/ZopeProducts/exUserFolder/PropSources/nullPropSource/manage_addNullPluginSourceForm.dtml b/ZopeProducts/exUserFolder/PropSources/nullPropSource/manage_addNullPluginSourceForm.dtml new file mode 100644 index 00000000..a1979b0e --- /dev/null +++ b/ZopeProducts/exUserFolder/PropSources/nullPropSource/manage_addNullPluginSourceForm.dtml @@ -0,0 +1,21 @@ + +
      + + + + + + + + + "> + + + + + +This Property Source has no configuration Items
      +
      +Add"> +
      + diff --git a/ZopeProducts/exUserFolder/PropSources/nullPropSource/nullPropSource.py b/ZopeProducts/exUserFolder/PropSources/nullPropSource/nullPropSource.py new file mode 100644 index 00000000..ab269b03 --- /dev/null +++ b/ZopeProducts/exUserFolder/PropSources/nullPropSource/nullPropSource.py @@ -0,0 +1,50 @@ +# +# Extensible User Folder +# +# Null Membership Source for exUserFolder +# +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: nullPropSource.py,v 1.1 2004/11/10 14:15:56 akm Exp $ +from Globals import HTMLFile, INSTANCE_HOME + +from OFS.Folder import Folder + +from Products.exUserFolder.exUserFolder import exUserFolder +from Products.exUserFolder.Plugins import PluginRegister +from Products.exUserFolder.nullPlugin import nullPlugin + +def manage_addNullPropSource(self, REQUEST): + """ Add a Property Source """ + self.currentPropSource=None + return '' + + +manage_addNullPropSourceForm=HTMLFile('manage_addNullPluginSourceForm',globals()) +manage_editNullPropSourceForm=None + + +nullPropReg=PluginRegister('nullPropSource', + 'Null Property Source', + nullPlugin, + manage_addNullPropSourceForm, + manage_addNullPropSource, + manage_editNullPropSourceForm) + +exUserFolder.propSources['nullPropSource']=nullPropReg + diff --git a/ZopeProducts/exUserFolder/PropertyEditor/.cvsignore b/ZopeProducts/exUserFolder/PropertyEditor/.cvsignore new file mode 100644 index 00000000..57c4bccb --- /dev/null +++ b/ZopeProducts/exUserFolder/PropertyEditor/.cvsignore @@ -0,0 +1,4 @@ +*.pyc +*.pyo +*~ +.*.swp diff --git a/ZopeProducts/exUserFolder/PropertyEditor/PropertyEditor.py b/ZopeProducts/exUserFolder/PropertyEditor/PropertyEditor.py new file mode 100644 index 00000000..711d4a0f --- /dev/null +++ b/ZopeProducts/exUserFolder/PropertyEditor/PropertyEditor.py @@ -0,0 +1,122 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: PropertyEditor.py,v 1.3 2002/01/29 17:42:02 alex_zxc Exp $ + +from Globals import DTMLFile, MessageDialog, INSTANCE_HOME +from string import join,strip,split,lower,upper,find +from urllib import quote, unquote + + +def editStringProperty( name, value): + """ """ + return('\n'%(name, value)) + +def viewStringProperty( name, value): + """ """ + return('
      %s
      \n'%(value, value)) + + +def editIntegerProperty( name, value): + """ """ + return('\n'%(name, value or 0)) + +def viewIntegerProperty( name, value): + """ """ + return('
      %d
      \n'%(value or 0 , value or 0)) + + +def editLongProperty( name, value): + """ """ + return('\n'%(name, value or 0)) + +def viewLongProperty( name, value): + """ """ + return('
      %d
      \n'%(value or 0, value or 0)) + + +def editFloatProperty( name, value): + """ """ + return('\n'%(name, value)) + +def viewFloatProperty( name, value): + """ """ + return('
      %f
      \n'%(value, value)) + + +def editListProperty( name, value): + a='' + if value: + a = a + 'Select Items to keep
      \n' + a = a + '\n
      ' + a = a + 'Add an item\n
      ' + a = a + ''%(name) + return(a) + +def viewListProperty( name, value): + a='' + if value: + for i in value: + a = a + ( + '\n'%(i)) + a = a + '%s\n
      '%(i) + return(a) + + +def editDictProperty( name, value): + """ """ + a='' + if value and value.keys(): + for i in value.keys(): + a = a + '%s : \n
      '%(i, name, i, value[i]) + return a + + +def viewDictProperty( name, value): + """ """ + a='' + if value and value.keys(): + for i in value.keys(): + a = a + '%s : \n
      '%(i, name, i, value[i]) + a = a + '%s\n
      '%(value[i]) + return a + +EditMethods={'String':editStringProperty, + 'Integer':editIntegerProperty, + 'Long':editLongProperty, + 'Float':editFloatProperty, + 'List':editListProperty, + 'Dict':editDictProperty} + + +ViewMethods={'String':viewStringProperty, + 'Integer':viewIntegerProperty, + 'Long':viewLongProperty, + 'Float':viewFloatProperty, + 'List':viewListProperty, + 'Dict':viewDictProperty} + + + + diff --git a/ZopeProducts/exUserFolder/PropertyEditor/__init__.py b/ZopeProducts/exUserFolder/PropertyEditor/__init__.py new file mode 100644 index 00000000..4189a84d --- /dev/null +++ b/ZopeProducts/exUserFolder/PropertyEditor/__init__.py @@ -0,0 +1,22 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: __init__.py,v 1.2 2001/12/01 08:40:04 akm Exp $ +import PropertyEditor diff --git a/ZopeProducts/exUserFolder/README.Upgrading b/ZopeProducts/exUserFolder/README.Upgrading new file mode 100644 index 00000000..384001a4 --- /dev/null +++ b/ZopeProducts/exUserFolder/README.Upgrading @@ -0,0 +1,14 @@ +If you are upgrading an existing site from < 0.50 + +I have restructured the source tree. This will make this version +incompatible with previous versions, as the classes have moved. This +breaks upgrading existing installs unless you keep the old classes +around. If you only use external Auth/Prop/Group sources, you will +probably be unaffected. + +This means for those of you using SQL or LDAP or any non-ZODB sources, +you can remove and then re-add your XUF acl_users to get going again. + +If you are using a ZODB source, then you need to keep the old classes +and the old paths around (e.g. symlink zodbAuthSource to +AuthSources/zodbAuthSource). diff --git a/ZopeProducts/exUserFolder/User.py b/ZopeProducts/exUserFolder/User.py new file mode 100644 index 00000000..7b55e0f9 --- /dev/null +++ b/ZopeProducts/exUserFolder/User.py @@ -0,0 +1,243 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: User.py,v 1.10 2004/12/14 05:30:29 akm Exp $ + +############################################################################## +# +# Zope Public License (ZPL) Version 0.9.4 +# --------------------------------------- +# +# Copyright (c) Digital Creations. All rights reserved. +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the following +# conditions are met: +# +# 1. Redistributions in source code must retain the above +# copyright notice, this list of conditions, and the following +# disclaimer. +# +# 6. Redistributions of any form whatsoever must retain the +# following acknowledgment: +# +# "This product includes software developed by Digital +# Creations for use in the Z Object Publishing Environment +# (http://www.zope.org/)." +# +# Disclaimer +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND +# ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +# SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. +# +############################################################################## +from AccessControl.User import BasicUser +from string import join,strip,split,lower,upper,find + +class XUFUser(BasicUser): + + icon='misc_/exUserFolder/exUser.gif' + + # cacheable is a dict that must contain at least name, password, + # roles, and domains -- unless you're working with your own User class, + # in which case you need to override __init__ and define it yourself. + def __init__(self, cacheable, propSource, cryptPassword, authSource, + groupSource=None): + self.name =cacheable['name'] + self.__ =cacheable['password'] + if cacheable['roles']: + self.roles = filter(None, cacheable['roles']) + else: + self.roles = [] + # domains may be passed as a string or a list + if type(cacheable['domains']) == type(''): + self.domains=filter(None, map(strip, + split(cacheable['domains'], ','))) + else: + self.domains=cacheable['domains'] + self._authSource=authSource + self._propSource=propSource + self._groupSource=groupSource + self.cryptPassword=cryptPassword + + def getUserName(self): + return self.name + + def _getPassword(self): + return self.__ + + def getRoles(self): + return tuple(self.roles) + ('Authenticated',) + + def getDomains(self): + return self.domains + + # Ultra generic way of getting, checking and setting properties + def getProperty(self, property, default=None): + if self._propSource: + return self._propSource.getUserProperty(property, self.name, default) + + def hasProperty(self, property): + if self._propSource: + return self._propSource.hasProperty(property) + + def setProperty(self, property, value): + if property[0]=='_': + return + if self._propSource: + return self._propSource.setUserProperty(property, self.name, value) + + def setTempProperty(self, property, value): + if property[0]=='_': + return + if self._propSource: + return self._propSource.setTempProperty(property, value) + + def flushTempProperties(self): + if self._propSource: + return self._propSource.flushTempProperties() + + def delProperty(self, property): + if property[0]=='_': + return + if self._propSource: + return self._propSource.delUserProperty(property, self.name) + + def listProperties(self): + if self._propSource: + return self._propSource.listUserProperties(self.name) + + # Try to allow User['property'] -- won't work for password d;) + def __getitem__(self, key): + # Don't return 'private' keys + if key[0] != '_': + if hasattr(self, key): + return getattr(self, key) + if self._propSource and self._propSource.hasProperty(key): + + return self._propSource.getUserProperty(key, self.name) + raise KeyError, key + + def __setitem__(self, key, value): + if key[0]=='_': + return + if self._propSource: + self._propSource.setUserProperty(key, self.name, value) + + # List one user is supplied by the Auth Source... + + def authenticate(self, listOneUser, password, request, remoteAuth=None): + result=listOneUser(username=self.name) + for people in result: + if remoteAuth: + return remoteAuth(self.name, password) + else: + secret=self.cryptPassword(self.name, password) + return secret==people['password'] + return None + + # You can set logout times or whatever here if you want to, the + # property source is still active. + def notifyCacheRemoval(self): + if self._propSource: + self._propSource.flushTempProperties() + + # You must override this and __init__ if you are subclassing + # the user object, or your user object may not be reconstructed + # properly! All values in this dict must be non-Persistent objects + # or types, and may not hold any references to Persistent objects, + # or the cache will break. + def _getCacheableDict(self): + return {'name': self.name, + 'password': self.__, + 'roles': self.roles, + 'domains': self.domains} + + def getGroups(self): + if self._groupSource: + return self._groupSource.getGroupsOfUser(self.name) + else: + return () + + + def _setGroups(self, groupnames): + if self._groupSource: + return self._groupSource.setGroupsOfUser(groupnames, self.name) + + + def _addGroups(self, groupnames): + if self._groupSource: + return self._groupSource.addGroupsToUser(groupnames, self.name) + + def _delGroups(self, groupnames): + if self._groupSource: + return self._groupSource.delGroupsFromUser(groupnames, self.name) + + def getId(self): + if self._propSource and self._propSource.getUserProperty('userid', self.name): + return self._propSource.getUserProperty('userid', self.name) + return self.name + + +# +# An Anonymous User for session tracking... +# Can set and get properties just like a normal user. +# +# These objects live in the cache, so, we have a __del__ method to +# clean ourselves up. +# + +class XUFAnonUser(XUFUser): + def __init__(self, name, roles, propSource): + self.name =name + self.__ ='' + self.roles =filter(None, roles) + self._propSource=propSource + + def getRoles(self): + return tuple(self.roles) + ('Anonymous',) + + def authenticate(self, listOneUser, password, request, remoteAuth=None): + return 1 + + def notifyCacheRemoval(self): + if self._propSource: + self._propSource.deleteUsers([self.name,]) + +# We now set up a dummy classes so that people can extend the User objects +# or override stuff with much less pain --akm + +class User(XUFUser): + pass + +class AnonUser(XUFAnonUser): + pass + diff --git a/ZopeProducts/exUserFolder/UserCache/.cvsignore b/ZopeProducts/exUserFolder/UserCache/.cvsignore new file mode 100644 index 00000000..57c4bccb --- /dev/null +++ b/ZopeProducts/exUserFolder/UserCache/.cvsignore @@ -0,0 +1,4 @@ +*.pyc +*.pyo +*~ +.*.swp diff --git a/ZopeProducts/exUserFolder/UserCache/UserCache.py b/ZopeProducts/exUserFolder/UserCache/UserCache.py new file mode 100644 index 00000000..b3b6a942 --- /dev/null +++ b/ZopeProducts/exUserFolder/UserCache/UserCache.py @@ -0,0 +1,409 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: UserCache.py,v 1.16 2003/07/10 21:11:26 akm Exp $ + +# Module Level Caches for Various User Operations + +from time import time +from BTrees.OOBTree import OOBTree +import threading +from Acquisition import aq_inner +from Products.exUserFolder.User import User + +class UserCacheItem: + lastAccessed=0 + def __init__(self, username, password, cacheable): + self.username=username + self.password=password + self.cacheable=cacheable + self.lastAccessed=time() + + def touch(self): + self.lastAccessed=time() + + def __repr__(self): + return self.username + +class NegativeUserCacheItem(UserCacheItem): + def __init__(self, username): + self.username=username + self.lastAccessed=time() + +class AdvancedCookieCacheItem(UserCacheItem): + lastAccessed=0 + def __init__(self, username, password): + self.username=username + self.password=password + self.lastAccessed=time() + +class SessionExpiredException(Exception): + 'User Session Expired' + + +class UserCache: + def __init__(self, sessionLength): + self.sessionLength=sessionLength + self.cache=OOBTree() + self.hits=0 + self.fail=0 + self.nouser=0 + self.attempts=0 + self.timeouts=0 + self.cacheStarted=time() + self.lock=threading.Lock() + + def addToCache(self, username, password, User): + self.lock.acquire() + try: + if not self.sessionLength: + return + + try: + u = self.cache.items(username) + if u: + for x in self.cache[username]: + self.cache.remove(x) + except: + pass + + u = UserCacheItem(username, password, User._getCacheableDict()) + self.cache[username]=u + finally: + self.lock.release() + + def getUser(self, caller, username, password, checkpassword=1): + self.lock.acquire() + try: + if not self.sessionLength: + return None + + self.attempts=self.attempts+1 + + u = None + try: + u = self.cache[username] + except KeyError: + self.nouser=self.nouser+1 + return None + + now = time() + if u: + if checkpassword and (u.password != password): + self.fail=self.fail+1 + del self.cache[u.username] + elif self.sessionLength and ( + (now - u.lastAccessed) > self.sessionLength): + del self.cache[u.username] + self.timeouts=self.timeouts+1 + user_object=User(u.cacheable, + caller.currentPropSource, + caller.cryptPassword, + caller.currentAuthSource, + caller.currentGroupSource) + user_object.notifyCacheRemoval() + del u + raise SessionExpiredException + else: + u.touch() + self.hits=self.hits+1 + return User(u.cacheable, + caller.currentPropSource, + caller.cryptPassword, + caller.currentAuthSource, + caller.currentGroupSource) + + self.nouser=self.nouser+1 + return None + finally: + self.lock.release() + + def removeUser(self, username): + self.lock.acquire() + try: + if not self.sessionLength: + return + try: + if self.cache[username]: + del self.cache[username] + except: + pass + finally: + self.lock.release() + + def getCacheStats(self): + self.lock.acquire() + try: + return ( + {'attempts':self.attempts, + 'hits':self.hits, + 'fail':self.fail, + 'misses':self.nouser, + 'cachesize':len(self.cache), + 'time':self.cacheStarted, + 'timeouts':self.timeouts, + 'length':self.sessionLength}) + finally: + self.lock.release() + + def getCurrentUsers(self, caller): + self.lock.acquire() + try: + x=[] + now = time() + for z in self.cache.keys(): + u = self.cache[z] + if self.sessionLength and ( + (now - u.lastAccessed) > self.sessionLength): + del self.cache[u.username] + self.timeouts=self.timeouts+1 + user_object=User(u.cacheable, + caller.currentPropSource, + caller.cryptPassword, + caller.currentAuthSource, + caller.currentGroupSource) + user_object.notifyCacheRemoval() + del u + else: + x.append({'username':u.username, + 'lastAccessed':u.lastAccessed}) + return x + finally: + self.lock.release() + +class NegativeUserCache: + def __init__(self, sessionLength): + self.sessionLength=sessionLength + self.cache=OOBTree() + self.hits=0 + self.cacheStarted=time() + self.lock=threading.Lock() + + def addToCache(self, username): + self.lock.acquire() + try: + if not self.sessionLength: + return + + try: + u = self.cache.items(username) + if u: + for x in self.cache[username]: + self.cache.remove(x) + except: + pass + + u = NegativeUserCacheItem(username) + self.cache[username]=u + finally: + self.lock.release() + + def getUser(self, username): + self.lock.acquire() + try: + if not self.sessionLength: + return 0 + + u = None + try: + u = self.cache[username] + except KeyError: + return 0 + + now = time() + if u: + if self.sessionLength and ( + (now - u.lastAccessed) > self.sessionLength): + del self.cache[u.username] + else: + # We don't touch negative user caches + # u.touch() + self.hits=self.hits+1 + return 1 + return 0 + finally: + self.lock.release() + + def removeUser(self, username): + self.lock.acquire() + try: + if not self.sessionLength: + return + try: + del self.cache[username] + except: + pass + finally: + self.lock.release() + +class CookieCache: + def __init__(self, sessionLength): + self.sessionLength=sessionLength + self.cache=OOBTree() + self.hits=0 + self.cacheStarted=time() + self.lock=threading.Lock() + + def addToCache(self, username, password, key): + self.lock.acquire() + try: + if not self.sessionLength: + return + + try: + u = self.cache.items(key) + if u: + for x in self.cache[key]: + self.cache.remove(x) + except: + pass + u = AdvancedCookieCacheItem(username, password) + self.cache[key]=u + finally: + self.lock.release() + + def getUser(self, key): + self.lock.acquire() + try: + if not self.sessionLength: + return None + + u = None + try: + u = self.cache[key] + except KeyError: + return None + + now = time() + if u: + if self.sessionLength and ( + (now - u.lastAccessed) > self.sessionLength): + del self.cache[key] + else: + # We don't touch negative user caches + # u.touch() + self.hits=self.hits+1 + return u.username, u.password + return None + finally: + self.lock.release() + + def removeUser(self, key): + self.lock.acquire() + try: + if not self.sessionLength: + return + try: + del self.cache[key] + except: + pass + finally: + self.lock.release() + +class GlobalUserCache: + caches={} + def __init__(self): + self.lock = threading.Lock() + + def createCache(self, who, sessionLength): + self.lock.acquire() + try: + self.caches[who]=UserCache(sessionLength) + return self.caches[who] + finally: + self.lock.release() + + def getCache(self, who): + self.lock.acquire() + try: + if self.caches.has_key(who): + return self.caches[who] + else: + return None + finally: + self.lock.release() + + def deleteCache(self, who): + self.lock.acquire() + try: + del self.caches[who] + finally: + self.lock.release() + +class GlobalNegativeUserCache: + caches={} + def __init__(self): + self.lock = threading.Lock() + + def createCache(self, who, sessionLength): + self.lock.acquire() + try: + self.caches[who]=NegativeUserCache(sessionLength) + return self.caches[who] + finally: + self.lock.release() + + def getCache(self, who): + self.lock.acquire() + try: + if self.caches.has_key(who): + return self.caches[who] + else: + return None + finally: + self.lock.release() + + def deleteCache(self, who): + self.lock.acquire() + try: + del self.caches[who] + finally: + self.lock.release() + +class GlobalAdvancedCookieCache: + caches={} + def __init__(self): + self.lock = threading.Lock() + + def createCache(self, who, sessionLength): + self.lock.acquire() + try: + self.caches[who]=CookieCache(sessionLength) + return self.caches[who] + finally: + self.lock.release() + + def getCache(self, who): + self.lock.acquire() + try: + if self.caches.has_key(who): + return self.caches[who] + else: + return None + finally: + self.lock.release() + + def deleteCache(self, who): + self.lock.acquire() + try: + del self.caches[who] + finally: + self.lock.release() + diff --git a/ZopeProducts/exUserFolder/UserCache/__init__.py b/ZopeProducts/exUserFolder/UserCache/__init__.py new file mode 100644 index 00000000..5a484ebd --- /dev/null +++ b/ZopeProducts/exUserFolder/UserCache/__init__.py @@ -0,0 +1,22 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: __init__.py,v 1.2 2001/12/01 08:40:04 akm Exp $ +import UserCache diff --git a/ZopeProducts/exUserFolder/__init__.py b/ZopeProducts/exUserFolder/__init__.py new file mode 100644 index 00000000..49f0cbcc --- /dev/null +++ b/ZopeProducts/exUserFolder/__init__.py @@ -0,0 +1,85 @@ +# +# Extensible User Folder +# +# (C) Copyright 2000-2005 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: __init__.py,v 1.18 2004/11/10 14:15:33 akm Exp $ + +import exUserFolder + +import CryptoSources +import AuthSources +import PropSources +import MembershipSources +import GroupSources + +from GroupSource import GroupSource + +from App.ImageFile import ImageFile +import OFS + +# +# Install a dummy ZBabel setup if we don't have ZBabel installed. +# +import dummyZBabelTag + +# Methods we need access to from any ObjectManager context +legacy_methods = ( + ('manage_addexUserFolderForm', exUserFolder.manage_addexUserFolderForm), + ('manage_addexUserFolder', exUserFolder.manage_addexUserFolder), + ('getAuthSources', exUserFolder.getAuthSources), + #('getPropSources', exUserFolder.getPropSources), + ('getCryptoSources', exUserFolder.getCryptoSources), + ('getMembershipSources', exUserFolder.getMembershipSources), + ('getGroupSources', exUserFolder.getGroupSources), + ('doAuthSourceForm', exUserFolder.doAuthSourceForm), + #('doPropSourceForm', exUserFolder.doPropSourceForm), + ('doMembershipSourceForm', exUserFolder.doMembershipSourceForm), + # ('doGroupSourceForm', exUserFolder.doGroupSourceForm), + ('getVariableType', exUserFolder.getVariableType), + ('DialogHeader', exUserFolder.exUserFolder.DialogHeader), + ('DialogFooter', exUserFolder.exUserFolder.DialogFooter), + #('MailHostIDs', exUserFolder.MailHostIDs), + ) + +# Image files to place in the misc_ object so they are accesible from misc_/exUserFolder +misc_={'exUserFolder.gif': ImageFile('exUserFolder.gif', globals()), + 'exUserFolderPlugin.gif': ImageFile('exUserFolderPlugin.gif', globals()), + 'exUser.gif': ImageFile('exUser.gif', globals()), + } + + +def initialize(context): + """ + Register base classes + """ + context.registerClass(exUserFolder.exUserFolder, + meta_type="ex User Folder", + permission="Add exUser Folder", + constructors=(exUserFolder.manage_addexUserFolderForm, + exUserFolder.manage_addexUserFolder,), + legacy=legacy_methods, + icon="exUserFolder.gif") + + context.registerClass(GroupSource.GroupSource, + meta_type="ex User Folder Group Source", + permission="Add exUser Folder", + constructors=(GroupSource.manage_addGroupSourceForm, + GroupSource.manage_addGroupSource,), + icon="exUserFolderPlugin.gif") + diff --git a/ZopeProducts/exUserFolder/common/DialogFooter.dtml b/ZopeProducts/exUserFolder/common/DialogFooter.dtml new file mode 100644 index 00000000..00add5fe --- /dev/null +++ b/ZopeProducts/exUserFolder/common/DialogFooter.dtml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ZopeProducts/exUserFolder/common/DialogHeader.dtml b/ZopeProducts/exUserFolder/common/DialogHeader.dtml new file mode 100644 index 00000000..fad53794 --- /dev/null +++ b/ZopeProducts/exUserFolder/common/DialogHeader.dtml @@ -0,0 +1,7 @@ +","
      + + + + + + ","
      diff --git a/ZopeProducts/exUserFolder/common/MessageDialog.dtml b/ZopeProducts/exUserFolder/common/MessageDialog.dtml new file mode 100644 index 00000000..0d0bc555 --- /dev/null +++ b/ZopeProducts/exUserFolder/common/MessageDialog.dtml @@ -0,0 +1,41 @@ + +
      TARGET=""> + + + + + + + + + "> + + + + + + + + + + + + +
      +
      +
      !
      +
      +

      +
      + +
      +
      + +
      + Ok "> +
      +
      + + + + diff --git a/ZopeProducts/exUserFolder/common/manage_tabs.dtml b/ZopeProducts/exUserFolder/common/manage_tabs.dtml new file mode 100644 index 00000000..c6c66c2e --- /dev/null +++ b/ZopeProducts/exUserFolder/common/manage_tabs.dtml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
         + ">
       href="&dtml-action;"href="&dtml-URL1;" target="&dtml-target;"> +   href="&dtml-action;"href="&dtml-URL1;" target="&dtml-target;"> +  
      +
      + + +
      + + + + + + + + + + + + + + + +
      +
      + + &dtml-meta_type; + + + + + &dtml-meta_type; + + &dtml-meta_type; + + + Object + + at + + + + This item has been modified in this version + + This item has been modified in another version + () + + + + This item has been locked by WebDAV + +
      +
      +
      + +
      +
      +
      + +
      +
      + + +
      +You are currently working in version +
      +
      +
      + + +
      + +() +
      +
      + +
      diff --git a/ZopeProducts/exUserFolder/common/text_manage_tabs.dtml b/ZopeProducts/exUserFolder/common/text_manage_tabs.dtml new file mode 100644 index 00000000..62922a0e --- /dev/null +++ b/ZopeProducts/exUserFolder/common/text_manage_tabs.dtml @@ -0,0 +1,19 @@ + + + +
      + + + + +
      + + href="" + href="" + target=""> + []  + +
      +
      +
      +
      diff --git a/ZopeProducts/exUserFolder/doc/FAQ.txt b/ZopeProducts/exUserFolder/doc/FAQ.txt new file mode 100644 index 00000000..d2a0be5d --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/FAQ.txt @@ -0,0 +1,119 @@ +Frequently Asked Questions + +1. Why shouldn't I use Core Session Tracking + Login Manager? +XUF serves a different set of users to the combination above. XUF +aims to be a simple out of the box solution. Login Manager allows +for very complex authorisation schemes that can query multiple user +sources. We don't do that. + +2. Why use XUF at all? +In its simplest configuration, XUF provides the same functionality +as the standard User Folder, but, is more secure. Passwords are +stored encrypted, which is not the case for the standard User Folder. +So even if you don't want to set properties on users, or any +membership facilities, there is a benefit to running XUF. + +3. Do I have to have all this other stuff? +No. The only thing you need to enable is authentication. There is +a null property source, and a null membership source. Everything +other than authentication is optional. + +4. Can I use it as a root folder? +Some people have reported success in doing so. We don't recommend +it for various reasons. The main one is that the internal Zope API +can change without warning, which could break XUF and lock you out +of your Zope. This can happen with any User Folder product. We +recommend you look at VHM and other Site Access methods to allow +you to store your main site in a sub-folder. + +5. When will XUF support authentication against XYZ system? +That depends. First the active developers need to have an interest +in it, and more importantly they need to be able to test it. Writing +your authentication method is very simple, so if you understand +what you want to authenticate against, and know some python you +could write one in an hour. You can also use the usAuthSource to +write one using PythonScripts, ExternalMethods, DTML, or any other +callable method that Zope supports. + +6. I wrote this cool authentication source can I get it into the main + distribution? +Yes and No. If your authentication is Open Source, and has a +compatible license with XUF, and doesn't require any external +libraries, odds are it'll go into the main distribution. If it +depends on external libraries, it's possible it can conditionally +go into the main distribution. The nice thing about XUF is that +Authentication, Property, and Membership sources are all packagable +as independent products, so you can distribute it as a standalone +product, and it'll work (without having to have the code drop into +the XUF directory either). + +7. Is XUF going to be part of the Core Zope? +No idea. At the moment (0.10.5) XUF is probably not at a level that +Zope Corporation would consider mature enough for core inclusion +anyway. + +Actually the answer now, is probably not. At a minimum smbAuthSource, +and radiusAuthSource would have to be stripped and distributed +seperately. Over and above that, I would have to assign Zope Corp +co-ownership rights on the IP, which amongst other things gives +them or anyone that buys them unlimited access to future derived +works. I refuse to do this on principle, the liberal licensing of +the product should be more than adequate for any (especially open +source) endeavour. + +8. What's with the Management Screens? +It's a joke on the Zope World. + +9. But they're really ugly I want to change them. +That's fine, you do that, that's the point. + +10. Can I send you patches to put them back to standard Management + Screens? +You can put patches into the tracker at Source Forge if you want to. + +11. HELP!!!! I tried to install XUF as my root folder, without + reading the FAQ, or really knowing what I'm doing, and now I'm + hosed!!! +That's a shame. + +12. Will XUF work with ZEO? +Unknown. However, it's almost certain that in its current form +credential caching will not work across ZEO -- you will get a +seperate User Cache for each Zope Client (which isn't really all +that bad). However, it means that if you want to use Session Tracking, +you need to lock users to one particular server. Most commercial +Load Balancers do this for you anyhow. A persistent cache will form +part of an upcoming release which will allow all of this to work +transparently across ZEO farms. + +13. Shouldn't it be EUF? +No, it should be XUF :-P + +14. How can I log in a user via a form submission? +Yes, the key is sending the __ac_name and __ac_password (yes that's +two underscores in front) as form variables to any object covered +by the XUF. + +This form will take your users to the /index_html and log them in. +You can place this anywhere in your site including /index_html. + +
      +Name:
      +Password: + +
      + +15. That Dialog box sure is ugly! How can I customize it so it looks + right in my site? +Under the contents tab add an object called MessageDialog , it can +be a dtml document, method, or even a page template. Make it look +how you want, it will acquire all objects from the folder the XUF +is in so you can use the standard_html_header, call scripts, etc. + +16. Why can't I change the default crypto method? +Because someone will change it, and all the existing passwords will cease +to work. Then I'll get lots of hate-mail, or get slagged off on other mailing +lists :-) + +17. Where is the Zen Master's Guide to exUserFolder? +Everywhere and nowhere. diff --git a/ZopeProducts/exUserFolder/doc/README.API b/ZopeProducts/exUserFolder/doc/README.API new file mode 100644 index 00000000..ab6e8ebd --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/README.API @@ -0,0 +1,171 @@ +User Sources +------------ + +This is the list of functions your auth source should provide. + +createUser(self, username, password, roles) +Create a User to authenticate against username, password and roles, +should all be obvious. + + +updateUser(self, username, password, roles) +Update a user's roles and password. +An empty password means do not change passwords, so at least at this time +it's not possible to have passwordless accounts. + + +cryptPassword(self, username, password) +Encrypt a password +If no 'crypt' method is supplied return the Password -- +i.e. plaintext password. + + +deleteUsers(self, userids) +Delete a set of users, userids is a list of usernames to delete. + + +listUserNames(self) +returns a list of usernames. + +listOneUser(self,username) +Return one user matching the username +Should be a dictionary; + {'username':username, 'password':cryptedPassword, 'roles':list_of_roles} +Once again, you can provide more information than this if you're customising +the forms. + +listUsers(self) +Return the list of users in the same format as listOneUser + + +remoteAuthMethod(self, username, password) +You can define this to go off and do the authentication instead of +using the basic one inside the User Object. Useful for IMAP/LDAP auth +type methods where the authentication is handled elsewhere and we just want +to know success or failure. If you don't want to do this you should have; + +remoteAuthMethod=None + +in your AuthSource (to explicitly tell the exUserFolder that you don't). + +------------------------------------------------------------------------ +This is a skeleton class; + +manage_addfooAuthSourceForm=HTMLFile('manage_addfooAuthSourceForm', globals()) +manage_editfooAuthSourceForm=HTMLFile('manage_editfooAuthSourceForm', globals()) + +class fooAuthSource(Folder): + + meta_type='Authentication Source' + title='User Supplied Authentication' + icon ='misc_/exUserFolder/exUserFolderPlugin.gif' + manage_editForm=manage_editfooAuthSourceForm + + def __init__(self): + self.id='fooAuthSource' + + # Create a User to authenticate against + # username, password and roles + def createUser(self, username, password, roles): + """ Add A Username """ + pass + + # Update a user's roles and password + # An empty password means do not change passwords... + def updateUser(self, username, password, roles): + pass + + # Encrypt a password + # If no 'crypt' method is supplied return the + # Password -- i.e. plaintext password + def cryptPassword(self, username, password): + pass + + # Delete a set of users + def deleteUsers(self, userids): + pass + + # Return a list of usernames + def listUserNames(self): + pass + + # Return a list of user dictionaries with + # {'username':username} can be extended to pass back other + # information, but, we don't do that just now + def listUsers(self): + pass + + # Return one user matching the username + # Should be a dictionary; + # {'username':username, 'password':cryptedPassword, 'roles':list_of_roles} + def listOneUser(self,username): + pass + + # + # Return a list of users, dictionary format as for listOneUser + # + def getUsers(self): + pass + + # + # You can define this to go off and do the authentication instead of + # using the basic one inside the User Object + # + remoteAuthMethod=None + +## def remoteAuthMethod(self, username, password): +## pass + + def postInitialisation(self, REQUEST): + pass + +fooAuthReg=PluginRegister('fooAuthSource', 'User Supplied Authentication Source', + fooAuthSource, manage_addfooAuthSourceForm, + manage_addfooAuthSource, + manage_editfooAuthSourceForm) +exUserFolder.authSources['fooAuthSource']=fooAuthReg + +------------------------------------------------------------------------ + +Property Sources +---------------- + +Property Sources have only a few things they need to provide; + +hasProperty(self, key) +Returns true if the current user has that property + +setProperty(self, key, value) +Sets a property for the current user + + +setUserProperty(self, key, username, value) +Sets a property for the given user. + + +getProperty(self, key, default=None) +Returns the requested property or the default for the current user. + + +getUserProperty(self, key, username, default=None) +Returns the requested property or the default for the named user. + + +listProperties(self) +Returns a list of properties (just the properties not their values). + + +listUserProperties(self, username) +Returns a list of properties for the named user. + +createUser(self, username, REQUEST) +Creates a new user, and adds in the properties in the REQUEST. +New properties are preceded with "user_KEYNAME", so strip user_ to +set the property. + +deleteUsers(self, userids) +Delete the list of users (and their properties) contained within userids. + +updateUser(self, username, REQUEST) +Change the list of properties for a user, the variables are formatted as for +createUser. diff --git a/ZopeProducts/exUserFolder/doc/README.LDAPAuthSource b/ZopeProducts/exUserFolder/doc/README.LDAPAuthSource new file mode 100644 index 00000000..fa3bc004 --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/README.LDAPAuthSource @@ -0,0 +1,20 @@ +This is a reimplementation of the auth_ldap auth source for apache, written by +Dave Carrigan and others. + +You can find the original auth_ldap Apache code at +http://www.rudedog.org/auth_ldap/ + +This auth source is covered by the Apache license; + +Copyright (C) 1998, 1999 Enbridge Pipelines Inc. +Copyright (C) 1999-2001 Dave Carrigan +Copyright (C) 2003 The Internet (Aust) Pty Ltd. +All rights reserved. + +This module is free software; you can redistribute it and/or modify +it under the same terms as Apache itself. This module is +distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. The copyright holder of this +module can not be held liable for any general, special, incidental +or consequential damages arising out of the use of the module. diff --git a/ZopeProducts/exUserFolder/doc/README.Membership b/ZopeProducts/exUserFolder/doc/README.Membership new file mode 100644 index 00000000..17937f39 --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/README.Membership @@ -0,0 +1,144 @@ +There are now membership hooks defined, and a basic membership +source defined for exUserFolder. This is a first stab at this, so +please be careful :-) + +Membership adds a level of complexity to everything, and basically is a +controlled way of breaching your security. + +You will need to prepare a few things before trying to add an exUserFolder +with Membership support; + +a) You will need a MailHost object available +b) You will need to define some methods for Membership to use + i) a Login Page + ii) a Change Password Page + iii) a Signup Page + iv) a Forgot My Password Page (optional) + v) a change Properties Page (optional) + These should live at the same level as your acl_user (i.e. not inside). + These should be fairly simple, I've included some examples below. + These should just wrap the ones below acl_users. There will be methods + you can edit to get all your fields and layout done. +c) If you want the results pages from Signup, Change Password, etc to fit + your site, you'll need to add a MessageDialog document in the contents + of the XUF. See FAQ 15 for more + + +When you see the creation form, obviously some of the options are +mutually exclusive. + +e.g. You can't choose system defined passwords, and have the system + email a hint, if they forgot their password. So try to pick sane + combinations of options. + + + + + +If you choose to have Home Directories, basicMemberSource will create +the path you provide, so you don't need to do that in advance. + +If you want to have skeleton files copied to their homedir you'll need +to have that directory (it can be empty) setup and ready to go, before +the first user signs up. + +WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +If you get basicMembershipSource to create Home Directories for your +users, it will create a 'Folder' and it will give the user management +permissions on that Folder. This means that they will be able to add +any object you can, just at a level below this. You should create/have +a proper HomeFolder object that is restricted in what is available +for adding, and change makeHomeDir() in basicMemberSource.py to create +one of those. + +I will look at creating a restricted HomeDirectory Object in a later +release, and allow you to add and remove meta_types from it. +WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING +WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING + +======================================================================== + + + +------------------------------------------------------------------------ +LoginForm +------------------------------------------------------------------------ + + + +------------------------------------------------------------------------ + + + +------------------------------------------------------------------------ +ChangePasswordForm +------------------------------------------------------------------------ + + + + + + + +------------------------------------------------------------------------ + + + +------------------------------------------------------------------------ +SignupForm +------------------------------------------------------------------------ + + + + + + + + + +------------------------------------------------------------------------ +ForgotMyPassword +------------------------------------------------------------------------ + +
      +Username: +
      + + + + +------------------------------------------------------------------------ +ChangePropertiesForm +------------------------------------------------------------------------ + + +

      Changing Properties for

      + + +
      + + +
      +

      Properties

      + + + + + + + + + +
      Property NameValue
      ">
      + + +
      + +
      + +------------------------------------------------------------------------ diff --git a/ZopeProducts/exUserFolder/doc/README.httpsAuthSource b/ZopeProducts/exUserFolder/doc/README.httpsAuthSource new file mode 100644 index 00000000..21697df3 --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/README.httpsAuthSource @@ -0,0 +1,28 @@ +This plugin implements authentication from an https service. + +Upon installation, the mangament forms allow you to configure: +* the url to the service, +* the parameter that will contain the username +* the parameter that will contain the password +* The expected authorization response regex (returned from the authorization service). +* The default role that authorized users will be assinged upon their first login + +The https auth source posts a request over https to the named service with the username and +passowrd passed according to the parameters defined in the configuration. It will attempt +to match the authorization pattern specified, and if the pattern is found, the user will be +authenticated. + +Once a user has logged in, they will appear in xuf user list, and their roles can be updated. + +This auth source has been developed using the zodbBTreeProps plugin, and stores the user's +roles in this property tool. + +A typical use case for this authorization service might be to authenticate against +a legacy user directory for which no xuf auth plugin currently exists. Hopefully, the +development of a the auth service on the other end will be trivial, or better yet, +already exist. + +IMPORTANT NOTE: In order to use this plugin you must compile your python to include +ssl support. The python that ships with zope 2.X does not have this enabled by default. + +Thanks to akm, bcsaller, runyaga for all their help. diff --git a/ZopeProducts/exUserFolder/doc/README.pgAuthSource-Alternate b/ZopeProducts/exUserFolder/doc/README.pgAuthSource-Alternate new file mode 100644 index 00000000..8652263c --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/README.pgAuthSource-Alternate @@ -0,0 +1,20 @@ +This alternate pgAuthSource was developed to allow Zope and jakarta-tomcat to +share common PostGreSQL auth tables. It's really just a mod of the original +pgAuthSource, with changes to the original kept to a minimum. This should help +when it comes to cross porting improvements / maintenence changes between the +two versions. + +The only thing that's new is the table schema. This auth source uses: +A user table + Username, password + +A role table: + rolename + +and a associative userrole table for relating the two: + username, rolename + +ps. Use the Source, Luke! +If you dig a little you will find a couple of different ways of crypting +passwords commented out (plain and MD5). + diff --git a/ZopeProducts/exUserFolder/doc/README.radiusAuthSource b/ZopeProducts/exUserFolder/doc/README.radiusAuthSource new file mode 100644 index 00000000..60b940a5 --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/README.radiusAuthSource @@ -0,0 +1,12 @@ +I have converted ZRadius to work with exUserFolder as an AuthSource +it should function at least as well as it functioned with GUF d8) +-- akm + +Radius + + This product implements simple Radius authentication. If you need + to authenticate Zope users from a Radius server, this product will + plug into the GenericUserFolder product. + + © Copywrite 1999 Stuart Bishop <zen@cs.rmit.edu.au> + diff --git a/ZopeProducts/exUserFolder/doc/README.smbAuthSource b/ZopeProducts/exUserFolder/doc/README.smbAuthSource new file mode 100644 index 00000000..ba4e52ab --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/README.smbAuthSource @@ -0,0 +1,41 @@ + +SMB Authentication Source for exUserFolder + +(C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +ACN: 082 081 472 ABN: 83 082 081 472 +All Rights Reserved + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +This smbAuthSource uses pySMB by Michael Teo whose copyright is as follows. + +Copyright (C) 2001 Michael Teo + +This software is provided 'as-is', without any express or implied warranty. +In no event will the author be held liable for any damages arising from the +use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice cannot be removed or altered from any source distribution. + diff --git a/ZopeProducts/exUserFolder/doc/README.txt b/ZopeProducts/exUserFolder/doc/README.txt new file mode 100644 index 00000000..3152aa79 --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/README.txt @@ -0,0 +1,127 @@ +A lot of this stuff is now covered in the Unenlightened Zopistas Guide to +exUserFolder, so this document is slightly redundant (but not completely). +It's also shockingly out of date... + +A note on the version numbering... there's none of that odd/even +numbering nonsense you find in Lin*x land. The numbers are based on + +Major.Minor.Micro + +A bump in Major means a milestone or set of milestones has been reached. +A bump in Minor means some piece of functionality has been added, or +a major bug was fixed. +A bump in Micro usually means bug fixes. + +These numbers go from 0-99, and are not necessarily continuous or +monotonically increasing (but they are increasing). + +What you consider major and what I consider major are probably two +different things. + +Release candidates before a Major bump start at 99.. so; + +0.99.0 is the first Release Candidate prior to 1.0.0 +0.99.1 is the next Release Candidate +1.0.0 is the 'final' release. + +It's possible that there will be no changes between final release candidate +and release. + +Sometimes due to the nature of the changes a release will be marked +development. This usually means some core functionality was changed. + +Extensible User Folder + +Extensible User Folder is a user folder that requires the authentication +of users to be removed from the storage of properties for users. + +Writing new authentication or property sources is (almost) a trivial operation +and require no authentication knowledge to write, most simply return lists +of attributes, or dictionaries. You don't need to incorporate them into +the base exUserFolder code, they can be written as normal Zope Products, +(i.e. you can distribute new sources independently of exUserFolder). + +There are three authentication sources provided OOTB; + +o pgAuthSource -- Postgresql Authentication Source +Actually this is pretty generic, and could be used for most SQL databases, +the schema isn't all that advanced either. + +This source allows you to specify the table, and the name of each of the +columns (username, password, roles), so you can use an existing database. + + +All ZSQL Methods are available inside the pgAuthSource folder for editing. +You need to have a DB Connection already in place for use. + +o usAuthSource -- User Supplied Authentication +This is similar to Generic User Folder, or Generic User Sources for +Login Manager. You provide a set of methods; + +createUser -- if you want to create users. +cryptPassword -- if you want to encrypt passwords. +listUsers -- return a list of userIds. +listOneUser -- return a dictionary containing the username, password, and + a list of roles +getUsers -- return a list of users ala listOneUser but lots of them. + +that's it. listOneUser is mandatory. + +There is an example of ExternalMethods you could use to create an 'sql' +user source in the Extensions directory. + +o etcAuthSource -- File Based Authentication +This is etcUserFolder reworked to be a plugin for this. Since I used +etcUserFolder as a base for this product, I might as well add the functionality +in. + +Each of the plugins has a 'manage_addForm' that is called when the User Folder +is added, so that parameters can be garnered from the user (pg*Source e.g. +get the dbconnection) + +etcAuthSource doesn't allow roles to be set, although it does ask for +a default role to be assigned to a user (which it dutifully does). + + +There are two property sources provided: + +o pgPropertySource -- Postgresql Property Source +A very naive sql implementation for properties, works fine, I wouldn't want +to load it up too high though. + +o zodbProperySource -- ZODB Property Source +This is a very simple property keeper, and is more available as an example +of what functionality needs to be provided. + +There is a postUserCreate method which you can replace with anything really, +if you want to do something once a user is created (send an email to someone, +page someone...) + +You can mix-n-match authentication methods and property methods. + +You can have cookie or standard (Basic) authentication. + +docLogin and docLogout methods are present in the ZODB for editing, because +you will hate the way the default login and logout pages look d8) + +The various plugins need some more configurable options atm, but, it +shouldn't be that much of a drama to add them in soon. + +Arbitrary properties can be set on a user (at least if the PropertySource +is written correctly they can). + + + + + + +Will all work (assuming the user is logged in). + +When creating a new user any fields with user_ will be set as properties. +So 'user_email' field will create an 'email' property. You just have to +provide the input form, and exUserFolder will do the rest. + +This has only been lightly tested, but, it seems to work just fine. + + + diff --git a/ZopeProducts/exUserFolder/doc/README.zodbAuthSource b/ZopeProducts/exUserFolder/doc/README.zodbAuthSource new file mode 100644 index 00000000..492e7f3f --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/README.zodbAuthSource @@ -0,0 +1,13 @@ +ZODB Authentication Source for exUserFolder + +This is an auth source that works pretty much like the standard user folder provided by zope. +It stores the usernames, roles and passwords on a ZODB persistent dictionary. + +It doesn't require any configuration at all, just select it as your auth source and you're +ready to add user accounts. + + +Author: Alex Verstraeten (aka: zxc) +Email: alex@quad.com.ar + + diff --git a/ZopeProducts/exUserFolder/doc/UnenlightenedZopistasGuide.txt b/ZopeProducts/exUserFolder/doc/UnenlightenedZopistasGuide.txt new file mode 100644 index 00000000..547bdc20 --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/UnenlightenedZopistasGuide.txt @@ -0,0 +1,671 @@ +The Unenlightened Zopistas Guide to exUserFolder. +(C) 2001-2003 Andrew Milton + +0. INTRODUCTION. + +exUserFolder is an extensible authentication product for the Zope +Application Server. It allows a user to choose from a number of +methods of authenticating their users, and allows the setting and +fetching of arbitrary properties on a User object. + +Authentication methods, and Property methods do not have to use the +same backing store, so it is possible to use legacy user sources, and +still have configuration information stored about a user. + +exUserFolder supports HTTP Basic Authentication, and the so called +Cookie Authentication schemes popular with most webservers. + +0.1 Audience. + +Everybody, and Nobody. If we build our product correctly, we shouldn't +need user documentation, that's why Nobody. For some reason, normal +sane people seem to lose all of their common sense when sitting in +front of a computer. To the point where plain messages e.g. "Please +Insert a Floppy", evoke a response of "What does that mean?" So that's +why this document is for Everybody. + +This is not a guide for writing your own authentication, property, or +membership sources. You need the Zen Masters Guide to exUserFolder +document for that. + + +1. GETTING STARTED + +exUserFolder requires Python 2.1 or above (may work with older +pythons, but, this is not supported). And has been tested on Zope +2.3.0 and above (including 2.4.3). + +exUserFolder comes as a source tarball, and it does not rely on any +outside dependencies to be work. Some items may require additional +products to be installed in your Zope tree, but, simply will not +appear if these are not available. Some items may also require +external products to add functionality to them, but, at no time should +the Product not install because of these dependencies. + + +1.1 Installing exUserFolder. + +Unpack exUserFolder in your Products directory. This can be achieved by +executing the following on most UNIX systems. + +gzip -d -c exUserFolder-Ma_Mi_u.tgz | tar xvf - + +where exUserFolder-Ma_Mi_u.tgz is the version of exUserFolder you have +downloaded. + +On systems that have the GNU tar this can be shortened to; + +tar zxvf exUserFolder-Ma_Mi_u.tgz + +You should restart Zope after unpacking. Installing the product will not +affect your Zope installation in anyway. + +If you go to Folder inside your Zope Management Interface, you should see +exUserFolder as a dropdown item near the bottom somewhere. + +Congratulations, it's installed. + + +2. AUTHENTICATION SOURCES AND YOU. + +The only mandatory component of an exUserFolder installation, is +choosing an Authentication Source. There are six Authentication +Sources to choose from in the default install. There are other add-on +Sources available from other parties. + +Each Authentication Source is different, and assumes at least some +knowledge of that type of authentication scheme. + +Most if not all sources store the password encrypted in some +manner. This means that discovering other people's passwords is going +to be more difficult, than with the standard user folder that comes +with Zope. + +By default crypt or fcrypt are used, which is are DES encryption +methods. While this is not the strongest, fastest, choose another +superlative, of encryption techniques, it is certainly adequate for +protecting webpages. + +In a later release exUserFolder will allow you to choose what method +is used for password hashing. + +Some Authentication Sources can list the users that are available, +some cannot (or will not). Some allow you to add users, and others do +not. What features are availble depend on the individual +Authentication Source. + + +2.1 ZODB Authentication Source + +The ZODB Authentication Source operates just like a normal User +Folder. It stores its authentication items in the ZODB as the name +suggests. This is the simplest folder to setup, requiring no +parameters. + +Choosing ZODB Authentication Source is recommended for testing your install. + + +2.2 File Based Authentication. + +File Based Authentication allows you to have a fixed set of users in a +file with their encrypted passwords. The prerequisites for this are +somewhat convoluted. + +In the root of your Zope Installation, on the actual file system (not +in the ZODB), create a directory called exUsers. Make sure that Zope +has access to that directory. + +This is the directory where you will create your files. + +This is a read only Authentication Source. You will not be able to +create users, or modify their passwords. You can change their roles if you +choose a Property Source. + +There are two parameters asked for; + +2.2.1 Password File + +This is the name of the file that contains your users and passwords. +It should be of the format; + +username:cryptedPassword +user2:otherCryptedPasswd + +I can contain other fields after the password also delimited by : but these +will not be ussed. + +This file should exist inside the exUsers directory. + +2.2.2 Default Role + +This is the role that all users should be given when the log in. Because this +is a Read Only authentication source, you may not be able to add Roles at a +later date. + + +2.3 Postgresql Authentication Source + +Postgresql Authentication source is an RDBMS backed user store. You +can add, change, and list users. It requires a Postgresql Database +Connection to be created before creating the User Folder. + +You should be familiar with databases, and with your schema before +using this Authentication Source. If you don't already have a table +structure in place, a default schema is provided called 'pgScheme.sql' +in the exUserFolder distribution. + +The required schema is very simple. You need to store usernames, +passwords, and roles. If your existing schema doesn't support a roles +column you will have to add one. + +The configuration scheme looks daunting, but, it is setup to use the +defaults for 'pgScheme.sql' so if you're using this you can safely +continue. + +We will run through the items. + +2.3.1 Database Connection + +If you have any database connections, they will be listed in the drop +down box. Choose the one that represents your connection to your users +table. + +2.3.2 Table Name + +This is the name of the table containing your users. If you have a +different table to the default, you should change it here. + +2.3.3 Username Column + +This is the name of the column inside your table that contains the +usernames or logins of your users. This should contain exactly what +the user needs to type in as their username. + + +2.3.4 Password Column + +This is the name of the column inside your table that contains the +encrypted passwords of your users. + + +2.3.5 Roles Column + +This is where the roles are stored. These are used to provide access +to items in Zope. + + +2.4 User Supplied Authentication Source + +This allows you to create your methods in DTML, PythonScripts, +External Methods, or any other callable Zope item for listing, +authenticating, adding and changing your users. + +It is beyond the scope of this guide to describe how to do this, but, +the API is quite well defined inside the source, and also in the +README.API document. + +This Authentication Source has no configuration parameters. + + +2.5 RADIUS Authentication Source + +This allows you to authenticate your users against a RADIUS server. If +you don't know what this means, then this User Source is not for you +:-) + +You will require a RADIUS server to be operating, and for the server +that Zope is running on to have access to it. You will also need to +know the secret key to access the RADIUS server. + +2.5.1 Host + +This is the host your RADIUS server is running on. + +2.5.2 Port + +This is the port your RADIUS server is running on. Older installs may +require this to be 1645. The new 'blessed' port by IANA is 1812, and +this is now the default port. + +2.5.3 Secret + +Every remote host has a secret key it has to share with the server in +order to gain access to the authentication server. You need to know +this. + +2.5.4 Retries + +Because this is a networked authentication service, errors can +occur. This sets the number of times it will try to authenticate +before giving up. + +2.5.5 Timeout + +This is how long the RADIUS authenticator will wait for a +response. Because RADIUS operates over UDP, which is a connectionless +protocol, answers may never come back, or never reach their +destination in the first place. + +The default is 5 seconds which is actually quite a long time. + + +2.6 SMB Authentication Source + +This source allows you to authenticate your users in a Microsoft +Environment, using the SMB protocols. This is not the same as +authenticating via Directory Services. + +If your SMB server requires passwords to be encrypted in transit, you'll +need to install mxCrypto. + +2.6.1 Host + +This is the host that your Authentication service is on, this is +normally an NT or Win2K server, but, it can also be a UNIX box running +Samba. This should be the NetBIOS name of the server. + +2.6.2 Domain + +This is the NT/Windows DOMAIN that the user is to authenticate +against. + +2.6.3 WINS Server IP Address (optional) + +If provided, this should be the IP address of the WINS server to be +queried to locate your auth host (see 2.5.1 above). + +If you leave this field empty, the location of the authentication host +will be queried by broadcast, which works just fine if the Zope +machine is on the same subnet as your auth host but not if the auth +host is across a subnet link or if it's in the same machine as Zope +(don't ask. Apparently, some braindmamaged creature at M$ decided that +a machine shouldn't answer to its own broadcasts no matter what) + +Fill in this field if you are getting "NetBIOSTimeout" errors but you +are sure that your auth host was specified correctly, or if Windows +machines in your subnet also use a WINS server. + + +2.7 LDAP Authentication + +This source allows you to authenticate your users against an LDAP +server. This code is based on the auth_ldap module for Apache. The +documentation for these parameters is unashamedly lifted directly from +the documentation of the Apache directives for auth_ldap. + +See: http://www.rudedog.org/auth_ldap/ + +You must choose a property source when using LDAP Authentication, all +of the properties associated with the LDAP user entry are stored as +properties when they authenticate. Items with multiple entries are +stored as a list of items. + +You will need the pyLDAP module installed to use this authsource. +If you don't have it installed, you will not see an LDAP Auth Source available +for use. + +2.7.1 URL + +An RFC 2255 URL which specifies the LDAP search parameters to use. The syntax +of the URL is +ldap://host:port/basedn?attribute?scope?filter + +ldap For regular ldap, use the string ldap. For secure LDAP, use ldaps + instead. Secure LDAP is only available if auth_ldap was compiled with + SSL support. +host:port The name/port of the ldap server (defaults to localhost:389 for ldap, + and localhost:636 for ldaps). + + Once a connection has been made to a server, that connection remains + active for the life of the Zope process, or until the LDAP server + goes down. + + If the LDAP server goes down and breaks an existing connection, + the Auth Source will attempt to re-connect + +basedn The DN of the branch of the directory where all searches should start + from. At the very least, this must be the top of your directory tree, + but could also specify a subtree in the directory. + +attribute The attribute to search for. Although RFC 2255 allows a + comma-separated list of attributes, only the first attribute will be + used, no matter how many are provided. If no attributes are provided, + the default is to use uid. It's a good idea to choose an attribute + that will be unique across all entries in the subtree you will be + using. + +scope The scope of the search. Can be either one or sub. Note that a scope + of base is also supported by RFC 2255, but is not supported by this + module. If the scope is not provided, or if base scope is specified, + the default is to use a scope of sub. + +filter A valid LDAP search filter. If not provided, defaults to (objectClass + =*), which will search for all objects in the tree. + +When doing searches, the attribute, filter and username passed by the HTTP +client are combined to create a search filter that looks like (&(filter) +(attribute=username)). + +For example, consider an URL of ldap://ldap.xuf.com/o=XUF?cn?sub?(posixid +=*). When a client attempts to connect using a username of The Jester, the +resulting search filter will be (&(posixid=*)(cn=The Jester)). + + +2.7.2 Bind DN + +An optional Distinguished Name user to bind to the server when searching +for entries. If not provided an Anonymous bind will be used. + +2.7.3 Bind Password. +A bind password to use in conjunction with the bind DN. Note that the bind +password is probably sensitive data, and should be properly protected. You +should only use the Bind DN and Bind Password if you absolutely +need them to search the directory. + +2.7.4 Cert DB Path + +Specifies in which directory LDAP Auth Source should look for the certificate +authorities database. There should be a file named cert7.db in that directory. + +2.7.5 Compare DN On Server + +When set, LDAP Auth Source will use the LDAP server to compare the +DNs. This is the only foolproof way to compare DNs. LDAP Auth Source +will search the directory for the DN specified with the require dn +directive, then, retrieve the DN and compare it with the DN retrieved +from the user entry. If this directive is not set, LDAP Auth Source +simply does a string comparison. It is possible to get false negatives +with this approach, but it is much faster. Note the LDAP Auth Source cache +can speed up DN comparison in most situations. + +2.7.6 Dereference Aliases + +This directive specifies when LDAP Auth Source will de-reference +aliases during LDAP operations. The default is always. + +2.7.7 Group Attribute is DN + +When set, this directive says to use the distinguished name of the +client username when checking for group membership. Otherwise, the +username will be used. For example, assume that the client sent the +username tjester, which corresponds to the LDAP DN cn=The Jester, +o=XUF. If this directive is set, LDAP Auth Source will check if the +group has cn=The Jester, o=XUF as a member. If this directive is not +set, then LDAP Auth Source will check if the group has tjester as a +member. + +2.7.8 Compare Cache Size + +This specifies the size of the cache used to cache LDAP compare +operations. The default is 1024 entries. Setting it to 0 disables +operation caching. + +2.7.9 Compare Cache TTL + +Specifies the time (in seconds) that entries in the operation cache +remain valid. The default is 600 seconds. + +2.7.10 Start TLS + +If this is set to Yes, LDAP Auth Source will start a secure TLS +session after connecting to the LDAP server. This requires your LDAP +server to support TLS. + +2.7.11 Require User (one per line) + +The require user directive specifies what usernames can access the +resource. Once LDAP Auth Source has retrieved a unique DN from the +directory, it does an LDAP compare operation using the username +specified in the require user to see if that username is part of the +just-fetched LDAP entry. Multiple users can be granted access by +putting multiple usernames in the box, separated with newlines. For +example, with a AuthLDAPURL of ldap://ldap/o=XUF?cn (i.e., cn is used +for searches), the following require entries could be used to restrict +access: The Jester Fred User Joe Manager + +Because of the way that LDAP Auth Source handles this directive, The +Jester could sign on as The Jester, Zen Jester or any other cn that he +has in his LDAP entry. Only the single require user line is needed to +support all values of the attribute in the user's entry. + +If the uid attribute was used instead of the cn attribute in the URL +above, the above three lines could be; + +tj +fred_u +jmanager + +2.7.12 Require Group (one per line) + +This directive specifies an LDAP group whose members are allowed +access. It takes the distinguished name of the LDAP group. For +example, assume that the following entry existed in the LDAP +directory: + +dn: cn=Administrators, o=XUF +objectClass: groupOfUniqueNames +uniqueMember: cn=The Jester, o=XUF +uniqueMember: cn=Fred User, o=XUF + +The following directive would grant access to both Fred and Jester: + +require group cn=Administrators, o=XUF + +Behavior of this directive is modified by the Group Attribute and +Group Attribute Is DN options. + +2.7.13 Require DN + +The require dn option allows the administrator to grant access based +on distinguished names. It specifies a DN that must match for access +to be granted. If the distinguished name that was retrieved from the +directory server matches the distinguished name in the require dn, +then authorization is granted. + +The following directive would grant access to a specific DN: +require dn cn=The Jester, o=XUF + +Behavior of this directive is modified by the Compare DN On Server option. + +2.7.14 Default Manager + +This allows you to specify the username of the Manager for this area. +The manager will still need to meet auth requirements above, but, if +they do they will get the 'Manager' role added to their list of roles. + +2.7.15 Default Role + +This is a role to be assigned to users when they auth correctly. This +is to differentiate them from merely being 'authenticated'. + +2.7.16 Examples + + * Grant access to anyone who exists in the LDAP directory, using their UID + for searches. + URL ldap://ldap1.zope.com:389/ou=People, o=XUF?uid?sub?(objectClass=*) + + * The next example is similar to the previous one, but is uses the common + name instead of the UID. Note that this could be problematical if multiple + people in the directory share the same cn, because a search on cn must + return exactly one entry. That's why this approach is not recommended: it's + a better idea to choose an attribute that is guaranteed unique in your + directory, such as uid. + URL ldap://ldap.zope.com/ou=People, o=XUF?cn + + * Grant access to anybody in the Administrators group. The users must + authenticate using their UID. + URL ldap://ldap.zope.com/o=XUF?uid + require group: + cn=Administrators, o=XUF + + * The next example assumes that everyone at XUF who carries an + alphanumeric pager will have an LDAP attribute of qpagePagerID. The example + will grant access only to people (authenticated via their UID) who have + alphanumeric pagers: + URL: ldap://ldap.zope.com/o=XUF?uid??(qpagePagerID=*) + + * The next example demonstrates the power of using filters to accomplish + complicated administrative requirements. Without filters, it would have + been necessary to create a new LDAP group and ensure that the group's + members remain synchronized with the pager users. This becomes trivial with + filters. The goal is to grant access to anyone who has a filter, plus grant + access to Joe Manager, who doesn't have a pager, but does need to access + the same resource: + URL ldap://ldap.zope.com/o=XUF?uid??(|(qpagePagerID=*)(uid=jmanager)) + + This last may look confusing at first, so it helps to evaluate what the + search filter will look like based on who connects, as shown below. + If Fred User connects as fuser, the filter would look like + + (&(|(qpagePagerID=*)(uid=jmanager))(uid=fuser)) + + The above search will only succeed if fuser has a pager. When Joe Manager + connects as jmanager, the filter looks like + + (&(|(qpagePagerID=*)(uid=jmanager))(uid=jmanager)) + + The above search will succeed whether jmanager has a pager or not. + + +2.8 General Items. + +You can choose to use standard auth, or cookie auth, and you can +decide how long you want to cache the users credentials before +retrying. + +2.8.1 Authentication Type + +2.8.1.1 Standard Authentication + +This method causes the browser to pop up a dialog box to ask for the +username and password. + +2.8.1.2 Cookie Authentication + +This method allows you to use a normal HTML form to get the username +and password from the user. It also will present the default form to +the user if they try to access an unauthorised area. + + +2.8.1.3 Secure Cookie based Authentication + +This method, like Cookie Authentication allows you to use a HTML form +to get the user details. However, the cookie it uses does not contain +any login information. It is internally checked against a cache of +hashes and the information is derived from that. This cache disappears +if you restart Zope, so this is not a good option for people who want +to persistently cache logins across sessions. + + +2.8.2 Credential Cache Timeout in Seconds + +exUserFolder by default caches credential information, so that the +authorisation source isn't hit *for every object and page* that has to +be fetched. For remote authentication services this can slow things +down quite considerably. Even setting this to a modest setting will +quicken response times. + +Setting this too long could cause problems if you want to lock out a +troublesome user. The credential cache is flushed if someone provides +a password that doesn't match the one in the cache. + + +2.8.3 Negative Credential Cache Timeout in Seconds + +exUserFolder allows you to cache login failures for users that do not +exist. This means you don't have to go out to your auth source when +you know for certain this user is never going to be able to +authenticate. + +Due to the way some auth sources are designed, this doesn't work for +auth sources like SMB Auth Source that lie initially about the user +existing (because you can't verify the existence of a user without +authenticating them), and then check the credentials later. + +It's possible to create a hybrid auth source that lets this work +correctly for auth sources that can't list the users. + + +2.8.4 Log out users who expire from cache? + +If you've got caching turned on, then this will force any user who has +their session expire to login again. Some people like to do this. + + +2.8.5 Activate Session Tracking for anoymous users? + +For any anonymous user, a new temporary user is created. This allows +you to set/get properties for anonymous users too. Currently +experimental. + + +3.0 PROPERTY SOURCES + +4.0 MEMBERSHIP SOURCES + +5.0 TIPS FOR THE UNWARY + +Generally these things apply to Cookie Authentication models, since +there is additional access required to present the login form. + +5.1 Too much protection. + +A lot of people try to protect a folder by placing an exUserFolder +inside. They then change the permissions on this folder to only allow +Authenticated or some Local Role to have permission. + +5.1.1 The problem + +When you try to access the folder, instead of getting the form, you +get a popup box, even though you chose Cookie Authentication. Even +when you enter a username and password it doesn't work. + + +5.1.2 What happened + +You tried to access an area you don't have access to. Zope found the +closest user folder to the object you were trying to access. The user +folder decided you were not authorised and tried to display the login +form. You don't have access to view the login form, so Zope finds the +nearest user folder to the login form, which is the user folder above +the protected directory. It pops up the authentication dialog. If you +put in a valid username and password for this top level, then lower +level then displays the login form. + +5.1.3 Solution 1 (preferred). + +Place the user folder one level *above* the folder you want to protect, +that is in the unprotected area. Everything should work fine. + +5.1.4. Solution 2 (not so preferred). + +Set the 'View' permission on the docLogin form inside the acl_users +folder. You can get there by Choosing 'Contents' on docLogin and +scrolling down to the bottom. + +6.0 MISCELLANY + +6.1 Adding an exUserFolder from a product. + +You can add an exUserFolder from a Python product fairly easily, if +not a tad messily. + + +from Products.exUserFolder.exUserFolder import manage_addexUserFolder, eUserFolder + +manage_addexUserFolder(authId='zodbAuthSource', propId='zodbPropSource', + memberId='basicMemberSource', + cookie_mode=1, session_length=600, REQUEST) + +Obviously change authId, propId, and memberId to what you want. +However, you'll need to ram in the appropriate form fields for the various +source constructors into your REQUEST. + +6.2 Session Tracking. + +Session tracking (currently) relies on having the credential cache +active, and a property source active. Your trackable user will only +last as long as they are not expired from the cache. You should set +the cache expiry length to be somewhat longer than normal if you plan +to use Session Tracking, and you should also be prepared to check that +the current session is valid. diff --git a/ZopeProducts/exUserFolder/doc/mysql.sql b/ZopeProducts/exUserFolder/doc/mysql.sql new file mode 100644 index 00000000..b2bf5f82 --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/mysql.sql @@ -0,0 +1,18 @@ +DROP TABLE IF EXISTS passwd; +CREATE TABLE passwd ( + username varchar(64) NOT NULL PRIMARY KEY, + password varchar(64) NOT NULL, + roles varchar(255) +); + +DROP TABLE IF EXISTS UserProperties; +CREATE TABLE UserProperties ( + username varchar(64) NOT NULL, + prop_key varchar(128) NOT NULL, + value text NOT NULL, + istemporary int +); + +CREATE UNIQUE INDEX username_prop_idx on UserProperties(username,prop_key ); +CREATE INDEX username_idx on UserProperties(username); + diff --git a/ZopeProducts/exUserFolder/doc/pgAndreasScheme.sql b/ZopeProducts/exUserFolder/doc/pgAndreasScheme.sql new file mode 100644 index 00000000..57fd21a4 --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/pgAndreasScheme.sql @@ -0,0 +1,14 @@ +CREATE TABLE "passwd" ( + "username" character varying(64) UNIQUE NOT NULL, + "password" character varying(64) NOT NULL, + "roles" character varying(255), + Constraint "passwd_pkey" Primary Key ("username") +); + +CREATE TABLE "userproperties" ( + "username" character varying(64) NOT NULL REFERENCES passwd (username) ON DELETE CASCADE ON UPDATE CASCADE, + "key" character varying(128) NOT NULL, + "value" text NOT NULL +); + +CREATE INDEX "username_idx" on "userproperties" using btree ( "username" "varchar_ops" ); diff --git a/ZopeProducts/exUserFolder/doc/pgScheme.sql b/ZopeProducts/exUserFolder/doc/pgScheme.sql new file mode 100644 index 00000000..45e5e1a0 --- /dev/null +++ b/ZopeProducts/exUserFolder/doc/pgScheme.sql @@ -0,0 +1,46 @@ +CREATE SEQUENCE "passwd_userid_seq" start 1 increment 1 maxvalue 2147483647 minvalue 1 cache 1 ; + +-- +-- TOC Entry ID 6 (OID 24949) +-- +-- Name: passwd Type: TABLE Owner: akm +-- + +CREATE TABLE "passwd" ( + "userid" integer DEFAULT nextval('"passwd_userid_seq"'::text) NOT NULL, + "username" character varying(64) NOT NULL, + "password" character varying(64) NOT NULL, + "roles" character varying(255), + Constraint "passwd_pkey" Primary Key ("userid") +); + +-- +-- TOC Entry ID 4 (OID 24965) +-- +-- Name: userproperties_propertyid_seq Type: SEQUENCE Owner: akm +-- + +CREATE SEQUENCE "userproperties_propertyid_seq" start 1 increment 1 maxvalue 2147483647 minvalue 1 cache 1 ; + +-- +-- TOC Entry ID 7 (OID 24984) +-- +-- Name: userproperties Type: TABLE Owner: akm +-- + +CREATE TABLE "userproperties" ( + "propertyid" integer DEFAULT nextval('"userproperties_propertyid_seq"'::text) NOT NULL, + "username" character varying(64) NOT NULL, + "key" character varying(128) NOT NULL, + "value" text NOT NULL, + "istemporary" integer, + Constraint "userproperties_pkey" Primary Key ("propertyid") +); + +-- +-- TOC Entry ID 8 (OID 24984) +-- +-- Name: "username_idx" Type: INDEX Owner: akm +-- + +CREATE INDEX "username_idx" on "userproperties" using btree ( "username" "varchar_ops" ); diff --git a/ZopeProducts/exUserFolder/dtml/docLogin.dtml b/ZopeProducts/exUserFolder/dtml/docLogin.dtml new file mode 100644 index 00000000..87e30194 --- /dev/null +++ b/ZopeProducts/exUserFolder/dtml/docLogin.dtml @@ -0,0 +1,49 @@ + +
      + + + + + + +

      + +

      + + + + + Added by Emmanuel for ScoDoc + + + + + + + + + + + + + + + +
      + Name + + +
      + Password + + +
      +
      +
      +Ok "> +
      + +
      + +
      + diff --git a/ZopeProducts/exUserFolder/dtml/docLoginRedirect.dtml b/ZopeProducts/exUserFolder/dtml/docLoginRedirect.dtml new file mode 100644 index 00000000..30ed97b4 --- /dev/null +++ b/ZopeProducts/exUserFolder/dtml/docLoginRedirect.dtml @@ -0,0 +1,26 @@ + + +Logging In + +"> +&authFailedCode=&dtml-authFailedCode;
      "> + + + + + diff --git a/ZopeProducts/exUserFolder/dtml/docLogout.dtml b/ZopeProducts/exUserFolder/dtml/docLogout.dtml new file mode 100644 index 00000000..5210fc74 --- /dev/null +++ b/ZopeProducts/exUserFolder/dtml/docLogout.dtml @@ -0,0 +1,16 @@ + +

      +

      +

      Vous êtes déconnecté de ScoDoc. +

      + +

      ">revenir à l'accueil

      + +
      +

      (Attention: si vous êtes administrateur, vous ne pouvez vous déconnecter complètement qu'en relançant votre navigateur) +

      +
      + + + + diff --git a/ZopeProducts/exUserFolder/dtml/mainUser.dtml b/ZopeProducts/exUserFolder/dtml/mainUser.dtml new file mode 100644 index 00000000..e1188165 --- /dev/null +++ b/ZopeProducts/exUserFolder/dtml/mainUser.dtml @@ -0,0 +1,55 @@ + + + +
      + +

      +The following users have been defined. Click on the name of a +user to edit that user. +

      + + + + + + + + + + + + + + + + +
      + + + +
        + +
      + + +
      +
      + +

      +There are no users defined. +

      + +

      +

      + +
      +

      +
      +
      + + + diff --git a/ZopeProducts/exUserFolder/dummyZBabelTag.py b/ZopeProducts/exUserFolder/dummyZBabelTag.py new file mode 100644 index 00000000..fc45c244 --- /dev/null +++ b/ZopeProducts/exUserFolder/dummyZBabelTag.py @@ -0,0 +1,164 @@ +try: + from Products.ZBabel import ZBabelTag +except: + + from DocumentTemplate.DT_String import String + from DocumentTemplate.DT_Util import render_blocks, Eval, ParseError + import string, zLOG + + + # fake Babel/Fish Tags + + class ZBabelTag: + '''ZBabel Tag class - The cool translation tag''' + + # define the name of the tag; also tell the system it is a doublet + name = 'babel' + blockContinuations = () + + + def __init__(self, blocks): + '''__init__(self, blocks) --> Initialize tag object; return None''' + (tname, args, section,) = blocks[0] + + self.section = section + + + def render(self, md): + '''render(self, md) --> Do the Translation; return string''' + return render_blocks(self.section.blocks, md) + __call__=render + # register the DTML-BABEL tag + String.commands['babel'] = ZBabelTag + + class FishTag: + '''Fish Tag class - Short-Cut version of the cool translation tag (babel) + + This tag is used to quickly translate menu-like text snippets, similar to + the KDE translation.''' + + # define the tag name + name = 'fish' + + # define additional variables + literal = 1 + + src = 'label' + attrs = {'dst': None, 'label': '', 'data': None, 'towerId': None} + + def __init__(self, args): + '''__init__(self, blocks) --> Initialize tag object; return None''' + self.section = None + args = parseTagParameters(args, tag=self.name) + self.args = self.validateArguments(args) + + for attr in self.attrs.keys(): + setattr(self, attr, self.attrs[attr]) + + def validateArguments(self, args): + '''validateArguments(self, args) --> try to evaluate the passed expression or try to get an object from the passed id; if all this fails, leave the string, it is probably cool!; return tuple of (name, value)''' + # I stole this from dtml-let... + # SR: Like he said: Always copy existing code to make you life easier (evben though + # I changed some variables around + for count in range(len(args)): + (name, expr,) = args[count] + if ((expr[:1] == '"') and ((expr[-1:] == '"') and (len(expr) > 1))): + expr = expr[1:-1] + try: + + args[count] = (name, Eval(expr).eval) + + except SyntaxError, v: + (m, (huh, l, c, src,),) = v + raise ParseError, (('Expression (Python) Syntax error:' + + '
      \012%s\012
      \012' % v[0]), 'babel') + + elif ((expr[:1] == "'") and ((expr[-1:] == "'") and (len(expr) > 1))): + expr = expr[1:-1] + args[count] = (name, expr) + + return args + + def render(self, md): + '''render(self, md) --> Do the Translation; return string''' + data = None + for name, expr in self.args: + if type(expr) is type(''): + try: + data = md[expr] + except: + data = expr + else: + data = expr(md) + + #zLOG.LOG("exUserFolder", zLOG.INFO, "rendering name=%s expr=%s data=%s"%(name,expr,data)) + + print data + return str(data) + + __call__=render + + + # register the DTML-FISH tag + String.commands['fish'] = FishTag + + + +try: + import re + parmre=re.compile('''([\000- ]*([^\000- ="']+)=([^\000- ="']+))''');#")) + dqparmre=re.compile('([\000- ]*([^\000- ="]+)="([^"]*)")') + sqparmre=re.compile('''([\000- ]*([^\000- =']+)='([^']*)')''') +except: + import regex + parmre=regex.compile('''([\000- ]*([^\000- ="']+)=([^\000- ="']+))''');#")) + dqparmre=regex.compile('([\000- ]*([^\000- ="]+)="([^"]*)")') + sqparmre=regex.compile('''([\000- ]*([^\000- =']+)='([^']*)')''') + + + +def parseTagParameters(paramText, result = None, tag = 'babel', + parmre=parmre, + dqparmre=dqparmre, + sqparmre=sqparmre, + **parms): + result = (result or []) + + parsedParam = parmre.match(paramText) + dqParsedParam = dqparmre.match(paramText) + sqParsedParam = sqparmre.match(paramText) + + # Parse parameters of the form: name=value + if parsedParam is not None: + name = parsedParam.group(2) + value = parsedParam.group(3) + length = len(parsedParam.group(1)) + + # Parse parameters of the form: name="value" + elif dqParsedParam is not None: + name = dqParsedParam.group(2) + value = ('"%s"' % dqParsedParam.group(3)) + length = len(dqParsedParam.group(1)) + + # Parse parameters of the form: name='value' + elif sqParsedParam is not None: + name = sqParsedParam.group(2) + value = ('''"'%s'"''' % sqParsedParam.group(3)) + length = len(sqParsedParam.group(1)) + + else: + # There are no more parameters to parse + if ((not paramText) or (not string.strip(paramText))): + return result + raise ParseError, (('invalid parameter: "%s"' % paramText), tag) + + # add the parameter/value pait to the results + result.append((name, value)) + + # remove the found parameter from the paramText + paramText = string.strip(paramText[length:]) + + if paramText: + return apply(parseTagParameters, (paramText, result, tag), parms) + else: + return result diff --git a/ZopeProducts/exUserFolder/exUser.gif b/ZopeProducts/exUserFolder/exUser.gif new file mode 100644 index 00000000..c7f852b0 Binary files /dev/null and b/ZopeProducts/exUserFolder/exUser.gif differ diff --git a/ZopeProducts/exUserFolder/exUserFolder.gif b/ZopeProducts/exUserFolder/exUserFolder.gif new file mode 100644 index 00000000..fea892ae Binary files /dev/null and b/ZopeProducts/exUserFolder/exUserFolder.gif differ diff --git a/ZopeProducts/exUserFolder/exUserFolder.py b/ZopeProducts/exUserFolder/exUserFolder.py new file mode 100644 index 00000000..1c330c96 --- /dev/null +++ b/ZopeProducts/exUserFolder/exUserFolder.py @@ -0,0 +1,1380 @@ +# Zope User Folder for ScoDoc +# Adapte de l'Extensible User Folder +# simplifie pour les besoins de ScoDoc. +# Emmanuel Viennet 2013 + +# +# Extensible User Folder +# +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: exUserFolder.py,v 1.93 2004/11/10 14:15:33 akm Exp $ + +############################################################################## +# +# Zope Public License (ZPL) Version 0.9.4 +# --------------------------------------- +# +# Copyright (c) Digital Creations. All rights reserved. +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the following +# conditions are met: +# +# 1. Redistributions in source code must retain the above +# copyright notice, this list of conditions, and the following +# disclaimer. +# +# 6. Redistributions of any form whatsoever must retain the +# following acknowledgment: +# +# "This product includes software developed by Digital +# Creations for use in the Z Object Publishing Environment +# (http://www.zope.org/)." +# +# Disclaimer +# +# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND +# ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +# SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. +# +############################################################################## + +# Portions Copyright (c) 2002 Nuxeo SARL , +# Copyright (c) 2002 Florent Guillaume . +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import Globals, App.Undo, socket, os, string, sha, random, sys, zLOG + +from Globals import DTMLFile, PersistentMapping +from string import join,strip,split,lower,upper,find + +from OFS.Folder import Folder +from OFS.CopySupport import CopyContainer + +from base64 import decodestring, encodestring +from urllib import quote, unquote + +from Acquisition import aq_base +from AccessControl import ClassSecurityInfo +from AccessControl.Role import RoleManager +from AccessControl.User import BasicUser, BasicUserFolder, readUserAccessFile +from AccessControl.PermissionRole import PermissionRole +from AccessControl.ZopeSecurityPolicy import _noroles +from OFS.DTMLMethod import DTMLMethod +from time import time +from OFS.ObjectManager import REPLACEABLE +from Persistence import Persistent + +from PropertyEditor import * + +from User import User, AnonUser +from UserCache.UserCache import GlobalUserCache, GlobalNegativeUserCache, GlobalAdvancedCookieCache, SessionExpiredException + +from LoginRequiredMessages import LoginRequiredMessages + +from AccessControl import Unauthorized + +class LoginRequired(Exception): + """Login required""" + pass + + +# If there is no NUG Product just define a dummy class +try: + from Products.NuxUserGroups.UserFolderWithGroups import BasicGroupFolderMixin, _marker +except ImportError: + class BasicGroupFolderMixin: + pass + _marker = None + +# Little function to create temp usernames +def createTempName(): + t=time() + t1=time() + t2=time() + t3 = 0.0 + t3 = (t + t1 + t2) / 3 + un = "Anonymous %.0f"%(t3) + return(un) + + +manage_addexUserFolderForm=DTMLFile('dtml/manage_addexUserFolder', globals(), __name__='manage_addexUserFolderForm') + + + +def manage_addexUserFolder(self, authId, propId, memberId, + cookie_mode=0, session_length=0, + not_session_length=0, + sessionTracking=None, idleTimeout=None, + REQUEST={}, groupId=None, cryptoId=None): + """ """ + if hasattr(self.aq_base, 'acl_users'): + return Globals.MessageDialog(self,REQUEST, + title ='Item Exists', + message='This object already contains a User Folder', + action ='%s/manage_main' % REQUEST['URL1']) + ob=exUserFolder(authId, propId, memberId, groupId, cryptoId, cookie_mode, + session_length, sessionTracking, idleTimeout, + not_session_length) + + self._setObject('acl_users', ob, None, None, 0) + self.__allow_groups__=self.acl_users + ob=getattr(self, 'acl_users') + ob.postInitialisation(REQUEST) + + if REQUEST: + return self.manage_main(self, REQUEST) + return '' + +# +# Module level caches +# +XUFUserCache=GlobalUserCache() +XUFNotUserCache=GlobalNegativeUserCache() +XUFCookieCache=GlobalAdvancedCookieCache() + +class exUserFolder(Folder,BasicUserFolder,BasicGroupFolderMixin, + CopyContainer): + """ """ + + # HACK! We use this meta_type internally so we can be pasted into + # the root. We registered with 'exUserFolder' meta_type however, so + # our constructors work. + meta_type='User Folder' + id ='acl_users' + title ='Extensible User Folder' + icon ='misc_/exUserFolder/exUserFolder.gif' + + isPrincipiaFolderish=1 + isAUserFolder=1 + __allow_access_to_unprotected_subobjects__=1 + authSources={} + propSources={} + cryptoSources={} + membershipSources={} + groupSources={} # UNUSED by ScoDoc + + manage_options=( + {'label':'Users', 'action':'manage_main'}, + {'label':'Groups', 'action':'manage_userGroups'}, + {'label':'Parameters', 'action':'manage_editexUserFolderForm'}, + {'label':'Authentication Source','action':'manage_editAuthSourceForm'}, + {'label':'Properties Source','action':'manage_editPropSourceForm'}, + {'label':'Membership Source', 'action':'manage_editMembershipSourceForm'}, + {'label':'Cache Data', 'action':'manage_showCacheData'}, + {'label':'Security', 'action':'manage_access'}, + {'label':'Contents', 'action':'manage_contents'}, + {'label':'Ownership', 'action':'manage_owner'}, + {'label':'Undo', 'action':'manage_UndoForm'}, + ) + + __ac_permissions__=( + ('View management screens', ('manage','manage_menu','manage_main', + 'manage_copyright', 'manage_tabs', + 'manage_properties', 'manage_UndoForm', + 'manage_edit', 'manage_contents', + 'manage_cutObjects','manage_copyObjects', + 'manage_pasteObjects', + 'manage_renameForm', + 'manage_renameObject', + 'manage_renameObjects', ), + ('Manager',)), + + ('Undo changes', ('manage_undo_transactions',), + ('Manager',)), + + ('Change permissions', ('manage_access',), + ('Manager',)), + + ('Manage users', ('manage_users', 'manage_editUserForm', + 'manage_editUser', 'manage_addUserForm', + 'manage_addUser', 'manage_userActions', + 'userFolderAddGroup', + 'userFolderDelGroups', + 'getGroupNames', + 'getGroupById', + 'manage_userGroups', + 'manage_addGroup', + 'manage_showGroup',), + ('Manager',)), + + ('Change exUser Folders', ('manage_edit',), + ('Manager',)), + + ('View', ('manage_changePassword', + 'manage_forgotPassword', 'docLogin','docLoginRedirect', + 'docLogout', 'logout', 'DialogHeader', + 'DialogFooter', 'manage_signupUser', + 'MessageDialog', 'redirectToLogin','manage_changeProps'), + ('Anonymous', 'Authenticated', 'Manager')), + + ('Manage properties', ('manage_addProperty', + 'manage_editProperties', + 'manage_delProperties', + 'manage_changeProperties', + 'manage_propertiesForm', + 'manage_propertyTypeForm', + 'manage_changePropertyTypes', + ), + ('Manager',)), + ('Access contents information', ('hasProperty', 'propertyIds', + 'propertyValues','propertyItems', + 'getProperty', 'getPropertyType', + 'propertyMap', 'docLogin','docLoginRedirect', + 'DialogHeader', 'DialogFooter', + 'MessageDialog', 'redirectToLogin',), + ('Anonymous', 'Authenticated', 'Manager')), + ) + manage_access=DTMLFile('dtml/access',globals()) + manage_tabs=DTMLFile('common/manage_tabs',globals()) + manage_properties=DTMLFile('dtml/properties', globals()) + manage_main=DTMLFile('dtml/mainUser', globals()) + manage_contents=Folder.manage_main + manage_showCacheData=DTMLFile('dtml/manage_showCacheData', globals()) + + # This is going away soon... + docLoginRedirect=DTMLFile('dtml/docLoginRedirect', globals()) + + # Stupid crap + try: + manage_contents._setName('manage_contents') + except AttributeError: + pass + + + MessageDialog=DTMLFile('common/MessageDialog', globals()) + MessageDialog.__replaceable__ = REPLACEABLE + + manage_addUserForm=DTMLFile('dtml/manage_addUserForm',globals()) + manage_editUserForm=DTMLFile('dtml/manage_editUserForm',globals()) + + DialogHeader__roles__=() + DialogHeader=DTMLFile('common/DialogHeader',globals()) + DialogFooter__roles__=() + DialogFooter=DTMLFile('common/DialogFooter',globals()) + + manage_editAuthSourceForm=DTMLFile('dtml/manage_editAuthSourceForm',globals()) + manage_editPropSourceForm=DTMLFile('dtml/manage_editPropSourceForm',globals()) + manage_editMembershipSourceForm=DTMLFile('dtml/manage_editMembershipSourceForm', globals()) + + manage_addPropertyForm=DTMLFile('dtml/manage_addPropertyForm', globals()) + manage_createPropertyForm=DTMLFile('dtml/manage_createPropertyForm', globals()) + manage_editUserPropertyForm=DTMLFile('dtml/manage_editUserPropertyForm', globals()) + + manage_editexUserFolderForm=DTMLFile('dtml/manage_editexUserFolderForm', globals()) + + manage_userGroups=DTMLFile('dtml/mainGroup',globals()) + + + # Use pages from NUG if it's there, otherwise no group support + try: + manage_addGroup = BasicGroupFolderMixin.manage_addGroup + manage_showGroup = BasicGroupFolderMixin.manage_showGroup + except: + manage_addGroup = None + manage_showGroup = None + + # No more class globals + + # sessionLength=0 # Upgrading users should get no caching. + # notSessionLength=0 # bad cache limit + # cookie_mode=0 + # sessionTracking=None # Or session tracking. + # idleTimeout=0 + + def __init__(self, authId, propId, memberId, groupId, cryptoId, + cookie_mode=0, session_length=0, sessionTracking=None, + idleTimeout=0, not_session_length=0): + self.cookie_mode=cookie_mode + self.sessionLength=session_length + self.notSessionLength=not_session_length + self.sessionTracking=sessionTracking + self.idleTimeout=idleTimeout + + _docLogin=DTMLFile('dtml/docLogin',globals()) + _docLogout=DTMLFile('dtml/docLogout',globals()) + + docLogin=DTMLMethod(__name__='docLogin') + docLogin.manage_edit(data=_docLogin, title='Login Page') + self._setObject('docLogin', docLogin, None, None, 0) + + docLogout=DTMLMethod(__name__='docLogout') + docLogout.manage_edit(data=_docLogout, title='Logout Page') + self._setObject('docLogout', docLogout, None, None, 0) + + postUserCreate=DTMLMethod(__name__='postUserCreate') + postUserCreate.manage_edit(data=_postUserCreate, title='Post User Creation methods') + self._setObject('postUserCreate', postUserCreate, None, None, 0) + + self.manage_addAuthSource=self.authSources[authId].manage_addMethod + self.manage_addPropSource=self.propSources[propId].manage_addMethod + self.manage_addMembershipSource=self.membershipSources[memberId].manage_addMethod + + self.manage_addGroupSource=None # UNUSED by ScoDoc + self.currentGroupsSource=None + + if cryptoId: + self.cryptoId = cryptoId + else: + self.cryptoId = 'Crypt' + + def __setstate__(self, state): + Persistent.__setstate__(self, state) + if not hasattr(self, 'currentGroupSource'): + self.currentGroupSource = None + if not hasattr(self, 'sessionLength'): + self.sessionLength = 0 + if not hasattr(self, 'notSessionLength'): + self.notSessionLength = 0 + if not hasattr(self, 'cookie_mode'): + self.cookie_mode = 0 + if not hasattr(self, 'sessionTraining'): + self.sessionTracking = None + if not hasattr(self, 'idleTimeout'): + self.idleTimeout=0 + + def manage_beforeDelete(self, item, container): + zLOG.LOG("exUserFolder", zLOG.BLATHER, "Attempting to delete an exUserFolder instance") + if item is self: + try: + self.cache_deleteCache() + self.xcache_deleteCache() + zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- Caches deleted") + except: + #pass + zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- Cache deletion failed") + + try: + del container.__allow_groups__ + zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- container.__allow_groups_ deleted") + except: + #pass + zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- container.__allow_groups_ deletion failed") + + + def manage_afterAdd(self, item, container): + zLOG.LOG("exUserFolder", zLOG.BLATHER, "Adding an exUserFolder") + + if item is self: + if hasattr(self, 'aq_base'): self=self.aq_base + container.__allow_groups__=self + + def manage_editPropSource(self, REQUEST): + """ Edit Prop Source """ + if self.currentPropSource: + self.currentPropSource.manage_editPropSource(REQUEST) + return self.manage_main(self, REQUEST) + + def manage_editAuthSource(self, REQUEST): + """ Edit Auth Source """ + self.currentAuthSource.manage_editAuthSource(REQUEST) + return self.manage_main(self, REQUEST) + + def manage_editMembershipSource(self, REQUEST): + """ Edit Membership Source """ + if self.currentMembershipSource: + return self.currentMembershipSource.manage_editMembershipSource(REQUEST) + + def postInitialisation(self, REQUEST): + self.manage_addAuthSource(self=self,REQUEST=REQUEST) + self.manage_addPropSource(self=self,REQUEST=REQUEST) + self.manage_addMembershipSource(self=self,REQUEST=REQUEST) + self.currentGroupSource = None + + def addAuthSource(self, REQUEST={}): + return self.manage_addAuthSourceForm(self, REQUEST) + + def addPropSource(self, REQUEST={}): + return self.manage_addPropSourceForm(self, REQUEST) + + def addMembershipSource(self, REQUEST={}): + return self.manage_editMembershipSourceForm(self, REQUEST) + + def listUserProperties(self, username): + if self.currentPropSource: + return self.currentPropSource.listUserProperties(username=username) + + def getUserProperty(self, username, key): + if self.currentPropSource: + return self.currentPropSource.getUserProperty(key=key, username=username) + + def reqattr(self, request, attr, default=None): + try: return request[attr] + except: return default + + def getAuthFailedMessage(self, code): + """ Return a code """ + if LoginRequiredMessages.has_key(code): + return LoginRequiredMessages[code] + return 'Login Required' + + # Called when we are deleted + def cache_deleteCache(self): + pp = string.join(self.getPhysicalPath(), '/') + XUFUserCache.deleteCache(pp) + + def cache_addToCache(self, username, password, user): + if not self.sessionLength: + return + # fix by emmanuel + if username == self._emergency_user.getUserName(): + return + # /fix + pp = string.join(self.getPhysicalPath(), '/') + x = XUFUserCache.getCache(pp) + if not x: + x = XUFUserCache.createCache(pp, self.sessionLength) + x.addToCache(username, password, user) + + def cache_getUser(self, username, password, checkpassword=1): + if not self.sessionLength: + return None + pp = string.join(self.getPhysicalPath(), '/') + x = XUFUserCache.getCache(pp) + if not x: + return None + u = x.getUser(self, username, password, checkpassword) + if u is not None: + u = u.__of__(self) + return u + + def cache_removeUser(self, username): + if not self.sessionLength: + return + pp = string.join(self.getPhysicalPath(), '/') + x = XUFUserCache.getCache(pp) + if x: + x.removeUser(username) + + def cache_getCacheStats(self): + pp = string.join(self.getPhysicalPath(), '/') + x = XUFUserCache.getCache(pp) + if not x: + x = XUFUserCache.createCache(pp, self.sessionLength) + if x: + return x.getCacheStats() + + def cache_getCurrentUsers(self): + pp = string.join(self.getPhysicalPath(), '/') + x = XUFUserCache.getCache(pp) + if x: + return x.getCurrentUsers(self) + + # negative cache functions + def xcache_deleteCache(self): + pp = string.join(self.getPhysicalPath(), '/') + XUFNotUserCache.deleteCache(pp) + + def xcache_addToCache(self, username): + if not self.notSessionLength: + return + pp = string.join(self.getPhysicalPath(), '/') + x = XUFNotUserCache.getCache(pp) + if not x: + x = XUFNotUserCache.createCache(pp, self.notSessionLength) + x.addToCache(username) + + def xcache_getUser(self, username): + if not self.notSessionLength: + return None + pp = string.join(self.getPhysicalPath(), '/') + x = XUFNotUserCache.getCache(pp) + if not x: + return None + return x.getUser(username) + + def xcache_removeUser(self, username): + #zLOG.LOG('exUserFolder', zLOG.PANIC, 'xcache_removeUser(%s)' % username) + if not self.notSessionLength: + return + pp = string.join(self.getPhysicalPath(), '/') + x = XUFNotUserCache.getCache(pp) + if x: + #zLOG.LOG('exUserFolder', zLOG.PANIC, 'xcache_removeUser removing') + x.removeUser(username) + + # Cookie Cache Functions + def cache_deleteCookieCache(self): + pp = string.join(self.getPhysicalPath(), '/') + XUFCookieCache.deleteCache(pp) + + def cache_addToCookieCache(self, username, password, key): + pp = string.join(self.getPhysicalPath(), '/') + c = XUFCookieCache.getCache(pp) + if not c: + c = XUFCookieCache.createCache(pp, 86400) + c.addToCache(username, password, key) + + def cache_getCookieCacheUser(self, key): + pp = string.join(self.getPhysicalPath(), '/') + c = XUFCookieCache.getCache(pp) + if not c: + return None + return c.getUser(key) + + def cache_removeCookieCacheUser(self, key): + pp = string.join(self.getPhysicalPath(), '/') + c = XUFCookieCache.getCache(pp) + if c: + c.removeUser(key) + + def manage_editUser(self, username, REQUEST={}): # UNUSED by ScoDoc + """ Edit a User """ + # username=self.reqattr(REQUEST,'username') + password=self.reqattr(REQUEST,'password') + password_confirm=self.reqattr(REQUEST,'password_confirm') + roles=self.reqattr(REQUEST,'roles', []) + groups=self.reqattr(REQUEST, 'groupnames', []) + + if not username: + return self.MessageDialog(self,REQUEST=REQUEST, + title ='Illegal value', + message='A username must be specified', + action ='manage_main') + + if (password or password_confirm) and (password != password_confirm): + return self.MessageDialog(self,REQUEST=REQUEST, + title ='Illegal value', + message='Password and confirmation do not match', + action ='manage_main') + + self._doChangeUser(username, password, roles, domains='', groups=groups, REQUEST=REQUEST) + + return self.MessageDialog(self,REQUEST=REQUEST, + title = 'User Updated', + message= 'User %s was updated.'%(username), + action = 'manage_main') + + + # Methode special pour ScoDoc: evite le code inutile dans notre contexte + # et accede a la BD via le curseur psycopg2 fourni + # (facilitera la separation de Zope) + def scodoc_editUser(self, cursor, username, password=None, roles=[]): + """Edit a ScoDoc user""" + roles = list(roles) + rolestring= ','.join(roles) + # Don't change passwords if it's null + if password: + secret=self.cryptPassword(username, password) + # Update just the password: + # self.sqlUpdateUserPassword(username=username, password=secret) + cursor.execute("UPDATE sco_users SET passwd=%(secret)s WHERE user_name=%(username)s", + { 'secret':secret, 'username': username } ) + + #self.sqlUpdateUser(username=username, roles=rolestring) + cursor.execute("UPDATE sco_users SET roles=%(rolestring)s WHERE user_name=%(username)s", + { 'rolestring':rolestring, 'username': username } ) + + if hasattr(self.currentAuthSource, '_v_lastUser'): + # Specific for pgAuthSource: + self.currentAuthSource._v_lastUser={} # clear pg user cache + + # We may have updated roles or passwords... flush the user... + self.cache_removeUser(username) + self.xcache_removeUser(username) + + # + # Membership helper + # + def goHome(self, REQUEST, RESPONSE): + """ Go to home directory """ + if self.currentMembershipSource: + self.currentMembershipSource.goHome(REQUEST, RESPONSE) + + + # + # Membership method of changing user properties + # + + def manage_changeProps(self, REQUEST): + """ Change Properties """ + if self.currentMembershipSource: + return self.currentMembershipSource.changeProperties(REQUEST) + else: + + return self.MessageDialog(self,REQUEST, + title = 'This is a test', + message= 'This was a test', + action = '..') + + + # + # Membership method of adding a new user. + # If everything goes well the membership plugin calls manage_addUser() + # + + def manage_signupUser(self, REQUEST): + """ Signup a new user """ + """ This is seperate so you can add users using the normal """ + """ interface w/o going through membership policy """ + + username=self.reqattr(REQUEST,'username') + roles=self.reqattr(REQUEST,'roles') + + if not username: + return self.MessageDialog(self,REQUEST=REQUEST, + title ='Illegal value', + message='A username must be specified', + action ='manage_main') + + if (self.getUser(username) or + (self._emergency_user and + username == self._emergency_user.getUserName())): + return self.MessageDialog(self,REQUEST=REQUEST, + title ='Illegal value', + message='A user with the specified name already exists', + action ='manage_main') + + if self.currentMembershipSource: + return self.currentMembershipSource.createUser(REQUEST) + + # + # Membership method of changing passwords + # + def manage_changePassword(self, REQUEST): + """ Change a password """ + if self.currentMembershipSource: + return self.currentMembershipSource.changePassword(REQUEST) + + # + # User says they can't remember their password + # + def manage_forgotPassword(self, REQUEST): + """ So something about forgetting your password """ + if self.currentMembershipSource: + return self.currentMembershipSource.forgotPassword(REQUEST) + + def __creatable_by_emergency_user__(self): return 1 + + def manage_addUser(self, REQUEST): + """ Add a New User """ + username=self.reqattr(REQUEST,'username') + password=self.reqattr(REQUEST,'password') + password_confirm=self.reqattr(REQUEST,'password_confirm') + roles=self.reqattr(REQUEST,'roles') + groups=self.reqattr(REQUEST, 'groupnames', []) + + if not username: + return self.MessageDialog(self,REQUEST=REQUEST, + title ='Illegal value', + message='A username must be specified', + action ='manage_main') + + if not password or not password_confirm: + return self.MessageDialog(self,REQUEST=REQUEST, + title ='Illegal value', + message='Password and confirmation must be specified', + action ='manage_main') + + if (self.getUser(username) or + (self._emergency_user and + username == self._emergency_user.getUserName())): + return self.MessageDialog(self,REQUEST=REQUEST, + title ='Illegal value', + message='A user with the specified name already exists', + action ='manage_main') + + if (password or password_confirm) and (password != password_confirm): + return self.MessageDialog(self,REQUEST=REQUEST, + title ='Illegal value', + message='Password and confirmation do not match', + action ='manage_main') + + self._doAddUser(username, password, roles, domains='', groups=groups, REQUEST=REQUEST) + # + # Explicitly check our contents, do not just acquire postUserCreate + # + if 'postUserCreate' in self.objectIds(): + self.postUserCreate(self, REQUEST) + + return self.MessageDialog(self,REQUEST=REQUEST, + title = 'User Created', + message= 'User %s was created.'%(username), + action = 'manage_main') + + def _doAddUser(self, name, password, roles, domains='', groups=(), **kw): + """ For programatically adding simple users """ + self.currentAuthSource.createUser(name, password, roles) + if self.currentPropSource: + # copy items not in kw from REQUEST + REQUEST = kw.get('REQUEST', self.REQUEST) + map(kw.setdefault, REQUEST.keys(), REQUEST.values()) + self.currentPropSource.createUser(name, kw) + + def _doChangeUser(self, name, password, roles, domains='', groups=(), **kw): + self.currentAuthSource.updateUser(name, password, roles) + if self.currentPropSource: + # copy items not in kw from REQUEST + REQUEST = kw.get('REQUEST', self.REQUEST) + map(kw.setdefault, REQUEST.keys(), REQUEST.values()) + self.currentPropSource.updateUser(name, kw) + # We may have updated roles or passwords... flush the user... + self.cache_removeUser(name) + self.xcache_removeUser(name) + + def _doDelUsers(self, names): + self.deleteUsers(names) + + def _createInitialUser(self): + if len(self.getUserNames()) <= 1: + info = readUserAccessFile('inituser') + if info: + name, password, domains, remote_user_mode = info + self._doAddUser(name, password, ('Manager',), domains) + + + def getUsers(self): + """Return a list of user objects or [] if no users exist""" + data=[] + try: + items=self.listUsers() + for people in items: + user=User({'name': people['username'], + 'password': people['password'], + 'roles': people['roles'], + 'domains': ''}, + self.currentPropSource, + self.cryptPassword, + self.currentAuthSource, + self.currentGroupSource) + data.append(user) + except: + import traceback + traceback.print_exc() + pass + + return data + + getUsers__roles__=('Anonymous','Authenticated') + + def getUser(self, name): + """Return the named user object or None if no such user exists""" + user = self.cache_getUser(name, '', 0) + #zLOG.LOG('exUserFolder.getUser', zLOG.PANIC, 'cache_getUser(%s)=%s' % (name,user)) + if user: + return user + try: + items=self.listOneUser(name) + #zLOG.LOG('exUserFolder.getUser', zLOG.PANIC, 'listOneUser=%s' % items) + except: + zLOG.LOG("exUserFolder", zLOG.ERROR, + "error trying to list user %s" % name, + '', + sys.exc_info()) + return None + + if not items: + return None + + for people in items: + user = User({'name': people['username'], + 'password':people['password'], + 'roles': people['roles'], + 'domains': ''}, + self.currentPropSource, + self.cryptPassword, + self.currentAuthSource, + self.currentGroupSource) + return user + return None + + def manage_userActions(self, submit=None, userids=None, REQUEST={}): + """ Do things to users """ + if submit==' Add ': + if hasattr(self.currentAuthSource,'manage_addUserForm'): + return self.currentAuthSource.manage_addUserForm(self, REQUEST) + else: + return self.manage_addUserForm(self, REQUEST) + if submit==' Delete ': + self.deleteUsers(userids) + return self.MessageDialog(self,REQUEST=REQUEST, + title ='Users Deleted', + message='Selected Users have been deleted', + action =REQUEST['URL1']+'/manage_main', + target ='manage_main') + + if REQUEST: + return self.manage_main(self,REQUEST) + return '' + + def identify(self, auth): + # Identify the username and password. This is where new modes should + # be called from, and if pluggable modes ever take shape, here ya go! + + if self.cookie_mode and not auth: + # The identify signature does not include the request, sadly. + # I think that's dumb. + request = self.REQUEST + response = request.RESPONSE + + if request.has_key('__ac_name') and request.has_key('__ac_password'): + return request['__ac_name'], request['__ac_password'] + elif request.has_key('__ac') and self.cookie_mode == 1: + return self.decodeBasicCookie(request, response) + elif request.has_key('__aca') and self.cookie_mode == 2: + return self.decodeAdvancedCookie(request, response) + + if auth and lower(auth[:6]) == 'basic ': + return tuple(split(decodestring(split(auth)[-1]), ':', 1)) + + return None, None + + def decodeUserCookie(self, request, response): + return self.identify('') + + def validate(self, request, auth='', roles=_noroles): + """ + Perform identification, authentication, and authorization. + """ + # Called at each web request + #zLOG.LOG('exUserFolder', zLOG.PANIC, 'validate') + v = request['PUBLISHED'] + a, c, n, v = self._getobcontext(v, request) + + name, password = self.identify(auth) # decode cookie, and raises LoginRequired if no ident info + # password is the cleartext passwd + # zLOG.LOG('exUserFolder', zLOG.DEBUG, 'identify returned %s, %s' % (name, password)) + + response = request.RESPONSE + if name is not None: + try: + xcached_user = self.xcache_getUser(name) + #zLOG.LOG('exUserFolder.validate', zLOG.PANIC, 'xcached_user=%s' % xcached_user) + if xcached_user: + #zLOG.LOG('exUserFolder.validate', zLOG.PANIC, 'returning None') + return None + except: + zLOG.LOG('exUserFolder', zLOG.ERROR, + "error while looking up '%s' on the xcache" % name, + '', + sys.exc_info()) + + user = self.authenticate(name, password, request) + #zLOG.LOG('exUserFolder.validate', zLOG.PANIC, 'user=%s' % user) + if user is None: + # If it's none, because there's no user by that name, + # don't raise a login, allow it to go higher... + # This kinda breaks for people putting in the wrong username + # when the Folder above uses a different auth method. + # But it doesn't lock Manager users out inside Zope. + # Perhaps this should be a tunable. + + # modified by Emmanuel + try: + lou = self.listOneUser(name) + except: + lou = None + if lou: + self.challenge(request, response, 'login_failed', auth) + return None + self.remember(name, password, request) + self.cache_addToCache(name, password, user) + emergency = self._emergency_user + if emergency and user is emergency: + if self._isTop(): + return emergency.__of__(self) + else: + return None + if self.authorize(user, a, c, n, v, roles): + return user.__of__(self) + if self._isTop() and self.authorize(self._nobody, a, c, n, v, roles): + return self._nobody.__of__(self) + self.challenge(request, response, 'unauthorized') + return None + else: + if self.sessionTracking and self.currentPropSource: + user = self.createAnonymousUser(request, response) + if self.authorize(user, a, c, n, v, roles): + return user.__of__(self) + if self.authorize(self._nobody, a, c, n, v, roles): + if self._isTop(): + return self._nobody.__of__(self) + else: + return None + else: + self.challenge(request, response, None, auth) + return None + + def authenticate(self, name, password, request): + #zLOG.LOG('exUserFolder.authenticate', zLOG.PANIC, '%s %s' % (name, password)) + emergency = self._emergency_user + if emergency and name == emergency.getUserName(): + return emergency + try: + user = self.cache_getUser(name, password) + #zLOG.LOG('exUserFolder.authenticate', zLOG.PANIC, 'cache_getUser=%s' % user) + if user: + return user + except SessionExpiredException: + if self.idleTimeout: + self.logout(request) + self.challenge(request, request.RESPONSE, 'session_expired') + return None + user = self.getUser(name) + #zLOG.LOG('exUserFolder.authenticate', zLOG.PANIC, 'getUser=%s' % user) + if user is not None: + if user.authenticate(self.currentAuthSource.listOneUser, + password, + request, + self.currentAuthSource.remoteAuthMethod): + return user + return None + + def challenge(self, request, response, reason_code='unauthorized', + auth=''): + # Give whatever mode we're in a chance to challenge the validation + # failure. We do this to preserve LoginRequired behavior. The + # other thing we could do is let the None propagate on up and patch + # the request's unauthorized method to + + if self.cookie_mode and not auth: + zLOG.LOG('exUserFolder', zLOG.DEBUG, 'raising LoginRequired for %s' % reason_code) + if reason_code == 'login_failed': + response.expireCookie('__ac', path='/') + response.expireCookie('__aca', path='/') + if reason_code: + request.set('authFailedCode', reason_code) + raise LoginRequired(self.docLogin(self, request)) + else: + zLOG.LOG('exUserFolder', zLOG.DEBUG, 'not raising LoginRequired for %s' % reason_code) + + def remember(self, name, password, request): + response = request.RESPONSE + if self.cookie_mode == 1: + self.setBasicCookie(name, password, request, response) + elif self.cookie_mode == 2: + self.setAdvancedCookie(name, password, request, response) + + if self.cookie_mode: + try: + del request.form['__ac_name'] + del request.form['__ac_password'] + except KeyError: + pass + + def makeRedirectPath(self): + REQUEST=self.REQUEST + if not REQUEST.has_key('destination'): + script=REQUEST['SCRIPT_NAME'] + pathinfo=REQUEST['PATH_INFO'] + redirectstring=script+pathinfo + if REQUEST.has_key('QUERY_STRING'): + querystring='?'+quote(REQUEST['QUERY_STRING']) + redirectstring=redirectstring+querystring + + REQUEST['destination']=redirectstring + + def redirectToLogin(self, REQUEST): + """ Allow methods to call from Web """ + script='' + pathinfo='' + querystring='' + redirectstring='' + authFailedCode='' + + if not REQUEST.has_key('destination'): + if self.currentMembershipSource: + redirectstring = self.currentMembershipSource.getLoginDestination(REQUEST) + else: + script=REQUEST['SCRIPT_NAME'] + pathinfo=REQUEST['PATH_INFO'] + redirectstring=script+pathinfo + if REQUEST.has_key('QUERY_STRING'): + querystring='?'+REQUEST['QUERY_STRING'] + redirectstring=redirectstring+querystring + + REQUEST['destination']=redirectstring + + + if REQUEST.has_key('authFailedCode'): + authFailedCode='&authFailedCode='+REQUEST['authFailedCode'] + + + + if self.currentMembershipSource and self.currentMembershipSource.loginPage: + try: + REQUEST.RESPONSE.redirect('%s/%s?destination=%s%s'%(self.currentMembershipSource.baseURL, self.currentMembershipSource.loginPage,REQUEST['destination'],authFailedCode)) + return + except: + pass + return self.docLogin(self,REQUEST) + + def decodeBasicCookie(self, request, response): + c=request['__ac'] + c=unquote(c) + try: + c=decodestring(c) + except: + response.expireCookie('__ac', path='/') + raise LoginRequired(self.docLogin(self, request)) + + name,password=tuple(split(c, ':', 1)) + return name, password + + def decodeAdvancedCookie(self, request, response): + c = '' + try: + c = request['__aca'] + c = unquote(c) + except: + response.expireCookie('__aca', path='/') + response.expireCookie('__ac', path='/') # Precaution + response.flush() + raise LoginRequired(self.docLogin(self, request)) + + u = self.cache_getCookieCacheUser(c) + if u: + return u + + response.expireCookie('__aca', path='/') + response.expireCookie('__ac', path='/') # Precaution + response.flush() + raise LoginRequired(self.docLogin(self, request)) + + def setBasicCookie(self, name, password, request, response): + token='%s:%s' % (name, password) + token=encodestring(token) + token=quote(token) + response.setCookie('__ac', token, path='/') + request['__ac']=token + + def setAdvancedCookie(self, name, password, request, response): + xufid = self._p_oid + hash = encodestring(sha.new('%s%s%f%f%s'%( + name, password, time(), random.random(), str(request))).digest()) + token=quote(hash) + response.setCookie('__aca', token, path='/') + response.flush() + request['__aca']=token + self.cache_addToCookieCache(name, password, hash) + + def setAnonCookie(self, name, request, resp): + token='%s:%s' % (name, '') + token=encodestring(token) + token=quote(token) + resp.setCookie('__ac', token, path='/') + request['__ac']=token + + def createAnonymousUser(self, request, resp): + aName=createTempName() + bogusREQUEST={} + bogusREQUEST['user_realname']='Guest User' + self.currentPropSource.createUser(aName, bogusREQUEST) + ob = AnonUser(aName, [], self.currentPropSource) + ob = ob.__of__(self) + self.cache_addToCache(aName, '', ob) + self.setAnonCookie(aName, request, resp) + return ob + + def manage_edit(self, cookie_mode, session_length, sessionTracking=None, + idleTimeout=0, not_session_length=0, + title=None, + REQUEST=None): + """Change properties""" + + self.cookie_mode=cookie_mode + self.sessionLength=session_length + self.notSessionLength=not_session_length + self.sessionTracking=sessionTracking + self.idleTimeout=idleTimeout + if title: + self.title = title + + if REQUEST: + return self.MessageDialog(self,REQUEST=REQUEST, + title ='exUserFolder Changed', + message='exUserFolder properties have been updated', + action =REQUEST['URL1']+'/manage_main', + target ='manage_main') + + def logout(self, REQUEST): + """Logout""" + try: + self.cache_removeUser(REQUEST['AUTHENTICATED_USER'].getUserName()) + except: + pass + + REQUEST['RESPONSE'].expireCookie('__ac', path='/') + REQUEST.cookies['__ac']='' + try: + acc = REQUEST['__aca'] + self.cache_removeCookieCacheUser(acc) + REQUEST.cookies['__aca']='' + except: + pass + REQUEST['RESPONSE'].expireCookie('__aca', path='/') + + + + return self.docLogout(self, REQUEST) + + # + # Methods to be supplied by Auth Source + # + def deleteUsers(self, userids): + self.currentAuthSource.deleteUsers(userids) + + # Comment out to use Andreas' pgSchema + if self.currentPropSource: + self.currentPropSource.deleteUsers(userids) + + if self.currentGroupSource: + self.currentGroupSource.deleteUsers(userids) + + + def listUsers(self): + return self.currentAuthSource.listUsers() + + def user_names(self): + return self.currentAuthSource.listUserNames() + + def getUserNames(self): + return self.currentAuthSource.listUserNames() + + def listOneUser(self,username): + return self.currentAuthSource.listOneUser(username) + + def cryptPassword(self, username, password): + if hasattr(aq_base(self.currentAuthSource), 'cryptPassword'): + return self.currentAuthSource.cryptPassword(username, password) + + if hasattr(self, 'cryptoId'): + return self.cryptoSources[self.cryptoId].plugin(self, username, password) + return self.cryptoSources['Crypt'].plugin(self, username, password) + + def PropertyEditor(self): + """ """ + if self.REQUEST.has_key(self.REQUEST['propName']): + return PropertyEditor.EditMethods[self.REQUEST['propType']](self.REQUEST['propName'], self.REQUEST[self.REQUEST['propName']]) + return PropertyEditor.EditMethods[self.REQUEST['propType']](self.REQUEST['propName'], None) + + def PropertyView(self): + """ """ + if self.REQUEST.has_key(self.REQUEST['propName']): + return PropertyEditor.ViewMethods[self.REQUEST['propType']](self.REQUEST['propName'], self.REQUEST[self.REQUEST['propName']]) + return PropertyEditor.ViewMethods[self.REQUEST['propType']](self.REQUEST['propName'], None) + + def manage_addUserProperty(self, username, propName, propValue, REQUEST): + """ add a new property """ + self.currentPropSource.setUserProperty(propName, username, propValue) + if hasattr(self.currentAuthSource,'manage_editUserForm'): + return self.currentAuthSource.manage_editUserForm(self, REQUEST) + else: + return self.manage_editUserForm(self,REQUEST) + + def getUserCacheStats(self): + """ Stats """ + if self.sessionLength: + if self.cache_getCacheStats()['attempts']: + return self.cache_getCacheStats() + return None + + def getUserCacheUsers(self): + """ Current Users """ + if self.sessionLength: + return self.cache_getCurrentUsers() + return None + + def userFolderAddGroup(self, groupname, title='', **kw): + """Creates a group""" + if self.currentGroupSource: + apply(self.currentGroupSource.addGroup, (groupname, title), kw) + + def userFolderDelGroups(self, groupnames): + """Deletes groups""" + if self.currentGroupSource: + for groupname in groupnames: + self.currentGroupSource.delGroup(groupname) + + def getGroupNames(self): + """Returns a list of group names""" + if self.currentGroupSource: + return self.currentGroupSource.listGroups() + else: + return [] + + + def getGroupById(self, groupname, default=_marker): + """Returns the given group""" + if self.currentGroupSource: + group = self.currentGroupSource.getGroup(groupname, default) + if group: + return group.__of__(self) + else: + return None + + def setUsersOfGroup(self, usernames, groupname): + """Sets the users of the group""" + if self.currentGroupSource: + return self.currentGroupSource.setUsersOfGroup(usernames, groupname) + + def addUsersToGroup(self, usernames, groupname): + """Adds users to a group""" + if self.currentGroupSource: + return self.currentGroupSource.addUsersToGroup(usernames, groupname) + + def delUsersFromGroup(self, usernames, groupname): + """Deletes users from a group""" + if self.currentGroupSource: + return self.currentGroupSource.delUsersFromGroup(usernames, groupname) + + def setGroupsOfUser(self, groupnames, username): + """Sets the groups of a user""" + if self.currentGroupSource: + return self.currentGroupSource.setGroupsOfUser(groupnames, username) + + def addGroupsOfUser(self, groupnames, username): + """Add groups to a user""" + if self.currentGroupSource: + return self.currentGroupSource.addGroupsToUser(groupnames, username) + + def delGroupsOfUser(self, groupnames, username): + """Deletes groups from a user""" + if self.currentGroupSource: + return self.currentGroupSource.delGroupsFromUser(groupnames, username) + + # We lie. + def hasUsers(self): + return 1 + + +def doAuthSourceForm(self,authId): + """ la de da """ + return exUserFolder.authSources[authId].manage_addForm + +def doPropSourceForm(self,propId): + """ la de da """ + return exUserFolder.propSources[propId].manage_addForm + +def doMembershipSourceForm(self, memberId): + """ doot de doo """ + return exUserFolder.membershipSources[memberId].manage_addForm + +#def doGroupSourceForm(self,groupId): +# """ la de da """ +# return exUserFolder.groupSources[groupId].manage_addForm + +def getAuthSources(self): + """ Hrm I need a docstring """ + l=[] + for o in exUserFolder.authSources.keys(): + l.append( + exUserFolder.authSources[o] + ) + return l + +def getPropSources(self): + """ Hrm I need a docstring """ + l=[] + for o in exUserFolder.propSources.keys(): + l.append( + exUserFolder.propSources[o] + ) + return l + +def getMembershipSources(self): + """ Hrm I need a docstring """ + l=[] + for o in exUserFolder.membershipSources.keys(): + l.append( + exUserFolder.membershipSources[o] + ) + return l + +def getGroupSources(self): + """ Hrm I need a docstring """ + return [] # UNUSED by ScoDoc: empty + +def getCryptoSources(self): + """ Doc String """ + l = [] + for o in exUserFolder.cryptoSources.keys(): + l.append( + exUserFolder.cryptoSources[o] + ) + return l + +def MailHostIDs(self): + """Find SQL database connections in the current folder and above + + This function return a list of ids. + """ + return [] # UNUSED BY SCODOC + +from types import ListType, IntType, LongType, FloatType, NoneType, DictType, StringType + +def getVariableType(self, o): + + if type(o) == ListType: + return 'List' + if type(o) == IntType: + return 'Int' + if type(o) == LongType: + return 'Long' + if type(o) == FloatType: + return 'Float' + if type(o) == NoneType: + return 'None' + if type(o) == DictType: + return 'Dict' + if type(o) == StringType: + return 'String' + return 'Unknown or Restricted' + +_postUserCreate=''' + +Replace this method with whatever you want to do +when a user is created, you can use a Python Script, +or External Method, or keep it as a DTML Method if you +want to + +''' diff --git a/ZopeProducts/exUserFolder/exUserFolderPlugin.gif b/ZopeProducts/exUserFolder/exUserFolderPlugin.gif new file mode 100644 index 00000000..dd8e2c9f Binary files /dev/null and b/ZopeProducts/exUserFolder/exUserFolderPlugin.gif differ diff --git a/ZopeProducts/exUserFolder/nullPlugin/__init__.py b/ZopeProducts/exUserFolder/nullPlugin/__init__.py new file mode 100644 index 00000000..b0d9e576 --- /dev/null +++ b/ZopeProducts/exUserFolder/nullPlugin/__init__.py @@ -0,0 +1,2 @@ +# $Id: __init__.py,v 1.4 2004/11/10 14:15:57 akm Exp $ +import nullPlugin diff --git a/ZopeProducts/exUserFolder/nullPlugin/nullPlugin.py b/ZopeProducts/exUserFolder/nullPlugin/nullPlugin.py new file mode 100644 index 00000000..dd9cd526 --- /dev/null +++ b/ZopeProducts/exUserFolder/nullPlugin/nullPlugin.py @@ -0,0 +1,39 @@ +# +# Extensible User Folder +# +# Null Plugin for exUserFolder +# +# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd +# ACN: 082 081 472 ABN: 83 082 081 472 +# All Rights Reserved +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# Author: Andrew Milton +# $Id: nullPlugin.py,v 1.5 2004/11/10 14:15:57 akm Exp $ +import string,Acquisition + +from Globals import HTMLFile, INSTANCE_HOME + +from OFS.Folder import Folder + +from Products.exUserFolder.exUserFolder import exUserFolder +from Products.exUserFolder.Plugins import PluginRegister + +class NullPlugin(Folder): + + def __init__(self): + pass + + def postInitialisation(self, REQUEST): + pass diff --git a/ZopeProducts/exUserFolder/version.txt b/ZopeProducts/exUserFolder/version.txt new file mode 100644 index 00000000..af0accc3 --- /dev/null +++ b/ZopeProducts/exUserFolder/version.txt @@ -0,0 +1 @@ +exUserFolder-0-50-1 diff --git a/__init__.py b/__init__.py new file mode 100644 index 00000000..97c50056 --- /dev/null +++ b/__init__.py @@ -0,0 +1,75 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +from ZScolar import ZScolar, manage_addZScolarForm, manage_addZScolar + +# from ZNotes import ZNotes, manage_addZNotesForm, manage_addZNotes + +from ZScoDoc import ZScoDoc, manage_addZScoDoc + +# from sco_zope import * +# from notes_log import log +# log.set_log_directory( INSTANCE_HOME + '/log' ) + + +__version__ = "1.0.0" + + +def initialize(context): + """initialize the Scolar products""" + # called at each startup (context is a ProductContext instance, basically useless) + + # --- ZScolars + context.registerClass( + ZScolar, + constructors=( + manage_addZScolarForm, # this is called when someone adds the product + manage_addZScolar, + ), + icon="static/icons/sco_icon.png", + ) + + # context.registerHelp() + # context.registerHelpTitle("ZScolar") + + # --- ZScoDoc + context.registerClass( + ZScoDoc, constructors=(manage_addZScoDoc,), icon="static/icons/sco_icon.png" + ) + + # --- ZNotes + # context.registerClass( + # ZNotes, + # constructors = ( + # manage_addZNotesForm, + # manage_addZNotes + # ), + # icon = 'static/icons/notes_icon.png' + # ) + + # context.registerHelp() + # context.registerHelpTitle("ZNotes") diff --git a/bonus_sport.py b/bonus_sport.py new file mode 100644 index 00000000..c002ff59 --- /dev/null +++ b/bonus_sport.py @@ -0,0 +1,422 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +from operator import mul +import pprint + + +def bonus_iutv(notes_sport, coefs, infos=None): + """Calcul bonus modules optionels (sport, culture), règle IUT Villetaneuse + + Les étudiants de l'IUT peuvent suivre des enseignements optionnels + de l'Université Paris 13 (sports, musique, deuxième langue, + culture, etc) non rattachés à une unité d'enseignement. Les points + au-dessus de 10 sur 20 obtenus dans chacune des matières + optionnelles sont cumulés et 5% de ces points cumulés s'ajoutent à + la moyenne générale du semestre déjà obtenue par l'étudiant. + """ + sumc = sum(coefs) # assumes sum. coefs > 0 + note_sport = sum(map(mul, notes_sport, coefs)) / sumc # moyenne pondérée + bonus = sum([(x - 10) / 20.0 for x in notes_sport if x > 10]) + return bonus + + +def bonus_iut_stdenis(notes_sport, coefs, infos=None): + """Semblable à bonus_iutv mais sans coefficients et total limité à 0.5 points. + """ + points = sum([x - 10 for x in notes_sport if x > 10]) # points au dessus de 10 + bonus = points * 0.05 # ou / 20 + return min(bonus, 0.5) # bonus limité à 1/2 point + + +def bonus_colmar(notes_sport, coefs, infos=None): + """Calcul bonus modules optionels (sport, culture), règle IUT Colmar. + + Les étudiants de l'IUT peuvent suivre des enseignements optionnels + de l'U.H.A. (sports, musique, deuxième langue, culture, etc) non + rattachés à une unité d'enseignement. Les points au-dessus de 10 + sur 20 obtenus dans chacune des matières optionnelles sont cumulés + dans la limite de 10 points. 5% de ces points cumulés s'ajoutent à + la moyenne générale du semestre déjà obtenue par l'étudiant. + + """ + # les coefs sont ignorés + points = sum([x - 10 for x in notes_sport if x > 10]) + points = min(10, points) # limite total à 10 + bonus = points / 20.0 # 5% + return bonus + + +def bonus_iutva(notes_sport, coefs, infos=None): + """Calcul bonus modules optionels (sport, culture), règle IUT Ville d'Avray + + Les étudiants de l'IUT peuvent suivre des enseignements optionnels + de l'Université Paris 10 (C2I) non rattachés à une unité d'enseignement. + Si la note est >= 10 et < 12, bonus de 0.1 point + Si la note est >= 12 et < 16, bonus de 0.2 point + Si la note est >= 16, bonus de 0.3 point + Ce bonus s'ajoute à la moyenne générale du semestre déjà obtenue par + l'étudiant. + """ + sumc = sum(coefs) # assumes sum. coefs > 0 + note_sport = sum(map(mul, notes_sport, coefs)) / sumc # moyenne pondérée + if note_sport >= 16.0: + return 0.3 + if note_sport >= 12.0: + return 0.2 + if note_sport >= 10.0: + return 0.1 + return 0 + + +# XXX Inutilisé (mai 2020) ? à confirmer avant suppression XXX +# def bonus_iut1grenoble_v0(notes_sport, coefs, infos=None): +# """Calcul bonus sport IUT Grenoble sur la moyenne générale +# +# La note de sport de nos étudiants va de 0 à 5 points. +# Chaque point correspond à un % qui augmente la moyenne de chaque UE et la moyenne générale. +# Par exemple : note de sport 2/5 : chaque UE sera augmentée de 2%, ainsi que la moyenne générale. +# +# Calcul ici du bonus sur moyenne générale et moyennes d'UE non capitalisées. +# """ +# # les coefs sont ignorés +# # notes de 0 à 5 +# points = sum([x for x in notes_sport]) +# factor = (points / 4.0) / 100.0 +# bonus = infos["moy"] * factor +# # Modifie les moyennes de toutes les UE: +# for ue_id in infos["moy_ues"]: +# ue_status = infos["moy_ues"][ue_id] +# if ue_status["sum_coefs"] > 0: +# # modifie moyenne UE ds semestre courant +# ue_status["cur_moy_ue"] = ue_status["cur_moy_ue"] * (1.0 + factor) +# if not ue_status["is_capitalized"]: +# # si non capitalisee, modifie moyenne prise en compte +# ue_status["moy"] = ue_status["cur_moy_ue"] +# +# # open('/tmp/log','a').write( pprint.pformat(ue_status) + '\n\n' ) +# return bonus + + +def bonus_iut1grenoble_2017(notes_sport, coefs, infos=None): + """Calcul bonus sport IUT Grenoble sur la moyenne générale (version 2017) + + La note de sport de nos étudiants va de 0 à 5 points. + Chaque point correspond à un % qui augmente la moyenne de chaque UE et la moyenne générale. + Par exemple : note de sport 2/5 : la moyenne générale sera augmentée de 2%. + + Calcul ici du bonus sur moyenne générale + """ + # les coefs sont ignorés + # notes de 0 à 5 + points = sum([x for x in notes_sport]) + factor = (points / 4.0) / 100.0 + bonus = infos["moy"] * factor + + return bonus + + +def bonus_lille(notes_sport, coefs, infos=None): + """calcul bonus modules optionels (sport, culture), règle IUT Villeneuve d'Ascq + + Les étudiants de l'IUT peuvent suivre des enseignements optionnels + de l'Université Lille 1 (sports,etc) non rattachés à une unité d'enseignement. Les points + au-dessus de 10 sur 20 obtenus dans chacune des matières + optionnelles sont cumulés et 4% (2% avant aout 2010) de ces points cumulés s'ajoutent à + la moyenne générale du semestre déjà obtenue par l'étudiant. + """ + sumc = sum(coefs) # assumes sum. coefs > 0 + note_sport = sum(map(mul, notes_sport, coefs)) / sumc # moyenne pondérée + if ( + infos["sem"]["date_debut_iso"] > "2010-08-01" + ): # changement de regle en aout 2010. + return sum([(x - 10) / 25.0 for x in notes_sport if x > 10]) + return sum([(x - 10) / 50.0 for x in notes_sport if x > 10]) + + +# Fonction Le Havre, par Dom. Soud. +def bonus_iutlh(notes_sport, coefs, infos=None): + """Calcul bonus sport IUT du Havre sur moyenne générale et UE + + La note de sport de nos étudiants va de 0 à 20 points. + m2=m1*(1+0.005*((10-N1)+(10-N2)) + m2 : Nouvelle moyenne de l'unité d'enseignement si note de sport et/ou de langue supérieure à 10 + m1 : moyenne de l'unité d'enseignement avant bonification + N1 : note de sport si supérieure à 10 + N2 : note de seconde langue si supérieure à 10 + Par exemple : sport 15/20 et langue 12/20 : chaque UE sera multipliée par 1+0.005*7, ainsi que la moyenne générale. + Calcul ici de la moyenne générale et moyennes d'UE non capitalisées. + """ + # les coefs sont ignorés + points = sum([x - 10 for x in notes_sport if x > 10]) + points = min(10, points) # limite total à 10 + factor = 1.0 + (0.005 * points) + # bonus nul puisque les moyennes sont directement modifiées par factor + bonus = 0 + # Modifie la moyenne générale + infos["moy"] = infos["moy"] * factor + # Modifie les moyennes de toutes les UE: + for ue_id in infos["moy_ues"]: + ue_status = infos["moy_ues"][ue_id] + if ue_status["sum_coefs"] > 0: + # modifie moyenne UE ds semestre courant + ue_status["cur_moy_ue"] = ue_status["cur_moy_ue"] * factor + if not ue_status["is_capitalized"]: + # si non capitalisee, modifie moyenne prise en compte + ue_status["moy"] = ue_status["cur_moy_ue"] + + # open('/tmp/log','a').write( pprint.pformat(ue_status) + '\n\n' ) + return bonus + + +# Bonus sport IUT Tours +def bonus_tours(notes_sport, coefs, infos=None): + """Calcul bonus sport & culture IUT Tours sur moyenne generale + + La note de sport & culture de nos etudiants est applique sur la moyenne generale. + """ + return min(1.0, sum(notes_sport)) # bonus maximum de 1 point + + +def bonus_iutr(notes_sport, coefs, infos=None): + """Calcul du bonus , regle de l'IUT de Roanne (contribuée par Raphael C., nov 2012) + + Le bonus est compris entre 0 et 0.35 point. + cette procédure modifie la moyenne de chaque UE capitalisable. + + """ + # modifie les moyennes de toutes les UE: + # le bonus est le minimum entre 0.35 et la somme de toutes les bonifs + bonus = min(0.35, sum([x for x in notes_sport])) + for ue_id in infos["moy_ues"]: + # open('/tmp/log','a').write( ue_id + infos['moy_ues'] + '\n\n' ) + ue_status = infos["moy_ues"][ue_id] + if ue_status["sum_coefs"] > 0: + # modifie moyenne UE dans semestre courant + ue_status["cur_moy_ue"] = ue_status["cur_moy_ue"] + bonus + if not ue_status["is_capitalized"]: + ue_status["moy"] = ue_status["cur_moy_ue"] + return bonus + + +def bonus_iutam(notes_sport, coefs, infos=None): + """Calcul bonus modules optionels (sport), regle IUT d'Amiens. + Les etudiants de l'IUT peuvent suivre des enseignements optionnels. + Si la note est de 10.00 a 10.49 -> 0.50% de la moyenne + Si la note est de 10.50 a 10.99 -> 0.75% + Si la note est de 11.00 a 11.49 -> 1.00% + Si la note est de 11.50 a 11.99 -> 1.25% + Si la note est de 12.00 a 12.49 -> 1.50% + Si la note est de 12.50 a 12.99 -> 1.75% + Si la note est de 13.00 a 13.49 -> 2.00% + Si la note est de 13.50 a 13.99 -> 2.25% + Si la note est de 14.00 a 14.49 -> 2.50% + Si la note est de 14.50 a 14.99 -> 2.75% + Si la note est de 15.00 a 15.49 -> 3.00% + Si la note est de 15.50 a 15.99 -> 3.25% + Si la note est de 16.00 a 16.49 -> 3.50% + Si la note est de 16.50 a 16.99 -> 3.75% + Si la note est de 17.00 a 17.49 -> 4.00% + Si la note est de 17.50 a 17.99 -> 4.25% + Si la note est de 18.00 a 18.49 -> 4.50% + Si la note est de 18.50 a 18.99 -> 4.75% + Si la note est de 19.00 a 20.00 -> 5.00% + Ce bonus s'ajoute a la moyenne generale du semestre de l'etudiant. + """ + # une seule note + note_sport = notes_sport[0] + if note_sport < 10.0: + return 0 + prc = min((int(2 * note_sport - 20.0) + 2) * 0.25, 5) + bonus = infos["moy"] * prc / 100 + return bonus + + +def bonus_saint_etienne(notes_sport, coefs, infos=None): + """IUT de Saint-Etienne (jan 2014) + Nous avons différents types de bonification + bonfication Sport / Associations + coopératives de département / Bureau Des Étudiants + / engagement citoyen / Langues optionnelles + Nous ajoutons sur le bulletin une bonification qui varie entre 0,1 et 0,3 ou 0,35 pour chaque item + la bonification totale ne doit pas excéder les 0,6 point. + Sur le bulletin nous ne mettons pas une note sur 20 mais directement les bonifications. + + + Dans ScoDoc: on a déclarer une UE "sport&culture" dans laquelle on aura des modules + pour chaque activité (Sport, Associations, ...) + avec à chaque fois une note (ScoDoc l'affichera comme une note sur 20, mais en fait ce sera la + valeur de la bonification: entrer 0,1/20 signifiera un bonus de 0,1 point la moyenne générale) + """ + bonus = min(0.6, sum([x for x in notes_sport])) # plafonnement à 0.6 points + + return bonus + + +def bonus_iutTarbes(notes_sport, coefs, infos=None): + """Calcul bonus modules optionnels + (sport, Langues, action sociale, Théâtre), règle IUT Tarbes + Les coefficients ne sont pas pris en compte, + seule la meilleure note est prise en compte + le 1/30ème des points au-dessus de 10 sur 20 est retenu et s'ajoute à + la moyenne générale du semestre déjà obtenue par l'étudiant. + """ + bonus = max([(x - 10) / 30.0 for x in notes_sport if x > 10] or [0.0]) + return bonus + + +def bonus_iutSN(notes_sport, coefs, infos=None): + """Calcul bonus sport IUT Saint-Nazaire sur moyenne générale + + La note de sport de nos étudiants va de 0 à 5 points. + La note de culture idem, + Elles sont cumulables, + Chaque point correspond à un % qui augmente la moyenne générale. + Par exemple : note de sport 2/5 : la moyenne générale sera augmentée de 2%. + + Calcul ici du bonus sur moyenne générale et moyennes d'UE non capitalisées. + """ + # les coefs sont ignorés + # notes de 0 à 5 + points = sum([x for x in notes_sport]) + factor = points / 100.0 + bonus = infos["moy"] * factor + return bonus + + +def bonus_iutBordeaux1(notes_sport, coefs, infos=None): + """Calcul bonus modules optionels (sport, culture), règle IUT Bordeaux 1, sur moyenne générale et UE + + Les étudiants de l'IUT peuvent suivre des enseignements optionnels + de l'Université Bordeaux 1 (sport, théâtre) non rattachés à une unité d'enseignement. + En cas de double activité, c'est la meilleure des 2 notes qui compte. + Chaque point au-dessus de 10 sur 20 obtenus dans cet enseignement correspond à un % + qui augmente la moyenne de chaque UE et la moyenne générale. + Formule : le % = points>moyenne / 2 + Par exemple : sport 13/20 : chaque UE sera multipliée par 1+0,015, ainsi que la moyenne générale. + + Calcul ici du bonus sur moyenne générale et moyennes d'UE non capitalisées. + """ + # open('/tmp/log','a').write( '\n---------------\n' + pprint.pformat(infos) + '\n' ) + # les coefs sont ignorés + # on récupère la note maximum et les points au-dessus de la moyenne + sport = max(notes_sport) + points = max(0, sport - 10) + # on calcule le bonus + factor = (points / 2.0) / 100.0 + bonus = infos["moy"] * factor + # Modifie les moyennes de toutes les UE: + for ue_id in infos["moy_ues"]: + ue_status = infos["moy_ues"][ue_id] + if ue_status["sum_coefs"] > 0: + # modifie moyenne UE ds semestre courant + ue_status["cur_moy_ue"] = ue_status["cur_moy_ue"] * (1.0 + factor) + if not ue_status["is_capitalized"]: + # si non capitalisee, modifie moyenne prise en compte + ue_status["moy"] = ue_status["cur_moy_ue"] + + # open('/tmp/log','a').write( pprint.pformat(ue_status) + '\n\n' ) + return bonus + + +def bonus_iuto(notes_sport, coefs, infos=None): + """Calcul bonus modules optionels (sport, culture), règle IUT Orleans + * Avant aout 2013 + Un bonus de 2,5% de la note de sport est accordé à chaque UE sauf + les UE de Projet et Stages + * Après aout 2013 + Un bonus de 2,5% de la note de sport est accordé à la moyenne générale + """ + sumc = sum(coefs) # assumes sum. coefs > 0 + note_sport = sum(map(mul, notes_sport, coefs)) / sumc # moyenne pondérée + bonus = note_sport * 2.5 / 100 + if ( + infos["sem"]["date_debut_iso"] > "2013-08-01" + ): # changement de regle en aout 2013. + return bonus + coefs = 0 + coefs_total = 0 + for ue_id in infos["moy_ues"]: + ue_status = infos["moy_ues"][ue_id] + coefs_total = coefs_total + ue_status["sum_coefs"] + # Extremement spécifique (et n'est plus utilisé) + if ue_status["ue"]["ue_code"] not in { + "ORA14", + "ORA24", + "ORA34", + "ORA44", + "ORB34", + "ORB44", + "ORD42", + "ORE14", + "ORE25", + "ORN44", + "ORO44", + "ORP44", + "ORV34", + "ORV42", + "ORV43", + }: + if ue_status["sum_coefs"] > 0: + coefs = coefs + ue_status["sum_coefs"] + # modifie moyenne UE ds semestre courant + ue_status["cur_moy_ue"] = ue_status["cur_moy_ue"] + bonus + if not ue_status["is_capitalized"]: + # si non capitalisee, modifie moyenne prise en compte + ue_status["moy"] = ue_status["cur_moy_ue"] + return bonus * coefs / coefs_total + + +def bonus_iutbethune(notes_sport, coefs, infos=None): + """Calcul bonus modules optionels (sport), règle IUT Bethune + + Les points au dessus de la moyenne de 10 apportent un bonus pour le semestre. + Ce bonus est égal au nombre de points divisé par 200 et multiplié par la + moyenne générale du semestre de l'étudiant. + """ + # les coefs sont ignorés + points = sum([x - 10 for x in notes_sport if x > 10]) + points = min(10, points) # limite total à 10 + bonus = int(infos["moy"] * points / 2) / 100.0 # moyenne-semestre x points x 0,5% + return bonus + + +def bonus_demo(notes_sport, coefs, infos=None): + """Fausse fonction "bonus" pour afficher les informations disponibles + et aider les développeurs. + Les informations sont placées dans le fichier /tmp/scodoc_bonus.log + qui est ECRASE à chaque appel. + *** Ne pas utiliser en production !!! *** + """ + f = open("/tmp/scodoc_bonus.log", "w") # mettre 'a' pour ajouter en fin + f.write("\n---------------\n" + pprint.pformat(infos) + "\n") + # Statut de chaque UE + # for ue_id in infos['moy_ues']: + # ue_status = infos['moy_ues'][ue_id] + # #open('/tmp/log','a').write( pprint.pformat(ue_status) + '\n\n' ) + + return 0.0 diff --git a/config/README b/config/README new file mode 100644 index 00000000..7d51341f --- /dev/null +++ b/config/README @@ -0,0 +1,56 @@ + + CONFIGURATION DE SCODOC + ----------------------- + +Emmanuel Viennet, juin 2008, mar 2017 + + + +0) INSTALL de base: +- prerequis: apache2, utilisateur www-data +- detarer ScoDoc.tgz (== Zope + produits + scodoc) + + +1) Creation de la base utilisateurs (initialement vide) + +------- + +Sept 2013: ScoDoc 7 : python2.7, Debian 7, Zope 2.13.21 + + +Pour la construction de Zope: +attention: on veut bénéficier des paquets python Debian ! +donc + +apt-get install python-dev +apt-get install python-virtualenv +apt-get install gcc +virtualenv --system-site-packages /opt/zope213 +cd zope213/ +bin/easy_install -i http://download.zope.org/Zope2/index/2.13.21 Zope2 +# ...long... + +bin/easy_install Products.ZSQLMethods +bin/easy_install ZPsycopgDA +# Avec Debian 8: probleme install de ZPsycopgDA +# essai en le copiant de la version Debian 7. Semble ok. + +# Problemes persistant avec pydot (voir commentaire dans script install) + +# Si besoin, création de l'instance Zope: +bin/mkzopeinstance -d /opt/inst + +(XXX admin / admin) + + +2) Migration bases de données +Sur ScoDoc6: + pg_dumpall > scodoc.dump.txt + + + +passage en UTF-8 +- sources Python +- locale systeme (scrip install) +- creation des bases (users et depts) +- recodage du dump sql diff --git a/config/anonymize_db.py b/config/anonymize_db.py new file mode 100755 index 00000000..dd3911ff --- /dev/null +++ b/config/anonymize_db.py @@ -0,0 +1,151 @@ +#!/opt/zope213/bin/python +# -*- coding: utf-8 -*- +# -*- mode: python -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2019 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + + +"""Anonymize une base de données ScoDoc + +Runned as "www-data" with scodoc and postgresql up. + +E. Viennet, Jan 2019 +""" + +import sys, os, traceback, psycopg2 + +def log(msg): + sys.stdout.flush() + sys.stderr.write(msg+'\n') + sys.stderr.flush() + + +# --- Fonctions d'Anonymisation, en SQL + +anonymize_name = "random_text_md5(8)" +anonymize_date = "'1970-01-01'" +anonymize_question_str = "'?'" +anonymize_null = "NULL" + +# aggregate_length = lambda column, _: 'length({})'.format(column) + + +# --- Champs à anonymiser (cette configuration pourrait être placé dans un fichier séparé +# et le code serait alors générique pour tout base posgresql, aux données de connection +# près) +# On essaie de retirer les données personnelles des étudiants et des entreprises +# L'identité (login) des enseignants n'est pas modifiée +# (on ne sait rien d'autre sur eux dans cette base, et changer le login ets compliqué +# car c'est la clé avec SCOUSERS) +# +ANONYMIZED_FIELDS = { + 'identite.nom' : anonymize_name, + 'identite.prenom' : anonymize_name, + 'identite.date_naissance' : anonymize_date, + 'identite.lieu_naissance' : anonymize_question_str, + 'identite.nationalite' : anonymize_question_str, + 'identite.foto' : anonymize_null, + 'identite.code_nip' : anonymize_null, + 'identite.code_ine' : anonymize_null, + 'identite.nom_usuel' : anonymize_null, + + 'adresse.email' : "'ano@nyme.fr'", + 'adresse.emailperso' : anonymize_null, + 'adresse.domicile' : anonymize_null, + 'adresse.telephone' : anonymize_null, + 'adresse.telephonemobile' : anonymize_null, + 'adresse.fax' : anonymize_null, + + 'billet_absence.description' : anonymize_null, + 'etud_annotations.comment' : anonymize_name, + + 'entreprises.nom' : anonymize_name, + 'entreprises.adresse' : anonymize_null, + 'entreprises.ville' : anonymize_null, + 'entreprises.codepostal' : anonymize_null, + 'entreprises.pays' : anonymize_null, + 'entreprises.contact_origine' : anonymize_null, + 'entreprises.secteur' : anonymize_null, + 'entreprises.note' : anonymize_null, + 'entreprises.privee' : anonymize_null, + 'entreprises.localisation' : anonymize_null, + + 'entreprise_correspondant.nom' : anonymize_name, + 'entreprise_correspondant.prenom' : anonymize_name, + 'entreprise_correspondant.phone1' : anonymize_null, + 'entreprise_correspondant.phone2' : anonymize_null, + 'entreprise_correspondant.mobile' : anonymize_null, + 'entreprise_correspondant.mail1' : anonymize_null, + 'entreprise_correspondant.mail2' : anonymize_null, + 'entreprise_correspondant.note' : anonymize_null, + 'entreprise_correspondant.fax' : anonymize_null, + + 'entreprise_contact.description' : anonymize_null, + 'entreprise_contact.enseignant' : anonymize_null, + + 'notes_appreciations.comment' : anonymize_name, + } + +def anonymize_column(cursor, tablecolumn): + """Anonymise une colonne + tablecolumn est de la forme nom_de_table.nom_de_colonne, par exemple "identite.nom" + key_name est le nom de la colonne (clé) à utiliser pour certains remplacements + (cette clé doit être anonyme et unique). Par exemple, un nom propre pourrait être + remplacé par nom_valeur_de_la_clé. + """ + table, column = tablecolumn.split('.') + anonymization = ANONYMIZED_FIELDS[ tablecolumn ] + log('processing {}'.format(tablecolumn)) + cursor.execute("UPDATE {table} SET {column} = {value};".format( + table=table, + column=column, + value=anonymization(column, key_name) if callable(anonymization) else anonymization + )) + +def anonymize_db(cursor): + """Traite, une à une, les colonnes indiquées dans ANONYMIZED_FIELDS + """ + for tablecolumn in ANONYMIZED_FIELDS: + anonymize_column(cursor, tablecolumn) + + +dbname = sys.argv[1] + +log('\nAnonymizing database %s' % dbname) +cnx_string = 'dbname=' + dbname +try: + cnx = psycopg2.connect( cnx_string ) +except: + log("\n*** Error: can't connect to database %s ***\n" % dbname) + log('connexion string was "%s"' % cnx_string) + traceback.print_exc() + +cnx.set_session(autocommit=False) +cursor = cnx.cursor() + +anonymize_db(cursor) + +cnx.commit() +cnx.close() diff --git a/config/config.sh b/config/config.sh new file mode 100644 index 00000000..4b8368b8 --- /dev/null +++ b/config/config.sh @@ -0,0 +1,61 @@ + +# Version majeure de Debian (..., 9, 10) +debian_version=$(cat /etc/debian_version) +debian_version=${debian_version%%.*} + + +# Fix path +export PATH="${PATH}":/usr/sbin:/sbin + +# ScoDoc: environment variables +umask 0022 + +export SCODOC_DIR=${PWD%/*} +# normalement: /opt/scodoc/Products/ScoDoc +export SCODOC_VAR_DIR=$(realpath "$SCODOC_DIR/../../var/scodoc") +# = /opt/scodoc/var/scodoc +export SCODOC_VERSION_DIR="${SCODOC_VAR_DIR}/config/version" +export SCODOC_LOGOS_DIR="${SCODOC_VAR_DIR}/config/logos" +# Postgresql superuser: +export POSTGRES_SUPERUSER=postgres + +# Postgresql normal user: (by default, same a zope==www-data) +# IMPORTANT: must match SCO_DEFAULT_SQL_USER defined in sco_utils.py +export POSTGRES_USER=www-data + +# psql command: if various versions installed, force the one we want: +if [ ${debian_version} = "10" ] +then + PSQL=/usr/lib/postgresql/11/bin/psql +elif [ ${debian_version} = "9" ] +then + PSQL=/usr/lib/postgresql/9.6/bin/psql +elif [ ${debian_version} = "8" ] +then + PSQL=/usr/lib/postgresql/9.4/bin/psql +elif [ ${debian_version} = "7" ] +then + PSQL=/usr/lib/postgresql/9.1/bin/psql +elif [ ${debian_version} = "5" ] + then + PSQL=/usr/lib/postgresql/8.3/bin/psql + elif [ ${debian_version} = "6" ] + then + PSQL=/usr/lib/postgresql/8.4/bin/psql + else + PSQL=/usr/lib/postgresql/8.1/bin/psql +fi + + +# tcp port for SQL server (under Debian 4, 5432 or 5433 for 8.1 if 7.4 also installed !) +# Important note: if changed, you should probably also change it in +# sco_utils.py (SCO_DEFAULT_SQL_PORT). +export POSTGRES_PORT=5432 + +# Utilise par le script de reset du mot de passe: +if [ ${debian_version} -ge "7" ] +then + export ZOPE_VERSION=2.13 +else + export ZOPE_VERSION=2.11.0 +fi diff --git a/config/create_database.sh b/config/create_database.sh new file mode 100755 index 00000000..3485b216 --- /dev/null +++ b/config/create_database.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# Create database for a ScoDoc instance +# This script must be executed as postgres user +# +# $db_name is passed ias an environment variable + +source config.sh +source utils.sh + +echo 'Creating postgresql database' + +# --- +echo 'Creating postgresql database ' $db_name +createdb -E UTF-8 -p $POSTGRES_PORT -O $POSTGRES_USER $db_name + diff --git a/config/create_dept.sh b/config/create_dept.sh new file mode 100755 index 00000000..4b39b628 --- /dev/null +++ b/config/create_dept.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# +# ScoDoc: creation initiale d'un departement +# +# Ce script prend en charge la creation de la base de donnees +# et doit être lancé par l'utilisateur unix root dans le repertoire .../config +# ^^^^^^^^^^^^^^^^^^^^^ +# E. Viennet, Juin 2008 +# + + +source config.sh +source utils.sh + +check_uid_root $0 + + +echo -n "Nom du departement (un mot sans ponctuation, exemple \"Info\"): " +read DEPT + +if [[ ! "$DEPT" =~ ^[A-Za-z0-9]+$ ]] +then + echo 'Nom de departement invalide !' + exit 1 +fi + +export DEPT + +export db_name=SCO$(to_upper "$DEPT") + +cfg_pathname="${SCODOC_VAR_DIR}/config/depts/$DEPT".cfg + +if [ -e $cfg_pathname ] +then + echo 'Erreur: Il existe deja une configuration pour "'$DEPT'"' + exit 1 +fi + +# --- Ensure postgres user www-data exists +init_postgres_user + +# ----------------------- Create database +su -c ./create_database.sh $POSTGRES_SUPERUSER + +# ----------------------- Create tables +# POSTGRES_USER == regular unix user (www-data) +su -c ./initialize_database.sh $POSTGRES_USER + +# ----------------------- Enregistre fichier config +echo "dbname="$db_name > $cfg_pathname + +# ----------------------- Force mise à jour +echo -n "Voulez vous mettre a jour ScoDoc (tres recommande) ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + (cd "$SCODOC_DIR/config"; ./upgrade.sh) +fi + +# ----------------------- +echo +echo " Departement $DEPT cree" +echo +echo " Attention: la base de donnees n'a pas de copies de sauvegarde" +echo +echo " Maintenant, vous pouvez ajouter le departement via l'application web" +echo " en suivant le lien \"Administration de ScoDoc\" sur la page d'accueil." +echo diff --git a/config/create_user_db.sh b/config/create_user_db.sh new file mode 100755 index 00000000..8790cd5d --- /dev/null +++ b/config/create_user_db.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# +# ScoDoc: creation de la base de donnees d'utilisateurs +# +# Ce script prend en charge la creation de la base de donnees +# et doit tre lanc par l'utilisateur unix root dans le repertoire .../config +# ^^^^^^^^^^^^^^^^^^^^^ +# E. Viennet, Juin 2008 +# + +source config.sh +source utils.sh + +check_uid_root $0 + +# --- Ensure postgres user www-data exists +init_postgres_user + +db_name=SCOUSERS + +echo 'Creating postgresql database ' $db_name + +su -c "createdb -E UTF-8 -O $POSTGRES_USER -p $POSTGRES_PORT $db_name" $POSTGRES_SUPERUSER + +echo 'Initializing tables in database ' $db_name +echo su -c "$PSQL -U $POSTGRES_USER -p $POSTGRES_PORT $db_name < $SCODOC_DIR/misc/create_user_table.sql" $POSTGRES_USER +su -c "$PSQL -U $POSTGRES_USER -p $POSTGRES_PORT $db_name < $SCODOC_DIR/misc/create_user_table.sql" $POSTGRES_USER diff --git a/config/default-etapes.txt b/config/default-etapes.txt new file mode 100644 index 00000000..80e037d1 --- /dev/null +++ b/config/default-etapes.txt @@ -0,0 +1,20 @@ +# Etapes Apogee par defaut: a adapter a votre cas +# Les etapes sont d'abord demandees au portail +# et en cas d'echec lues dans ce fichier +# +# E. Viennet, 2007 + +# Format: Departement : Code_etape_apogee : nom de la formation +# Le code departement est celui indiqué par la propriété +# "portal_dept_name" du département. + +# Attention: le codage de ce fichier doit etre le même que +# celui de ScoDoc (utf-8) + +rt : V1RT : DUT Réseaux et Télécommunications 1 +rt : V2RT2 : DUT Réseaux et Télécommunications 2 +rt : V3ASR3 : Licence Pro R&T ASUR (FC) +rt : V3ASR2 : Licence Pro R&T ASUR (Apprentissage) +rt : V3ON : Licence pro. Electronique, Optique et Nanotechnologies +gea: V1GE : DUT Gestion des Entreprises et Administration 1 +gea: V2GE : DUT Gestion des Entreprises et Administration 2 diff --git a/config/delete_dept.sh b/config/delete_dept.sh new file mode 100755 index 00000000..9a88a70c --- /dev/null +++ b/config/delete_dept.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# +# ScoDoc: suppression d'un departement +# +# Ce script supprime la base de donnees ScoDoc d'un departement +# *** le departement doit au prealable avoir �t� supprime via l'interface web ! *** +# +# Ne fonctionne que pour les configurations "standards" (dbname=xxx) +# +# Il doit �tre lanc� par l'utilisateur unix root dans le repertoire .../config +# ^^^^^^^^^^^^^^^^^^^^^ +# E. Viennet, Sept 2008 +# + + +source config.sh +source utils.sh + +check_uid_root $0 + +echo +echo "Ce script supprime la base de donnees ScoDoc d'un departement" +echo +echo "Attention: le departement doit au prealable avoir ete supprime via l'interface web !" +echo "faites le AVANT d'executer ce script !!!" +echo +echo -n "Nom du departement a supprimer (un mot sans ponctuation, exemple \"Info\"): " +read DEPT + +if [[ ! "$DEPT" =~ ^[A-Za-z0-9]+$ ]] +then + echo "Nom de departement invalide !" + exit 1 +fi + +export DEPT + +cfg_pathname="${SCODOC_VAR_DIR}/config/depts/$DEPT".cfg + +if [ -e $cfg_pathname ] +then + # arret de ScoDoc + /etc/init.d/scodoc stop + # suppression de la base postgres + db_name=$(cat $cfg_pathname | sed '/^dbname=*/!d; s///;q') + echo "suppression de la base postgres $db_name" + su -c "dropdb $db_name" $POSTGRES_SUPERUSER || terminate "ne peux supprimer base de donnees $db_name" + # suppression du fichier de config + /bin/rm -f $cfg_pathname || terminate "ne peux supprimer $cfg_pathname" + # relance ScoDoc + echo -n "Demarrer le serveur ScoDoc ? (y/n) [n]" + read ans + if [ "$(norm_ans "$ans")" = 'Y' ] + then + /etc/init.d/scodoc start + fi + exit 0 +else + echo 'Erreur: pas de configuration trouvee pour "'$DEPT'"' + exit 1 +fi diff --git a/config/diagnostic.sh b/config/diagnostic.sh new file mode 100755 index 00000000..fc0fb959 --- /dev/null +++ b/config/diagnostic.sh @@ -0,0 +1,256 @@ +#!/bin/bash + +# Rassemble informations sur le systeme et l'installation ScoDoc pour +# faciliter le support a distance. +# +# Avec option: +# -a : sauve aussi les bases de données +# +DEST_ADDRESS=emmanuel.viennet@univ-paris13.fr + +INSTANCE_DIR=/opt/scodoc + +TMP=/tmp/scodoc-$(date +%F-%s) + +DEPTS_TO_SAVE="" +SAVE_USERS=0 +SEND_BY_MAIL=1 + +# ------------------------------------- +# Arguments +# ------------------------------------- + +function join_by { local IFS="$1"; shift; echo "$*"; } + +while getopts ":d:aunh" opt; do + case $opt in + a) + # Liste des noms des departements, a partir des bases SQL SCO* + DEPTS_TO_SAVE=$( (su postgres -c "psql -l") | grep SCO | grep -v SCOUSERS | awk '{ gsub("SCO", "", $1); print $1}' ) + SAVE_USERS=1 + ;; + u) + SAVE_USERS=1 + ;; + n) + SEND_BY_MAIL=0 + ;; + d) + DEPTS_TO_SAVE=$( join_by ' ' $DEPTS_TO_SAVE $OPTARG ) + ;; + h) + echo "Diagnostic installation ScoDoc" + echo "Rassemble informations sur le systeme et l'installation ScoDoc" + echo "Usage: $0 [-h] [-n] [-a] [-u] [-d dept]" + echo " -h cette aide" + echo " -n pas d'envoi par mail" + echo " -a enregistre aussi toutes les bases de donnees" + echo " -u enregistre la base utilisateurs" + echo " -d dept enregistre la base du departement dept" + exit 0 + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + exit 1 + ;; + esac +done + + +# ------------------------------------- +# Configuration +# ------------------------------------- + +# needed for uuencode +if [ ! -e /usr/bin/uuencode ] +then + apt-get install sharutils +fi + +mkdir $TMP + +# Files to copy: +FILES="/etc/hosts /etc/debian_version /etc/apt /etc/apache2" + + +echo "ScoDoc diagnostic: informations about your system will be " +if [ "${SEND_BY_MAIL}" = "1" ] +then + echo "sent to ${DEST_ADDRESS}" + echo -n "and " +fi +echo "left in ${TMP}" + + +# ------------------------------------- +# Logs +# ------------------------------------- + +copy_log() { + if [ -e $1 ] + then + cp $1 $TMP/scodoc_logs/ + fi +} +mkdir $TMP/scodoc_logs/ +copy_log /opt/scodoc/instance/log/event.log +copy_log /opt/scodoc/instance/log/event.log.1 +copy_log /opt/scodoc/instance/log/notes.log +copy_log /opt/scodoc/instance/log/notes.log.1 + + +# ------------------------------------- +# Linux System Configuration +# ------------------------------------- + +iptables -L > $TMP/iptables.out +ip a > $TMP/ifconfig.out +ps auxww > $TMP/ps.out +df -h > $TMP/df.out +dpkg -l > $TMP/dpkg.lst + +(cd /opt/scodoc/instance/Products/ScoDoc; svn status > $TMP/svn.status) +(cd /opt/scodoc/instance/Products/ScoDoc; svn diff > $TMP/svn.diff) + +(cd /opt/scodoc/instance/Products/ScoDoc; svnversion > $TMP/svn.version) +ls -laR /opt/scodoc/instance/Products/ScoDoc > $TMP/ls-laR + + +# ------------------------------------- +# Databases configurations +# ------------------------------------- +(su postgres -c "psql -l") > "${TMP}/psql-l.out" +for dept in "${INSTANCE_DIR}"/var/scodoc/config/depts/*.cfg +do + cnx=$(cat $dept) + (su postgres -c "echo '\dt' | psql -d $cnx") > "${TMP}/psql-$(basename ${dept%%.*}).out" +done + + +# ------------------------------------- +# Other system configuration files +# ------------------------------------- +# copy files: +for f in $FILES +do + cp -R $f $TMP +done + + +# ------------------------------------- +# Optionally save dept(s) database(s) +# ------------------------------------- +DEPTS_TO_SAVE=$(echo ${DEPTS_TO_SAVE} | tr ' ' '\n' | sort | uniq) + +# Dump database of a dept (eg "RT") +function dump_dept_db { + dept=$1 + DB=$2 + echo "Dumping database ${DB}..." + mkdir -p "${TMP}/depts/${dept}" + chmod -R a+wr "${TMP}/depts/" + (su postgres -c "pg_dump --create ${DB}") | gzip > "${TMP}/depts/${dept}/${DB}.dump.gz" + # may add archives ? (but probably too big) +} + +for dept in ${DEPTS_TO_SAVE} +do + dump_dept_db "${dept}" "SCO${dept}" +done + + +# ------------------------------------- +# Optionally saveUSERS db +# ------------------------------------- +if [ "${SAVE_USERS}" = "1" ] +then + dump_dept_db "USERS" "SCOUSERS" +fi + + +# ------------------------------------- +# Archive all stuff to /tmp +# ------------------------------------- + +tar cfz $TMP.tgz $TMP + +echo +echo "Fichier de diagnostic: $TMP.tgz" +echo + +# If no mail, stop here +if [ "${SEND_BY_MAIL}" = "0" ] +then + exit 0 +fi + +# ------------------------------------- +# Send by e-mail +# ------------------------------------- + + +# Code below found on http://www.zedwood.com/article/103/bash-send-mail-with-an-attachment + +#requires: basename,date,md5sum,sed,sendmail,uuencode +function fappend { + echo "$2">>$1; +} +YYYYMMDD=`date +%Y%m%d` + +# CHANGE THESE +TOEMAIL=$DEST_ADDRESS +FREMAIL="scodoc-diagnostic@none.org"; +SUBJECT="ScoDoc diagnostic - $YYYYMMDD"; +MSGBODY="ScoDoc diagnostic sent by diagnostic.sh"; +ATTACHMENT="$TMP.tgz" +MIMETYPE="application/gnutar" #if not sure, use http://www.webmaster-toolkit.com/mime-types.shtml + + +# DON'T CHANGE ANYTHING BELOW +TMP="/tmp/tmpfil_123"$RANDOM; +BOUNDARY=`date +%s|md5sum` +BOUNDARY=${BOUNDARY:0:32} +FILENAME=`basename $ATTACHMENT` + +rm -rf $TMP; +cat $ATTACHMENT|uuencode --base64 $FILENAME>$TMP; +sed -i -e '1,1d' -e '$d' $TMP;#removes first & last lines from $TMP +DATA=`cat $TMP` + +rm -rf $TMP; +fappend $TMP "From: $FREMAIL"; +fappend $TMP "To: $TOEMAIL"; +fappend $TMP "Reply-To: $FREMAIL"; +fappend $TMP "Subject: $SUBJECT"; +fappend $TMP "Content-Type: multipart/mixed; boundary=\""$BOUNDARY"\""; +fappend $TMP ""; +fappend $TMP "This is a MIME formatted message. If you see this text it means that your"; +fappend $TMP "email software does not support MIME formatted messages."; +fappend $TMP ""; +fappend $TMP "--$BOUNDARY"; +fappend $TMP "Content-Type: text/plain; charset=ISO-8859-1; format=flowed"; +fappend $TMP "Content-Transfer-Encoding: 7bit"; +fappend $TMP "Content-Disposition: inline"; +fappend $TMP ""; +fappend $TMP "$MSGBODY"; +fappend $TMP ""; +fappend $TMP ""; +fappend $TMP "--$BOUNDARY"; +fappend $TMP "Content-Type: $MIMETYPE; name=\"$FILENAME\""; +fappend $TMP "Content-Transfer-Encoding: base64"; +fappend $TMP "Content-Disposition: attachment; filename=\"$FILENAME\";"; +fappend $TMP ""; +fappend $TMP "$DATA"; +fappend $TMP ""; +fappend $TMP ""; +fappend $TMP "--$BOUNDARY--"; +fappend $TMP ""; +fappend $TMP ""; +#cat $TMP>out.txt +cat $TMP|sendmail -t -f none@example.com; +rm $TMP; + diff --git a/config/distrib.sh b/config/distrib.sh new file mode 100644 index 00000000..0958fb10 --- /dev/null +++ b/config/distrib.sh @@ -0,0 +1,97 @@ +#!/bin/bash + +# Pense bete pour tout nettoyer avant de faire une distribution... +# +# +# E. Viennet, jul 2008 + +source config.sh +source utils.sh + +if [ "$UID" != "0" ] +then + echo "Erreur: le script $0 doit etre lance par root" + exit 1 +fi + + +echo "Changing to directory " $SCODOC_DIR/config +cd $SCODOC_DIR/config + +echo "Stopping ScoDoc..." +/etc/init.d/scodoc stop + +# DROITS +echo -n "Verification des droits: proprietaire www-data ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo 'changing owner to www-data' + chown -R www-data.www-data .. +fi + +echo -n 'Suppression des backups des sources (*~) ? (y/n) [y] ' +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + /bin/rm -f ../*~ ../*/*~ +fi + + +# SVN +echo -n "svn update ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo 'Updating from SVN...' + (cd ..; svn update) +fi + + +# DEPARTEMENTS (maintenant inutile car dans /var) +echo -n "Supprimer les (anciennes) configs de departements ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo "moving " depts/*.cfg "to /tmp" + mv depts/*.cfg /tmp +fi + +# .../var/ +echo -n "Supprimer et recréer .../var (archives, photos, configs, ...) ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo "moving ../../../var/scodoc to /tmp" + mv ../../../var/scodoc /tmp + mkdir -p ../../../var/scodoc/config/depts + mkdir ../../../var/scodoc/photos + mkdir ../../../var/scodoc/tmp + mkdir ../../../var/scodoc/archives + chown -R www-data.www-data ../../../var/scodoc/ +fi + +# LOGS ZOPE +echo -n "Effacer les logs de Zope et ScoDoc ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + (cd ../../../log/; ./purge) +fi + +# IMAGE Data.fs +echo -n "Recopier le Data.fs original ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo "moving Data.fs to /tmp" + mv ../../../var/Data.fs ../../../var/Data.fs.index /tmp + DATAFS=../../../var/Data.fs.ok-to-distrib-545 + echo "copying $DATAFS to Data.fs" + cp -p $DATAFS ../../../var/Data.fs +fi + +# +echo +echo "OK, vous pouvez archiver la distribution !" +echo diff --git a/config/doc_poursuites_etudes/README.txt b/config/doc_poursuites_etudes/README.txt new file mode 100644 index 00000000..6e2449c6 --- /dev/null +++ b/config/doc_poursuites_etudes/README.txt @@ -0,0 +1,16 @@ +Documents pour l'édition des avis de poursuite d'étude + +Ces documents sont joints au zip renvoyé par la fonction de synthèse des avis de poursuite d'étude: + +- images (logos) +- classe LaTeX +- modele pour frise chronologique en LaTeX + + +Tous les documents et répertoires placés dans le répertoire distrib/ ou local/ seront ajoutés. +Si un document du même nom est présent dans le répertoire local/, c'est lui qui sera +utilisé à la place. +Cela permet des adaptations locales, sans générer de conflits lors des mises à jour de ScoDoc +(pensez à sauvegarder votre répertoire local/) + + diff --git a/config/doc_poursuites_etudes/distrib/README.txt b/config/doc_poursuites_etudes/distrib/README.txt new file mode 100644 index 00000000..ea19fd3d --- /dev/null +++ b/config/doc_poursuites_etudes/distrib/README.txt @@ -0,0 +1,11 @@ + + Avis de poursuites d'étude générés par ScoDoc + + +Pour compiler et générer le document pdf (avec les avis de tous les étudiants): + + pdflatex avis.txt + + + +(si vous souhaitez modifier les fichiers sur le serveur, faites le dans le répertoire ../local/) diff --git a/config/doc_poursuites_etudes/distrib/avis.tex b/config/doc_poursuites_etudes/distrib/avis.tex new file mode 100755 index 00000000..524c590b --- /dev/null +++ b/config/doc_poursuites_etudes/distrib/avis.tex @@ -0,0 +1,29 @@ +%% Original : Cléo BARAS +%% +%% Version 1.0 +%% +%% Ce fichier est distribué par ScoDoc +%% +%% Si vous voulez le modifier sur le serveur, copiez le dans +%% ../local/ +%% et éditez votre copie dans local. +%% + + +\documentclass[12pt]{avisPE} + +\usepackage[T1]{fontenc} +\usepackage[utf8x]{inputenc} +\usepackage{frcursive} +\usepackage{graphics,graphicx} +\usepackage{tikz} +\usepackage{colortbl} +\usepackage[francais]{babel} % Date francisée + + +\begin{document} +\input{modeles/parcourstimeline.tex} + +\input{avis_poursuite} + +\end{document} diff --git a/config/doc_poursuites_etudes/distrib/avisPE.cls b/config/doc_poursuites_etudes/distrib/avisPE.cls new file mode 100755 index 00000000..eacc1b8d --- /dev/null +++ b/config/doc_poursuites_etudes/distrib/avisPE.cls @@ -0,0 +1,126 @@ +%% Original : Cléo BARAS +%% +%% Version 1.0 +%% +%% Ce fichier est distribué par ScoDoc +%% +%% Si vous voulez le modifier sur le serveur, copiez le dans +%% ../local/ +%% et éditez votre copie dans local. +%% + +\NeedsTeXFormat{LaTeX2e} +\ProvidesClass{avisPE} + [2017/05/06 v0.1 Modele avis PE] + +\LoadClass[12pt]{article} +\RequirePackage{ifthen} +\RequirePackage{array} +\RequirePackage[paper=a4paper,textwidth=190mm]{geometry} +\RequirePackage{bookman} +\RequirePackage{xcolor} +\RequirePackage{fontawesome} + + %%%%%%%%%%%%%%%%%%%%%%%%%%% + % Mise en page par défaut % + %%%%%%%%%%%%%%%%%%%%%%%%%%% + +\setlength{\textwidth}{190mm} %% Largeur de la zone texte +\setlength{\textheight}{300mm} %% Hauteur de la zone texte +\setlength{\topmargin}{-25mm} +\setlength{\evensidemargin}{-15mm} +\setlength{\oddsidemargin}{-15mm} + +\definecolor{ugared}{rgb}{0.94,0.02,0.02} +\definecolor{ugadarkgray}{rgb}{0.72,0.72,0.72} +\definecolor{ugagray}{rgb}{0.42,0.42,0.42} +\definecolor{ugablack}{rgb}{0,0,0} + +\pagestyle{empty} % pour ne pas indiquer de numéro de page... + +% On définit les pieds de page comme des \stretch de force 1, +% soit 4 plus fort que celui qui est en bas du chapeau +% (Cf. ci-dessous). De cette façon, un avis qui ne remplit pas toute la +% page a un bel espacement. +\makeatletter +\def\@textbottom{\vspace*{\stretch{1}}} +\makeatother + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Paramètres réglables % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% L'utilisateur peut les redéfinir ceux-ci «\renewcommand» si besoin : +\newcommand{\espaceInterRubrique}{\medskip\vspace{\stretch{0.1}}} + +\newlength{\rubriquedureeparindent} +\setlength{\rubriquedureeparindent}{-16pt} + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Macros pratiques % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\def\er{$^{\hbox{er}}$} +\def\ere{$^{\hbox{ère}}$} +\def\eme{$^{\hbox{ème}}$} + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % L'environnement "rubrique" % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Usage : \begin{rubrique}[Indentation]{Titre} [...] \end{rubrique} +% On met le texte qu'on veut à l'intérieur. + +\newenvironment{nom} % "rubrique" prend un seul argument, le titre +{ +\relax% +\noindent +\medskip\bfseries \LARGE}{% +\normalsize\normalfont +\espaceInterRubrique +} + +\newenvironment{rubrique}[1] % "rubrique" prend un seul argument, le titre +{ +\relax% +\noindent +\tikz{\fill[ugagray] (0,0) rectangle (0.5cm,0.5cm); } +%\faArrowCircleRight +\medskip\hspace{0.2cm}\bfseries \color{ugagray} \Large{#1}\par +\noindent\tikz{\fill[white] (0,0) rectangle (0.5cm,0.5cm); +\draw[ugagray] (0.8cm,1cm)--(19cm,1cm); }\par + \color{ugablack}\normalsize\normalfont}{% le contenu +\espaceInterRubrique +} + +\newenvironment{sousrubrique}[1] % "rubrique" prend un seul argument, le titre +{\espaceInterRubrique\relax% + \color{ugagray}{\faCaretSquareORight~\textbf{\large{#1}}} + \color{ugablack}\normalsize\normalfont\par% + \espaceInterRubrique + }{} + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % L'en-tête de l'avis % + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% en paramètre: le logo +\newenvironment{entete}[1]{% + \def\ligne{\rule[2pt]{24pt}{1pt}} % Ligne séparatrice + \noindent + \begin{minipage}{\linewidth}%\hspace{-2cm}% + \begin{minipage}{5.5cm} + \includegraphics[height=3.5cm,keepaspectratio=true]{#1} + \end{minipage} + \hfill + \begin{minipage}{13cm} + \color{ugagray} +} +{ \color{ugablack} +\relax\vspace*{\fill} +\end{minipage} +\end{minipage} +\vspace{\stretch{0.25}}} diff --git a/config/doc_poursuites_etudes/distrib/make_avis.bat b/config/doc_poursuites_etudes/distrib/make_avis.bat new file mode 100755 index 00000000..ceb8ac31 --- /dev/null +++ b/config/doc_poursuites_etudes/distrib/make_avis.bat @@ -0,0 +1,3 @@ + +echo "Compilation des avis latex" +pdflatex avis.tex diff --git a/config/doc_poursuites_etudes/distrib/modeles/parcourstimeline.tex b/config/doc_poursuites_etudes/distrib/modeles/parcourstimeline.tex new file mode 100755 index 00000000..fec03f65 --- /dev/null +++ b/config/doc_poursuites_etudes/distrib/modeles/parcourstimeline.tex @@ -0,0 +1,67 @@ +%% Original : Cléo BARAS +%% +%% Version 1.0 +%% +%% Ce fichier est distribué par ScoDoc +%% +%% Si vous voulez le modifier sur le serveur, copiez le dans +%% ../local/ +%% et éditez votre copie dans local. +%% + +\definecolor{ugagray}{rgb}{0.42,0.42,0.42} + +% define global counters +\newcounter{yearcount} + + +\newcounter{leftcount} + +% env parcourstimeline (d'après copyright (c) 2016 Jan Küster) +% param 1: année de début +% param 2: année de fin +% param 3: nombre de semestre +% param 4: largeur totale +\newenvironment{parcourstimeline}[4]{ + + % param 1: no + % param 2: Nom + \newcommand{\parcoursevent}[3] { + \pgfmathparse{\largeur*(##1-1)+\largeur/2+ \marge}\let\startexp\pgfmathresult + + \node[draw,color=white,] (Ev##1) at (\startexp,0.7){ + \textcolor{black}{##2} + }; + \node[draw,color=white,] (Ev##1) at (\startexp,-0.6){ + \textcolor{black}{##3} + }; +} + + %------------------------------------------------------------ + % BEGIN + %------------------------------------------------------------ + + \begin{tikzpicture} + + %calc pas = number of years + \pgfmathparse{(#4/#3)}\let\pas\pgfmathresult + \pgfmathparse{\pas*0.9}\let\largeur\pgfmathresult + \pgfmathparse{(#4-\largeur*#3)/2}\let\marge\pgfmathresult + \draw[draw=ugagray,line width=2pt] (0,0) -- (#4,0) ; %the timeline + + %for each year put a horizontal line in place + \setcounter{yearcount}{1} + \whiledo{\value{yearcount} < #3}{ % \fullrange}{ + \draw[fill=white,draw=ugagray, line width=2pt] (\largeur*\value{yearcount}+\marge+0.,0) circle (0.1); + \stepcounter{yearcount} + } + + %start year + \node[draw,color=ugagray,rectangle,line width=2pt,fill=ugagray!20,rounded corners=3pt] (Yd) at(0,0){\textcolor{black}{\textbf{\small#1}}}; + + %end year + \node[draw,color=ugagray,rectangle,line width=2pt,fill=ugagray!20,rounded corners=3pt] (Yd) at(#4,0){\textcolor{black}{\textbf{\small#2}}}; + + +}%end begin part of newenv +{\end{tikzpicture}} \ No newline at end of file diff --git a/config/doc_poursuites_etudes/distrib/modeles/un_avis.tex b/config/doc_poursuites_etudes/distrib/modeles/un_avis.tex new file mode 100644 index 00000000..05a6320f --- /dev/null +++ b/config/doc_poursuites_etudes/distrib/modeles/un_avis.tex @@ -0,0 +1,164 @@ +%% Original : Cléo BARAS +%% +%% Template LaTeX des avis +%% +%% Version 1.0 +%% +%% Ce fichier est distribué par ScoDoc +%% +%% Si vous voulez le modifier sur le serveur, copiez le dans +%% ../local/ +%% et éditez votre copie dans local. +%% + + +% Ce document est n'est pas utilisé si le paramètre eTemplate LaTeX des avis" est rempli. + +% ************************************************************ +% En-tête de l'avis +% ************************************************************ +\begin{entete}{logos/logo_header} + \textbf{\Huge{Avis de Poursuites d'Etudes}} \\ + \ligne \\ + \normalsize{Département **DeptFullName**} \\ + \normalsize{**InstituteName**} \\ +\end{entete} + +\begin{nom} +**sexe** **prenom** **nom** (**age** ans) +\end{nom} + +% ************************************************************ +% Parcours scolaire de l'étudiant +% ************************************************************ + +\begin{rubrique}{Parcours scolaire} + +% TIMELINE +**parcourstimeline** + +{ +\scriptsize +\textbf{Guide de lecture} : +$\bullet$ Les options de la formation: FI = initiale, UFA = alternance (contrat pro), ENEPS = École nationale de l'enseignement professionnel supérieur +} +\end{rubrique} + +% ************************************************************ +% Bilan scolaire +% ************************************************************ + +\begin{rubrique}{Bilan scolaire synthétique} + + +\begin{tabular}{|p{0.08\textwidth}|p{0.11\textwidth}|*{8}{p{0.07\textwidth}|}} +% Titres des colonnes +\hline & \textbf{Resultats} & \multicolumn{4}{c|}{\textbf{Dans le groupe}\textdagger} + & \multicolumn{4}{c|}{\textbf{Dans la promo}\textdagger} \\ + & & Class. & Min & Moy & Max + & Class. & Min & Moy & Max \\ +\hline +\hline S1 & **S1:groupe:dut:note** & **S1:groupe:dut:rang** + & {\scriptsize **S1:groupe:dut:min**} + & {\scriptsize **S1:groupe:dut:moy**} + & {\scriptsize **S1:groupe:dut:max**} + & **S1:promo:dut:rang** + & {\scriptsize **S1:promo:dut:min**} + & {\scriptsize **S1:promo:dut:moy**} + & {\scriptsize **S1:promo:dut:max**} \\ +\hline S2 & **S2:groupe:dut:note** & **S2:groupe:dut:rang** + & {\scriptsize **S2:groupe:dut:min**} + & {\scriptsize **S2:groupe:dut:moy**} + & {\scriptsize **S2:groupe:dut:max**} + & **S2:promo:dut:rang** + & {\scriptsize **S2:promo:dut:min**} + & {\scriptsize **S2:promo:dut:moy**} + & {\scriptsize **S2:promo:dut:max**} \\ +\hline +\hline \cellcolor{black!20} 1A & **1A:groupe:dut:note** & **1A:groupe:dut:rang** + & {\scriptsize **1A:groupe:dut:min**} + & {\scriptsize **1A:groupe:dut:moy**} + & {\scriptsize **1A:groupe:dut:max**} + & **1A:promo:dut:rang** + & {\scriptsize **1A:promo:dut:min**} + & {\scriptsize **1A:promo:dut:moy**} + & {\scriptsize **1A:promo:dut:max**} \\ +\hline +\hline S3 & **S3:groupe:dut:note** & **S3:groupe:dut:rang** + & {\scriptsize **S3:groupe:dut:min**} + & {\scriptsize **S3:groupe:dut:moy**} + & {\scriptsize **S3:groupe:dut:max**} + & **S3:promo:dut:rang** + & {\scriptsize **S3:promo:dut:min**} + & {\scriptsize **S3:promo:dut:moy**} + & {\scriptsize **S3:promo:dut:max**} \\ +\hline S4* & **S4:groupe:dut:note** & **S4:groupe:dut:rang** + & {\scriptsize **S4:groupe:dut:min**} + & {\scriptsize **S4:groupe:dut:moy**} + & {\scriptsize **S4:groupe:dut:max**} + & **S4:promo:dut:rang** + & {\scriptsize **S4:promo:dut:min**} + & {\scriptsize **S4:promo:dut:moy**} + & {\scriptsize **S4:promo:dut:max**} \\ +\hline +\hline \cellcolor{black!20} 2A* & **2A:groupe:dut:note** & **2A:groupe:dut:rang** + & {\scriptsize **2A:groupe:dut:min**} + & {\scriptsize **2A:groupe:dut:moy**} + & {\scriptsize **2A:groupe:dut:max**} + & **2A:promo:dut:rang** + & {\scriptsize **2A:promo:dut:min**} + & {\scriptsize **2A:promo:dut:moy**} + & {\scriptsize **2A:promo:dut:max**} \\ +\hline +\hline DUT* & **4S:groupe:dut:note** & **4S:groupe:dut:rang** + & {\scriptsize **4S:groupe:dut:min**} + & {\scriptsize **4S:groupe:dut:moy**} + & {\scriptsize **4S:groupe:dut:max**} + & **4S:promo:dut:rang** + & {\scriptsize **4S:promo:dut:min**} + & {\scriptsize **4S:promo:dut:moy**} + & {\scriptsize **4S:promo:dut:max**} \\ +\hline +\end{tabular} + +\begin{minipage}{\textwidth} +\scriptsize +\textbf{Guide de lecture} : * Sur notes disponibles ; \textdagger~Le groupe est l'ensemble des étudiants ayant suivi le même semestre que **prenom** **nom** alors que la promo est l'ensemble des étudiants recevant le diplôme de DUT à la même date que **prenom** **nom** (ayant donc des parcours potentiellement différents) +\end{minipage} + +\end{rubrique} + +% ************************************************************ +% Avis de poursuite d'études manuscrit ou saisi dans les +% annotations de l'étudiant +% ************************************************************ + +\begin{rubrique}{Avis du jury de Poursuites d'Etudes} +**annotation** +\end{rubrique} + +\newpage % Saut de page +% ------------------------------------------------------------ + +% ************************************************************ +% Bilan automatisé par matières +% ************************************************************ + +\begin{rubrique}{Bilan par matières} +**bilanParTag** +\end{rubrique} + +% ************************************************************ +% Signature +% ************************************************************ + +\begin{rubrique}{Cachet de l'établissement} +\begin{flushright} +Le~\today,\\ +**NomResponsablePE**~\\ +\textit{Responsable des Poursuites d'Études du département **DeptName**} +\end{flushright} +\vspace{0.5cm} +\end{rubrique} + +% Le pied de page (indiqué dans les paramètres ScoDoc) vient après cette ligne diff --git a/config/doc_poursuites_etudes/distrib/un_footer.tex b/config/doc_poursuites_etudes/distrib/un_footer.tex new file mode 100644 index 00000000..ba4ff162 --- /dev/null +++ b/config/doc_poursuites_etudes/distrib/un_footer.tex @@ -0,0 +1,7 @@ +\begin{minipage}{\textwidth} \scriptsize +%IUT exemple - Dpt RT, 3 rue xxx, France + +% \textbf{Secrétariat Poursuite d'Etudes}: Xxx Yyy, Tel 01.02.03.04.05, xxx.yyy@example.com + +% \textbf{Responsables Poursuite d'Etudes RT}: Zzz Wwww, Tel 01.02.03.04.05, zzz.www@example.com +\end{minipage} \ No newline at end of file diff --git a/config/etablissements.csv b/config/etablissements.csv new file mode 100644 index 00000000..8cd2ceaf --- /dev/null +++ b/config/etablissements.csv @@ -0,0 +1,5317 @@ +COD_ETB;LIB_OFF_ETB;LIB_AD1_ETB;COD_POS_ADR_ETB;LIB_COM;LAT;LNG +0400786M;Afasec Ecole Des Courses Ctre Des Lads Et Jockeys;Hippodrome 164 Rue Geroges Pela;40000;Mont De Marsan;43.893485;-0.499782 +0753239N;Agence Nat Valorisation Recher;43 Rue De Caumartin;75009;Paris 09;48.8727925;2.3281692 +0290324V;Agrotech Formation;Le Cleusmeur;29260;Lesneven;48.5782643;-4.3214093 +0410629L;Annexe Du Legta De Vendome Lycée Horticole De Blois;5 7 R Des Grands Champs;41029;Blois;47.5660138;1.3272299 +0910831J;Annexe Ecole Agricole Privée St Nicolas;10 Avenue Division Leclerc;91430;Igny;48.7405146;2.229604 +0602005D;Annexe Lycée Agricole;40 Rue Villebois Mareuil;60000;Beauvais;49.4368684;2.0892144 +0593462T;Annexe Lycée G. Et T. Agricole Legta Du Nord;52 Rue Jean-baptiste Lebas;59177;Sains Du Nord;50.0905687;4.0044665 +0595540B;Annexe Lycée G. Et T. Agricole Legta Du Nord;17 Rue Des Tilleuls;59530;Le Quesnoy;50.245023;3.6509335 +0422266K;Ant Du Lap E Gautier De Nandax;219 Route De Fleury;42190;Charlieu;46.1618988;4.1710338 +0421168S;Ant Du Legta De Precieux;Le Creux Du Balay;42660;St Genest Malifaux;45.3482709;4.431769 +0421858S;Antenne Du Legta Roanne Cherve;37 Rue De La Republique;42440;Noiretable;45.8193741;3.764883 +0011056T;Antenne Du Lppr De L'ain;160 Rue De La Poype;01330;Villars Les Dombes;46.0035471;5.0313797 +0772151V;Ass Les Sinoplies Ctre De Formation;12 Rue Joseph Bodin Boismortier;77680;Roissy En Brie;48.8016537;2.6384353 +0410980T;C F P A;Domaine Des Frileuses;41150;Chaumont Sur Loire;47.480012;1.182876 +0133440U;C.n.r.s.;31 Chemin Joseph Aiguier;13009;Marseille 09;43.25633;5.4049604 +0130231F;Centre D Enseignement Et De Recherche De L Ensam;2 Cours Des Arts Et Metiers;13617;Aix En Provence;43.5302287;5.454456 +0490067G;Centre D Enseignement Et De Recherche De L Ensam D Angers;2 Bd Du Ronceray;49035;Angers;47.4751627;-0.5593541 +0761366X;Centre De Formation Le Havre Formation Entreprise;10 R Mal Delattre De Tassigny;76600;Le Havre;49.4914767;0.1139103 +0831023L;Centre De Formation Technique Drh De La Deleg.gener.armement;Rte Tour Royale Le Mourillon;83000;Toulon;43.1200697;5.9383291 +0831024M;Centre De Formation Technique Drh De La Deleg.gener.armement;Rte Tour Royale Le Mourillon;83000;Toulon;43.1200697;5.9383291 +0312020C;Centre D'etudes Superieures Industrielles Midi-pyrenees;Rue Magellan Innopole;31315;Labege;43.530582;1.529754 +0291758D;Centre D'instruction Naval Ecole De Maistrance;;29240;Brest;48.4472219;-4.421667 +0332373A;Centre Enseignement Medoc Hauteville;1 Impasse Lamire Hauteville;33250;Pauillac;45.1985079;-0.753281 +0831521C;Centre Etud Sup Techn Indust;Pl G.pompidou Qua Mayol;83000;Toulon;43.1200697;5.9383291 +0451467A;Centre Nat Rech Scientifique;3e Av Recherche Scientifique;45071;Orleans;47.8396941;1.9431002 +0451502N;Centre Nat Rech Scientifique;3e Av Recherche Scientifique;45071;Orleans;47.8396941;1.9431002 +0341860M;Centre National De La Recherche Scientif;1919 Route De Mende;34093;Montpellier;43.6388093;3.8653614 +0383049H;Centre National De La Recherche Scientif;;38042;Grenoble;45.195057;5.732322 +0753639Y;Centre National De La Recherche Scientif;15 Quai Anatole France;75007;Paris 07;48.86134;2.3230543 +0251189G;Centre National De Recherche;32 Avenue De L Observatoire;25030;Besancon;47.2478005;5.9896566 +0802092P;Centre Regional De Formation Aux Metiers Du Football;Rue Du Chapitre;80016;Amiens;49.8922653;2.2592521 +0171423E;Centre Sup Technique Privé Dietetique Esthetique Optique;5 Ter Avenue Kennedy;17044;La Rochelle;46.1680031;-1.1851484 +0332984P;Cesi Aquitaine;60 Rue De Maurian;33290;Blanquefort;44.9197812;-0.630999 +0542260N;Cesi Nancy;2 Bis Rue De La Credence;54600;Villers Les Nancy;48.6552886;6.1174951 +0622384E;Cesi Nord;7 Rue Diderot;62000;Arras;50.2971954;2.7335494 +0922455U;Cesi Paris;116 Avenue Aristide Briand;92220;Bagneux;48.7985634;2.322656 +0691696U;Cesi Rhone Alpes Auvergne;19 Avenue Guy De Collongue;69131;Dardilly;45.7832503;4.7658188 +0762969P;Cesi Rouen;Parc Vatine 1 Rue Marconi;76130;Mont St Aignan;49.459778;1.079798 +0442292C;Cesi St Nazaire;Boulevard De L Universite;44603;St Nazaire;47.253238;-2.264146 +0341564R;Cnearc- Eitarc Montpellier;1101 Avenue Agropolis;34033;Montpellier;43.6477471;3.868611 +0352342F;Cnrs;74e Rue De Paris Ctre Oberthur;35000;Rennes;48.1173421;-1.7075198 +0332865K;Cnrs Et Organisme Associe;Esplanade Des Arts Metiers;33405;Talence;44.8052072;-0.6012446 +0753480A;College De France;11 Pl Marcelin Berthelot;75231;Paris 05;48.8493963;2.3447743 +9840018X;College Privé College Pomare Iv;Papeete;98714;Papeete;-17.535022;-149.569594 +0672866B;Compagnie De Formation Pigier;15 Rue Des Magasins;67000;Strasbourg;48.5900534;7.7400921 +0753471R;Conservatoire National Des Arts Et Métiers (paris);292,rue Saint Martin;75141;Paris 03;48.8667317;2.3542533 +0312733C;Cours Assistance Toulouse;269 Route De Narbonne;31400;Toulouse;43.5536414;1.4680912 +0922297X;Cours Complementaire Privé Assurmath;44 Avenue Maurice Labrousse;92160;Antony;48.7555554;2.2959254 +0831447X;Cours Privé Cours Sigma;Chemin De La Buge;83110;Sanary Sur Mer;43.1207576;5.8079614 +0211551X;Cours Privé Saint Dominique;Rue De Velard;21320;Pouilly En Auxois;47.266633;4.5539379 +0312150U;Cours Privé Voltaire;57 Bd Armand Duportal;31000;Toulouse;43.6095732;1.4347543 +0411021M;Cours Promotion Sociale Coiffure;5 Rue De La Picardiere;41000;Blois;47.5884358;1.2950781 +0541559B;Ctr.form.prof.agricole Jeunes;3 Rue Drouas;54200;Toul;48.6725691;5.8924462 +0291759E;Ctre De Formation De Brest;Porte De Carpon;29240;Brest;48.390394;-4.486076 +0311230U;Ctre D'ecologie;29 Rue J Marvig;31055;Toulouse;43.5779576;1.4639799 +0930604B;Ctre Etud Sup Tech Industriel;3 Rue Fernand Hainaut;93407;St Ouen;48.9192718;2.3327857 +0751700R;Ctre Nat Exploitation Oceans;39 Avenue D Iena;75116;Paris 16;48.8687074;2.2955003 +0312339Z;Ctre Nat Rech Scientifique;16 Avenue Edouard Belin;31054;Toulouse;43.5631811;1.480326 +0631814E;Ctre Nat.rech.scientifique;34 Avenue Carnot;63000;Clermont Ferrand;45.7762666;3.0916043 +0110942P;Ctre Polyvalent Formation Prof;La Rouatiere;11400;Souilhanels;43.355982;1.902977 +0941958T;Deleg Regionale;27 Rue Paul Bert;94200;Ivry Sur Seine;48.8181225;2.3709464 +0941957S;Deleg Regionale Cnrs;Tour Europa 126 Belle Epine;94320;Thiais;48.760344;2.387405 +0672674T;Delegation Regionale Cnrs;23 Rue Du Loess;67037;Strasbourg;48.6042226;7.7115393 +0542276F;Delegation Regionale Du Cnrs;39 Rue Gambetta;54000;Nancy;48.691842;6.1794993 +0595987M;Delegation Regionale Du Cnrs;2 Rue Des Cannonniers;59046;Lille;50.6401336;3.0691392 +0411013D;E T I C Ecole Des Tech Communication;2 Rue Jehan De Saveuse;41000;Blois;47.5882653;1.3381421 +0611020C;E.s.a.t.;;61210;Giel Courteilles;48.7645229;-0.196656 +0951623Y;Ec Inter Sc Trait Information;Avenue Du Parc;95011;Cergy;49.0337852;2.072182 +0180910S;Ec Nat Sup Ingenieurs Bourges;10 Boulevard Lahitolle;18020;Bourges;47.0830802;2.417787 +0330203S;Ec Nle Ing Des Travaux Agricol;1 Cours Du Gl De Gaulle;33175;Gradignan;44.774267;-0.618945 +0641923T;Ec Sup Des Techno Indust Avanc;;64102;Bayonne;43.493181;-1.4749344 +0801911T;Ec Sup Ing Electr Et Electrote;14 Quai De La Somme;80083;Amiens;49.8996519;2.2914433 +0142182W;Ec Sup Ing Travaux Constr Caen;1 Rue Pierre Et Marie Curie;14610;Epron;49.2154076;-0.3662197 +0590353N;Ec Sup Tech Indust Textiles;52 Allee Lakanal;59654;Villeneuve D Ascq;50.6409021;3.1377931 +0332880B;Ec Tech Pr Esthetique Cosmet. Cecile Briat;21 Cours Marechal Foch;33000;Bordeaux;44.8470956;-0.575386 +0060693W;Ec.secondaire Et Techn.privee Cours Cannois-guy Furet;134 Boulevard De La Republique;06400;Cannes;43.5631735;7.0210192 +0782128P;Ec.secondaire Et Techn.privee Cours Du Prieure;27 Rue Diderot;78100;St Germain En Laye;48.8923567;2.0910716 +0730768N;Ec.secondaire Et Techn.privee D'altitude Le Val D'arly;Le Val D'arly;73590;St Nicolas La Chapelle;45.808921;6.501631 +0781947T;Ec.secondaire Et Techn.privee Foot;Domaine De Montjoye;78120;Clairefontaine En Yvelines;48.61237;1.908489 +0920925F;Ec.secondaire Et Techn.privee Marymount School;72 Boulevard De La Saussaye;92200;Neuilly Sur Seine;48.8928396;2.2700496 +0941954N;Ec.sup.ing.trav.const.;28 Ave Du President Wilson;94234;Cachan;48.7934331;2.3305073 +0332911K;Ec.tech.pr.coiffure Ec.tech.pr.a.berge;8 Place Tartas;33000;Bordeaux;44.8408689;-0.590832 +0952044F;Ecole 2d Degre General Privée;9 Bis Rue Saint Flaive;95120;Ermont;48.9881778;2.2598042 +0061991G;Ecole 2d Degre General Privée Alliance/apeda;22 Rue Michelet;06100;Nice;43.714606;7.2590664 +0752180M;Ecole 2d Degre General Privée Beausejour;23 Boulevard Beausejour;75016;Paris 16;48.8574466;2.2702555 +0952134D;Ecole 2d Degre General Privée Beith Yaacov;14 Avenue Charles Peguy;95200;Sarcelles;48.9811624;2.3754349 +0694066V;Ecole 2d Degre General Privée Beth Hanna;293 Rue Francis De Pressense;69100;Villeurbanne;45.7681543;4.8969586 +0952126V;Ecole 2d Degre General Privée Beth Israel;9 Ruelle Des Jardins;95360;Montmagny;48.9769243;2.3448886 +0132965C;Ecole 2d Degre General Privée Beth-myriam;60 Che Vallon De Toulouse;13010;Marseille 10;43.2664233;5.4263163 +0881749Y;Ecole 2d Degre General Privée Bienheureux Frassati;50 Rue Monseigneur Rodhain;88800;Mandres Sur Vair;48.2234792;5.891083 +0754083F;Ecole 2d Degre General Privée Carnot;98 Rue Pierre Demours;75017;Paris 17;48.8835651;2.2995698 +9720871P;Ecole 2d Degre General Privée Centre Saint Raphael;Quartier Bel Air;97233;Schoelcher;0.0;0.0 +0753829E;Ecole 2d Degre General Privée Claude Bernard;34 Rue De La Clef;75005;Paris 05;48.8416993;2.3526449 +0754672W;Ecole 2d Degre General Privée Colbert;5 Rue De La Sante;75013;Paris 13;48.8378098;2.342051 +0940872M;Ecole 2d Degre General Privée Cours Henri Iv;6 Avenue Du Reservoir;94100;St Maur Des Fosses;48.8081416;2.4967768 +0391047B;Ecole 2d Degre General Privée Cours N.d. De L'annonciation;Chateau;39270;Cressia;46.5223129;5.483452 +0690557F;Ecole 2d Degre General Privée Cours Pascal;21 Rue Longue;69001;Lyon 01;45.7654048;4.8337143 +0754971W;Ecole 2d Degre General Privée Cours Privé Progress;41 Rue De Turenne;75003;Paris 03;48.8571717;2.364311 +0342019K;Ecole 2d Degre General Privée Ec Pr Bilingue Internationale;Le Chateau Domaine De Massane;34670;Baillargues;43.650414;4.003782 +0753857K;Ecole 2d Degre General Privée Ecole Du Champ De Mars;87 Avenue De La Bourdonnais;75007;Paris 07;48.8559885;2.3026302 +0133785U;Ecole 2d Degre General Privée Fides;6 Cours Sexctius;13100;Aix En Provence;43.5275763;5.4433404 +0753859M;Ecole 2d Degre General Privée Fides;10 Avenue De La Bourdonnais;75007;Paris 07;48.8600406;2.2962319 +0492344G;Ecole 2d Degre General Privée Foyer Saint Thomas;Couvent Haye Aux Bonshommes;49240;Avrille;47.505335;-0.590629 +0651036Y;Ecole 2d Degre General Privée Gambetta;2 Rue De L'harmonie;65000;Tarbes;43.232008;0.0723895 +0755193M;Ecole 2d Degre General Privée Gaston Tenoudji;2 Rue Emile Borel;75017;Paris 17;48.8988905;2.3223466 +0752456M;Ecole 2d Degre General Privée Geoffroy Saint-hilaire;24 Rue Charcot;75013;Paris 13;48.8308116;2.3712295 +0753870Z;Ecole 2d Degre General Privée Hattemer;52 Rue De Londres;75008;Paris 08;48.8784036;2.3254588 +0430967T;Ecole 2d Degre General Privée Institut Alcuin;;43270;Ceaux D Allegre;45.181719;3.746936 +0611249B;Ecole 2d Degre General Privée Institut Croix Des Vents;55 Rue D Argentre;61500;Sees;48.6058338;0.1799387 +0940873N;Ecole 2d Degre General Privée Institut Francais De Grignon;19 Rue Jean-francois Marmontel;94320;Thiais;48.7530756;2.3962989 +0490950S;Ecole 2d Degre General Privée Institution Bois Robert;;49370;Becon Les Granits;47.503413;-0.80139 +0752803P;Ecole 2d Degre General Privée International School Of Paris;6 Rue Beethoven;75016;Paris 16;48.8584557;2.2873279 +0062013F;Ecole 2d Degre General Privée International School Of Sophia;60 Rue Du Vallon;06560;Valbonne;43.6263899;7.0532296 +0753377N;Ecole 2d Degre General Privée Ipecom Prepa Sciences;8 Rue Benjamin Godard;75016;Paris 16;48.8664881;2.275755 +0755278E;Ecole 2d Degre General Privée Jcours Cours Privés;6 Rue Mayran;75009;Paris 09;48.8772948;2.3454524 +0312170R;Ecole 2d Degre General Privée Jean-jacques Rousseau;22 Rue Ingres;31000;Toulouse;43.614223;1.4426803 +0940890G;Ecole 2d Degre General Privée Jeanne D'albret;3 Place Moreau David;94120;Fontenay Sous Bois;48.8431774;2.4652823 +0312527D;Ecole 2d Degre General Privée La Clairiere;8 Impasse Marestan;31100;Toulouse;43.5649875;1.4068322 +0320682T;Ecole 2d Degre General Privée Le Carouet;;32230;Monlezun;43.500014;0.213362 +0753911U;Ecole 2d Degre General Privée L'ecole;24 R Ingenieur Robert Keller;75015;Paris 15;48.8472783;2.2838185 +0750026W;Ecole 2d Degre General Privée L'enseign Par Petits Groupes;17 Avenue Felix Faure;75015;Paris 15;48.8421981;2.2908869 +0672863Y;Ecole 2d Degre General Privée Michael;2c Rue Du Schnokeloch;67200;Strasbourg;48.5784653;7.7138219 +0753895B;Ecole 2d Degre General Privée Moliere;2 Boulevard Soult;75012;Paris 12;48.8360654;2.4076243 +0753845X;Ecole 2d Degre General Privée Montaigne;1 Rue Dupin;75006;Paris 06;48.8502773;2.324821 +0693316E;Ecole 2d Degre General Privée Multilingue Ombrosa;95 Quai Clemenceau;69300;Caluire Et Cuire;45.807247;4.8438576 +0753060U;Ecole 2d Degre General Privée Ohr Joseph;29 Rue De Thionville;75019;Paris 19;48.8904226;2.3857306 +0753146M;Ecole 2d Degre General Privée Prepasup;38 Rue Des Blancs Manteaux;75004;Paris 04;48.8597021;2.3551971 +0753818T;Ecole 2d Degre General Privée Rene Reaumur;81 Rue Reaumur;75002;Paris 02;48.866918;2.3486024 +0030906U;Ecole 2d Degre General Privée Rudolf Steiner;Chateau De La Mhotte;03210;St Menoux;46.572769;3.115683 +0595489W;Ecole 2d Degre General Privée Saint Benoit;116 Boulevard D'armentieres;59100;Roubaix;50.7010617;3.1628397 +0142219L;Ecole 2d Degre General Privée Sainte Catherine De Sienne;;14740;St Manvieu Norrey;49.180678;-0.501961 +0753109X;Ecole 2d Degre General Privée Saint-john Perse;14 Rue Cavallotti;75018;Paris 18;48.8866068;2.327952 +0753376M;Ecole 2d Degre General Privée Saint-john Perse;3 Rue De L'eure;75014;Paris 14;48.8324435;2.3221673 +0942025R;Ecole 2d Degre General Privée Sophia;5 Rue Colonel P.brossolette;94480;Ablon Sur Seine;48.7249353;2.4195636 +0753894A;Ecole 2d Degre General Privée Spinoza;22 Rue Du Sergent Bauchat;75012;Paris 12;48.845549;2.3930299 +0861216E;Ecole 2d Degre General Privée St Thomas D Aquin;Le Parc;86700;Romagne;46.290382;0.3073 +0441850X;Ecole 2d Degre General Privée Ste Catherine De Sienne;32 Rue De La Bastille;44000;Nantes;47.2185622;-1.5664552 +0753878H;Ecole 2d Degre General Privée Verlaine;37 Rue Des Martyrs;75009;Paris 09;48.879285;2.3398549 +0754676A;Ecole 2d Degre General Privée Yechiva Chaare Thora;3 5 Rue Henri Turot;75019;Paris 19;48.876668;2.3725952 +0851557F;Ecole 2d Degre International Chavagnes;96 Rue Du Calvaire;85250;Chavagnes En Paillers;46.8935629;-1.2523775 +0261430N;Ecole 2d Degre Polyval Privée Formacom;3 Place Marx Dormoy;26200;Montelimar;44.5549839;4.7495125 +0450771U;Ecole 2d Degre Polyval Privée Formation Entreprise - Cecam;28 Rue Du Fbg De Bourgogne;45000;Orleans;47.9011332;1.9212467 +0731504N;Ecole 2d Degre Polyval Privée Univeria;725 Faubourg Montmelian;73000;Chambery;45.5665104;5.9347242 +0312579K;Ecole 2d Degre Prof Privée Dynameca;75 Avenue De Grande Bretagne;31300;Toulouse;43.5999437;1.4151779 +0171382K;Ecole 2d Degre Prof Privée Esthetique Coiffure De Sainton;4 Rue De La Comedie;17100;Saintes;45.7454397;-0.6320982 +0171434S;Ecole 2d Degre Prof Privée Inst Reg Techn Image Et Son;25 Avenue Dassault;17304;Rochefort;45.945147;-0.9578502 +0171416X;Ecole 2d Degre Prof Privée J. Eudes;8 Boul. Du Commandant Charcot;17443;Aytre;46.1351019;-1.1261536 +0861245L;Ecole 2d Degre Prof Privée Skhole D'art;2 Et 4 Place Jean De Berry;86000;Poitiers;46.5903977;0.3396993 +0141992P;Ecole 2d Degre Prof. Privée Arcade Caen;9 Rue Du Colonel Remy;14000;Caen;49.202484;-0.3878073 +0593022P;Ecole 2d Degre Prof. Privée Coupe Couture;38 Rue De Roubaix;59800;Lille;50.6388879;3.0697772 +0251070C;Ecole 2d Degre Prof. Privée Cours Hotelier De Besancon;98 Grande Rue;25000;Besancon;47.2357282;6.0272858 +0593076Y;Ecole 2d Degre Prof. Privée Cte Formation Perfectionnement;Rue Des Cligneux;59330;Hautmont;50.24159;3.895865 +0251674J;Ecole 2d Degre Prof. Privée Ecole De Coiffure Cordier;11 Bis Rue Nicolas Bruand;25000;Besancon;47.2493423;6.0227103 +0900039D;Ecole 2d Degre Prof. Privée Ecole De Commerce Kisel;7 Faubourg De Lyon;90000;Belfort;47.6343043;6.850814 +0251523V;Ecole 2d Degre Prof. Privée Ecole Du Fc Sochaux;20 Rue De La Cote;25230;Seloncourt;47.4588815;6.8458273 +0900035Z;Ecole 2d Degre Prof. Privée Ecole Ema / Imt;11-13 Rue Aristide Briand;90000;Belfort;47.6327477;6.8553195 +0622169W;Ecole 2d Degre Prof. Privée Ecole Europeenne D'esthetique;114 Rue Saint Aubert;62000;Arras;50.2924651;2.7692463 +0596339V;Ecole 2d Degre Prof. Privée Ecole Privée De Coiffure;74 Boulevard De La Liberte;59800;Lille;50.633826;3.0576975 +0596351H;Ecole 2d Degre Prof. Privée Efficom-nord;144/146 Rue Nationale;59000;Lille;50.6335348;3.0541112 +0595721Y;Ecole 2d Degre Prof. Privée Lp Privé Cours Auber-icam;6 Rue Auber;59046;Lille;50.6302567;3.0417529 +0595828P;Ecole 2d Degre Prof. Privée Pigier I.t.m.-i.c.a.;14 Rue Des Fusiliers Marins;59140;Dunkerque;51.0329454;2.3721355 +0593019L;Ecole 2d Degre Prof. Privée Schanfelaer-normand;20 Avenue Charles Saint Venant;59800;Lille;50.634035;3.0703394 +0594621C;Ecole 2d Degre Prof. Privée Soins Esthetiques;9-11 Rue Leon Trulin;59800;Lille;50.6384147;3.0663107 +0593098X;Ecole 2d Degre Prof. Privée Sueur Delabre Ctr Format Privé;Parks Service Unite1;59406;Cambrai;50.173538;3.236633 +0251908N;Ecole 2d Degre Prof.priv. Afco Ass. Franc-comt. Form.coiffur;16 Rue De Belfort;25000;Besancon;47.2469149;6.026832 +0360846Z;Ecole 2d Degre Prof.privee;8-10 Rue Guimon Latouche;36000;Chateauroux;46.8126291;1.6942297 +0580977X;Ecole 2d Degre Prof.privee;5 Avenue Claude Delisse;58360;St Honore Les Bains;46.8962966;3.8561571 +0711892W;Ecole 2d Degre Prof.privee;Charly;71250;Mazille;46.396423;4.610709 +0610799M;Ecole 2d Degre Prof.privee A.d.f.i.;40 Rue Du Puit Au Verrier;61001;Alencon;48.4363865;0.0876791 +0142359N;Ecole 2d Degre Prof.privee Acad Coif Esthet Morriss Partn;177 Rue D Auge;14000;Caen;49.1747803;-0.3407445 +0596780Z;Ecole 2d Degre Prof.privee Academie Privée Michel Dervyn;344 Avenue De La Marne;59700;Marcq En Baroeul;50.6798793;3.1150119 +0672904T;Ecole 2d Degre Prof.privee Academy Serge Comtesse;11 Rue Boston;67000;Strasbourg;48.5766769;7.7707505 +0333122P;Ecole 2d Degre Prof.privee Alienor D Esthetique;55 57 Rue Jules Ferry;33200;Bordeaux;44.842255;-0.6034116 +0851559H;Ecole 2d Degre Prof.privee Animation Et Petite Enfance;Zi Montifaut;85700;Pouzauges;46.782648;-0.837243 +0440432F;Ecole 2d Degre Prof.privee Anjorrant;80 Rue Du General Buat;44009;Nantes;47.2305106;-1.5393662 +0422052C;Ecole 2d Degre Prof.privee Ass Forezienne Ec Production;10 Rue Scheurer Kestner;42000;St Etienne;45.453516;4.3906874 +0721300X;Ecole 2d Degre Prof.privee Assoc Formation Personnel;47 Avenue Olivier Messiaen;72018;Le Mans;48.0146373;0.1594294 +0383299E;Ecole 2d Degre Prof.privee A-tel Communication;21 Avenue De La Plaine Fleurie;38240;Meylan;45.20604;5.7576666 +0693134G;Ecole 2d Degre Prof.privee Atelier Apprent Gorge De Loup;105 Avenue Sidoine Apollinaire;69009;Lyon 09;45.7672746;4.7977054 +0691474C;Ecole 2d Degre Prof.privee Ateliers D'apprentissage;La Giraudiere;69690;Brussieu;45.752063;4.547178 +0133909D;Ecole 2d Degre Prof.privee Ateliers De L'image Et Du Son;40 Rue Borde;13008;Marseille 08;43.2788168;5.3910828 +0693444U;Ecole 2d Degre Prof.privee Athena;22 Rue Pizay;69202;Lyon 01;45.7671314;4.836327 +0422109P;Ecole 2d Degre Prof.privee Avenir Formation;10 Rue Emile Noirot;42302;Roanne;46.0386546;4.0683222 +0611282M;Ecole 2d Degre Prof.privee Beaute Coiffure Et Formation;93 Avenue Rhin Et Danube;61000;Alencon;48.4183713;0.0973169 +0492317C;Ecole 2d Degre Prof.privee Beaute Coiffure Formation;51 Place Allain Targe;49400;Saumur;47.2570769;-0.0691588 +0171586G;Ecole 2d Degre Prof.privee Campus Des Metiers;Rue Du Chateau Site Du Prieure;17000;La Rochelle;48.7991114;-1.3941003 +0761772N;Ecole 2d Degre Prof.privee Catherine Lorene;52 Rue Jean Lecanuet;76000;Rouen;49.4454094;1.0882638 +0754129F;Ecole 2d Degre Prof.privee Ccip Ecoles Gregoire-ferrandi;28 Rue De L'abbe Gregoire;75006;Paris 06;48.8474723;2.3254989 +0660876U;Ecole 2d Degre Prof.privee Centre Enseignement Formation;Route De Thuir;66011;Perpignan;42.6820032;2.8646257 +0755207C;Ecole 2d Degre Prof.privee Chapellerie Modiste;8 Passage Abel Leblanc;75012;Paris 12;48.8459871;2.3814297 +0501212W;Ecole 2d Degre Prof.privee Coiff'ecole;26 Rue De La Paix;50100;Cherbourg Octeville;49.643053;-1.6270152 +0195038Y;Ecole 2d Degre Prof.privee Coiffure;4 Place Saint Pierre;19100;Brive La Gaillarde;45.1594566;1.5344809 +0721515F;Ecole 2d Degre Prof.privee Coiffure Esthet Espace Bel Air;200 Avenue Georges Durand;72100;Le Mans;47.9754992;0.2147089 +0442345K;Ecole 2d Degre Prof.privee Coiffure Et Esthetique;44 Quai Malakoff;44000;Nantes;47.2147689;-1.5414584 +0011255J;Ecole 2d Degre Prof.privee Coiffure Europeenne De L'ain;30 Avenue Alphonse Baudin;01000;Bourg En Bresse;46.200325;5.216353 +0111011P;Ecole 2d Degre Prof.privee Coiffure K L;3 Square Gambetta;11000;Carcassonne;43.2116828;2.357763 +0133683H;Ecole 2d Degre Prof.privee Coiffure Leader;441 Avenue Du Prado;13008;Marseille 08;43.266863;5.3830066 +0851437A;Ecole 2d Degre Prof.privee Coiffure Morriss;12 Rue Jean Jaures;85000;La Roche Sur Yon;46.6696044;-1.4289695 +0421913B;Ecole 2d Degre Prof.privee Createc;12 Rue Michel Rondet;42000;St Etienne;45.4380867;4.3854206 +0754168Y;Ecole 2d Degre Prof.privee Ctre Marcel Lamy Coiffure-esth;15-17 Rue Des Fillettes;75018;Paris 18;48.8946669;2.3633961 +0754338H;Ecole 2d Degre Prof.privee Ctre Pri Ens Soins Esthetiques;9 Rue Volney;75002;Paris 02;48.8696803;2.3301585 +0311202N;Ecole 2d Degre Prof.privee D'esthetique Cosmetique;23 Rue Gabriel Peri;31000;Toulouse;43.6060897;1.4534456 +0752123A;Ecole 2d Degre Prof.privee Ec Francaise D'ens Technique;110 Rue De Picpus;75012;Paris 12;48.8385284;2.4013752 +0754872N;Ecole 2d Degre Prof.privee Ec Internat Esthetiq Francaise;5 Avenue Bertie Albrecht;75008;Paris 08;48.8756935;2.3011723 +0754108H;Ecole 2d Degre Prof.privee Ec Internationale De Coiffure;76 Rue Reaumur;75002;Paris 02;48.8664449;2.3517469 +0342216Z;Ecole 2d Degre Prof.privee Ec Pr Esthetique Millenaire;1350 Av Albert Einstein;34000;Montpellier;43.6101676;3.9133879 +0341752V;Ecole 2d Degre Prof.privee Ec Sup Metiers Aern Esma;Aerp Montpellier Mediterranee;34137;Mauguio;43.5788389;3.959328 +0922623B;Ecole 2d Degre Prof.privee Ecofac;72 Avenue De La Republique;92320;Chatillon;48.8019714;2.2959133 +0754169Z;Ecole 2d Degre Prof.privee Ecole Active De Coiffure;203 205 Rue Lafayette;75010;Paris 10;48.8810547;2.3636236 +0754112M;Ecole 2d Degre Prof.privee Ecole De Bijouterie-joaillerie;58 Rue Du Louvre;75002;Paris 02;48.8658847;2.3434157 +0750115T;Ecole 2d Degre Prof.privee Ecole De Coiffure;60 Rue Saint Lazare;75009;Paris 09;48.8768576;2.3333014 +0754114P;Ecole 2d Degre Prof.privee Ecole Dentaire Francaise;3 Rue De L'est;75020;Paris 20;48.8708623;2.3944941 +0180939Y;Ecole 2d Degre Prof.privee Ecole Des Metiers;145-151 Rue De Turly;18000;Bourges;47.1015295;2.4242279 +0681962N;Ecole 2d Degre Prof.privee Ecole Des Metiers Artistiques;2 Rue Sainte-catherine;68100;Mulhouse;47.7426617;7.3390234 +0251890U;Ecole 2d Degre Prof.privee Ecole Ema 25 Besancon;13 Rue Du Balcon;25000;Besancon;47.2476688;6.0251837 +0624303R;Ecole 2d Degre Prof.privee Ecole Europeenne Esthetique Ii;112 Rue Saint Aubert;62000;Arras;50.2924167;2.7693128 +0442412H;Ecole 2d Degre Prof.privee Ecole Nantaise De Coiffure;6 Place Montaigne;44000;Nantes;47.2201576;-1.5615212 +0011294B;Ecole 2d Degre Prof.privee Ecole Technique Du Bois;560 B Rue Du Marais;01110;Cormaranche En Bugey;45.957123;5.602139 +0212188P;Ecole 2d Degre Prof.privee Eisec;35 Rue Charles Dumont;21000;Dijon;47.3123064;5.0399242 +0624372R;Ecole 2d Degre Prof.privee Elit Academy;50 60 Rue De Folkestone;62200;Boulogne Sur Mer;50.7279326;1.5994104 +0062052Y;Ecole 2d Degre Prof.privee Elysees Cannes;22 Bd De La Republique;06400;Cannes;43.5545307;7.0250715 +0672997U;Ecole 2d Degre Prof.privee Elysees Marbeuf;26 Rue Des Magasins;67000;Strasbourg;48.591175;7.7408775 +0212187N;Ecole 2d Degre Prof.privee Ema;15 Rue De L'arquebuse;21000;Dijon;47.3190199;5.031125 +0831653W;Ecole 2d Degre Prof.privee Ema;1349 Boulevard Jean Moulin;83700;St Raphael;43.4279147;6.7900075 +0292256V;Ecole 2d Degre Prof.privee Emb Pigier;31 Rue De Douarnenez;29000;Quimper;47.9980478;-4.1098548 +0121234B;Ecole 2d Degre Prof.privee Emilie De Rodat;11 Rue Seguy;12000;Rodez;44.3519011;2.5755888 +0460637T;Ecole 2d Degre Prof.privee Enseignement Commercial;17 Rue De La Prefecture;46000;Cahors;44.4475229;1.441989 +0421947N;Ecole 2d Degre Prof.privee Epec-darfeuille;5 Place Jean Jaures;42000;St Etienne;45.4405716;4.3855444 +0580978Y;Ecole 2d Degre Prof.privee Estec;2/4 Rue Saint-benin;58000;Nevers;46.9862229;3.1492371 +0875054W;Ecole 2d Degre Prof.privee Esthetiq Cosmetiq Coiffure;3 Allee St Alexis;87000;Limoges;45.8253606;1.2591981 +0573543S;Ecole 2d Degre Prof.privee Esthetique Coiffure;9 Rue Ausone;57000;Metz;49.1087379;6.1725896 +0820777Z;Ecole 2d Degre Prof.privee Esthetique Cosmet Skhole D'art;3 R Bataille De Dunkerque 1940;82000;Montauban;44.0068309;1.3461514 +0011256K;Ecole 2d Degre Prof.privee Esthetique M C Dumonceau;30 Avenue Alphonse Baudin;01000;Bourg En Bresse;46.200325;5.216353 +0753082T;Ecole 2d Degre Prof.privee Ets - Iris;63 Rue Ampere;75017;Paris 17;48.885201;2.3009995 +0752124B;Ecole 2d Degre Prof.privee F.morice Esthetique-cosmetique;27 Rue Vernet;75008;Paris 08;48.8720338;2.2978549 +0241248B;Ecole 2d Degre Prof.privee Formafoot;87 Route De Bordeaux;24430;Marsac Sur L Isle;45.1841986;0.6576263 +0442617F;Ecole 2d Degre Prof.privee Formation Corps Et Beaute;24 Rue De L'ile De France;44600;St Nazaire;47.2845926;-2.209649 +0754465W;Ecole 2d Degre Prof.privee Formul?abc;105 Boulevard De Magenta;75010;Paris 10;48.878965;2.3535647 +0492028N;Ecole 2d Degre Prof.privee Francaise Coiffure Esthetique;Place Du Chapeau De Gendarme;49000;Angers;47.4511367;-0.5553704 +0492343F;Ecole 2d Degre Prof.privee Francaise Coiffure Esthetique;192 Rue Nationale;49300;Cholet;47.0618334;-0.8842425 +0133825M;Ecole 2d Degre Prof.privee Giorgifont;2a Rue De Rome;13001;Marseille 01;43.2957629;5.3782139 +0133567G;Ecole 2d Degre Prof.privee Groupe Pluralis;16 Bd Theodore Thurner;13006;Marseille 06;43.2918566;5.383141 +0422027A;Ecole 2d Degre Prof.privee Hair School;35 Rue D'arcole;42000;St Etienne;45.43977;4.3804548 +0912185F;Ecole 2d Degre Prof.privee I P F C;93 Bd Decauville;91024;Evry;48.6332379;2.4421065 +0301757X;Ecole 2d Degre Prof.privee Ifc Gard;125 Rue De L Hostellerie;30000;Nimes;43.8139025;4.3459059 +0693368L;Ecole 2d Degre Prof.privee Ifosupd;181 Avenue Jean Jaures;69348;Lyon 07;45.7368155;4.836897 +0142172K;Ecole 2d Degre Prof.privee Inst Form Esth Et Coiff Ifec;1 Et 3 Rue De L'arquette;14000;Caen;49.1748513;-0.3556685 +0341674K;Ecole 2d Degre Prof.privee Inst Sup Privé Sup Exup;Aerp Montpellier Mediterranee;34137;Mauguio;43.5788389;3.959328 +0851584K;Ecole 2d Degre Prof.privee Inst Sup Prothese Assist Dent;Route De La Goriandiere;85504;Les Herbiers;46.872157;-0.999918 +0841042B;Ecole 2d Degre Prof.privee Inst. Privé Format.commerciale;250 Rue Du 12e Reg. De Zouaves;84094;Avignon;43.949317;4.805528 +0721610J;Ecole 2d Degre Prof.privee Instit K Form Esthetique;200 Avenue Georges Durand;72100;Le Mans;47.9754992;0.2147089 +0492342E;Ecole 2d Degre Prof.privee Institut De Bijouterie;Square Balzac;49412;Saumur;47.2519481;-0.0800867 +0754163T;Ecole 2d Degre Prof.privee Institut Europeen De Coiffure;28 Rue De Trevise;75009;Paris 09;48.8746401;2.3454931 +0851599B;Ecole 2d Degre Prof.privee Iseca;Village D'entreprises;85700;Pouzauges;46.782648;-0.837243 +0840077C;Ecole 2d Degre Prof.privee Isfap;11 Avenue Des Sources;84000;Avignon;43.9413462;4.8139846 +0573734Z;Ecole 2d Degre Prof.privee Isfec;15 Rue Des Charpentiers;57070;Metz;49.1114889;6.2334259 +0061888V;Ecole 2d Degre Prof.privee Jeune Aiglon;177 Route De Grenoble;06200;Nice;43.6810228;7.1990378 +0624366J;Ecole 2d Degre Prof.privee Joe's Art Concept;3 Rue Bayard;62300;Lens;50.4298166;2.8333087 +0312693J;Ecole 2d Degre Prof.privee La Pradette;12 Chemin De La Pradette;31600;Muret;43.4624447;1.3224734 +0690636S;Ecole 2d Degre Prof.privee Le C.e.p.a.j;Chemin De Bernicot;69564;St Genis Laval;45.6933714;4.771675 +0673004B;Ecole 2d Degre Prof.privee L'inst. Coiffure Et Esthetique;10 Rue Paul Eluard;67200;Strasbourg;48.5882779;7.6973786 +0301777U;Ecole 2d Degre Prof.privee L'institut;4 Bis Rue Julien Trelis;30100;Ales;44.127204;4.083352 +0631974D;Ecole 2d Degre Prof.privee Maestris;9/12 Rue Gilbert Romme;63000;Clermont Ferrand;45.7675847;3.101037 +0596791L;Ecole 2d Degre Prof.privee Maestris Beaute;679 Avenue De La Republique;59000;Lille;50.6562547;3.0875691 +0596794P;Ecole 2d Degre Prof.privee Maestris Beaute;9 Bis Rue De Roubaix;59400;Cambrai;50.1811372;3.2380149 +0133780N;Ecole 2d Degre Prof.privee Maestris Marseille;54 Traverse Maridet;13012;Marseille 12;43.3096761;5.4199628 +0133787W;Ecole 2d Degre Prof.privee Maestris Pigier;2 Rue Le Corbusier;13090;Aix En Provence;43.5233286;5.4325772 +0422190C;Ecole 2d Degre Prof.privee Maison De La Coiffure;38 Rue Des Passementiers;42100;St Etienne;45.4198431;4.396064 +0754242D;Ecole 2d Degre Prof.privee Marbeuf Esthetique Elysees;27 Rue Marbeuf;75008;Paris 08;48.8691291;2.3042693 +0312251D;Ecole 2d Degre Prof.privee Marge Verlair;5 Impasse Saint Aubin;31000;Toulouse;43.6037233;1.4532092 +0440425Y;Ecole 2d Degre Prof.privee Matile Esthetique Manucurie;24 Rue Crebillon;44000;Nantes;47.2132721;-1.5619479 +0721546P;Ecole 2d Degre Prof.privee Metiers De La Musique;71 Avenue Olivier Messiaen;72000;Le Mans;48.0154161;0.1544436 +0492333V;Ecole 2d Degre Prof.privee Morriss Partner;70 Rue Du Grand Launay;49000;Angers;47.4678294;-0.5932746 +0442196Y;Ecole 2d Degre Prof.privee Morriss-partner;5 Allee Baco;44002;Nantes;47.2146182;-1.5479843 +0061926L;Ecole 2d Degre Prof.privee N?1 Ctre Perf Maitres Coiffeur;1 Place Massena;06000;Nice;43.6974679;7.2702352 +0596406T;Ecole 2d Degre Prof.privee Patricia Houzet;12 Rue Sadi Carnot;59290;Wasquehal;50.668925;3.1303161 +0660070T;Ecole 2d Degre Prof.privee Pigier;49 Bd Clemenceau 7 Rue P Courty;66000;Perpignan;42.698684;2.8958719 +0694088U;Ecole 2d Degre Prof.privee Pigier;110 Place De La Gare;69400;Villefranche Sur Saone;45.9845849;4.7202289 +0841119K;Ecole 2d Degre Prof.privee Pigier;40 Bis Chemin Du Lavarin;84000;Avignon;43.9295739;4.8047154 +0442547E;Ecole 2d Degre Prof.privee Pigier Coiffure;16 Rue De Vauzelles;44110;Chateaubriant;47.712001;-1.3750486 +0492230H;Ecole 2d Degre Prof.privee Pigier Coiffure Esthetique;30 Rue Bodinier;49000;Angers;47.4726409;-0.5544881 +0720829K;Ecole 2d Degre Prof.privee Pigier Coiffure Esthetique;41 Rue Du Docteur Leroy;72005;Le Mans;48.0016328;0.1969314 +0301749N;Ecole 2d Degre Prof.privee Pigier Formanimes;105 Rue Claude Nicolas Ledoux;30900;Nimes;43.8150783;4.3471676 +0754686L;Ecole 2d Degre Prof.privee R.ferrere Esthetiq-cosmetique;14 Rue Du Faubourg St Honore;75008;Paris 08;48.8687211;2.3223202 +0751593Z;Ecole 2d Degre Prof.privee Saint-louis Union Academie;113 115 Rue Du Fg Poissonniere;75009;Paris 09;48.8779515;2.3490733 +0341831F;Ecole 2d Degre Prof.privee Sarl Ecole Sonia Delaunay;Route De Boujan;34500;Beziers;43.3663036;3.2315349 +0721645X;Ecole 2d Degre Prof.privee Sarth'esthetique Formations;185 Rue Henri Champion;72100;Le Mans;47.9827235;0.232009 +0690696G;Ecole 2d Degre Prof.privee Silvya Terrade;53 Place De La Republique;69002;Lyon 02;45.7606843;4.8360085 +0693974V;Ecole 2d Degre Prof.privee Studio M;565 Rue Du Sans Souci;69760;Limonest;45.805516;4.7733391 +0333187K;Ecole 2d Degre Prof.privee Visagisme Coiffure Developpeme;81 Avenue De L'epinette;33500;Libourne;44.9106614;-0.2289695 +0752435P;Ecole 2d Degre Prof.privee Y.rocher Esthetique-cosmetique;62 Avenue D'iena;75016;Paris 16;48.8687256;2.2959626 +0751698N;Ecole 2d Degree Prof. Privée Pigier;67 69 Rue De Douai;75009;Paris 09;48.883762;2.3287504 +0132665B;Ecole 2nd Degre General Privée Cours Du Parc Breteuil;7 Rue Lacedemone;13006;Marseille 06;43.2814125;5.3771657 +0133446A;Ecole 2nd Degre General Privée Hamaskaine;60 Boulevard Pinatel;13012;Marseille 12;43.3148118;5.4413806 +0133148B;Ecole 2nd Degre General Privée I.b.s. Of Provence;500 Petite Rte De Bouc Bel Air;13080;Aix En Provence;43.4513324;5.429245 +0131938L;Ecole 2nd Degre General Privée Institut Leschi;58 Cours Julien;13006;Marseille 06;43.2943303;5.383053 +0133242D;Ecole 2nd Degre General Privée St Pierre;24 Av St Eloi;13790;Peynier;43.4467555;5.6397325 +0131669U;Ecole 2nd Degre General Privée Val Saint Andre;19 Avenue Malacrida;13100;Aix En Provence;43.5165876;5.4668556 +0430842G;Ecole Agricole Privée;36 Rue Du Colonel De Turenne;43200;Yssingeaux;45.1426639;4.1220723 +0601404A;Ecole Agricole Privée;;60120;Rouvroy Les Merles;49.647906;2.358818 +0601575L;Ecole Agricole Privée;Place De L'hotel De Ville;60190;Estrees St Denis;49.426115;2.639508 +0611018A;Ecole Agricole Privée;Saint Gervais;61220;Briouze;48.704021;-0.369682 +0611021D;Ecole Agricole Privée;;61240;Nonant Le Pin;48.7069199;0.217466 +0631560D;Ecole Agricole Privée;;63580;Vernet La Varenne;45.472891;3.451612 +0631561E;Ecole Agricole Privée;1 Route De Riom;63720;Ennezat;45.8977695;3.2206849 +0601576M;Ecole Agricole Privée (filles);39 Route Des Flandres;60490;Orvillers Sorel;49.5786617;2.7084991 +0332376D;Ecole Agricole Privée Centre D Enseignement Agricole;44 Crs Gambetta;33210;Langon;44.5532316;-0.2524309 +0840746E;Ecole Agricole Privée Centre De Formation;19 Quai Pasteur;84110;Vaison La Romaine;44.2401186;5.0726233 +0595121W;Ecole Agricole Privée Centre D'enseignement Agricole;110 Route De Gravelines;59630;Bourbourg;50.9522873;2.1920151 +0501576S;Ecole Agricole Privée Centre D'etudes Rurales;Rue De Monteglise;50720;Barenton;48.6026445;-0.8315884 +0595122X;Ecole Agricole Privée Centre Education Et Promotion;15 Rue Du College;59940;Estaires;50.6462778;2.7208128 +0141724Y;Ecole Agricole Privée Centre Horticole;Rue Du Prieure;14480;St Gabriel Brecy;49.2780979;-0.5664422 +0840747F;Ecole Agricole Privée Centre Saint Dominique;Rue Des Ursulines;84601;Valreas;44.385702;4.989613 +0030888Z;Ecole Agricole Privée Claude Mercier;Route De Lapalisse;03250;Le Mayet De Montagne;46.0750346;3.6671551 +0261265J;Ecole Agricole Privée Ctr D'etudes Forestieres Agr.;103 Av De Rochemaure;26200;Montelimar;44.5657532;4.735804 +0611175W;Ecole Agricole Privée Ctre Form Technicien Agricole;25 Rue Pierre Neveu;61600;La Ferte Mace;48.5935429;-0.3524704 +0771738W;Ecole Agricole Privée Ctre Formation Prof Agr Et Hor;Domaine De Morfonde;77270;Villeparisis;48.947136;2.638554 +0511646M;Ecole Agricole Privée Ctre Hort Foyer De L'enfance;33 Rue Du Clair Logis;51000;Chalons En Champagne;48.9740922;4.3620971 +0501793C;Ecole Agricole Privée Ctre Nat.form.lads Et Jockeys;Graignes;50620;Graignes Mesnil Angot;49.2381699;-1.19717 +0931654T;Ecole Agricole Privée Ec Horticulture Fenelon;1 Rue De Montauban;93410;Vaujours;48.9301569;2.5695529 +0881452A;Ecole Agricole Privée Ecole D Horticulture Et De Pay;6 Rue Du College;88700;Roville Aux Chenes;48.3861293;6.6027303 +0840788A;Ecole Agricole Privée Et Reg Form Prof Agric Forest;;84240;La Bastide Des Jourdans;43.784867;5.634354 +0781068M;Ecole Agricole Privée Horticole;43 Rue Du General De Gaulle;78490;Le Tremblay Sur Mauldre;48.7703215;1.8781482 +0133500J;Ecole Agricole Privée I.c.o.p.;1 Allee Des Pinsons;13012;Marseille 12;43.3136607;5.4380651 +0595012C;Ecole Agricole Privée Inst Enseignement Technologiqu;Route De Warhem;59492;Hoymille;50.972972;2.461305 +0641545G;Ecole Agricole Privée Inst.secondaire Jean Errecart;Route Aicirits;64120;St Palais;43.327408;-1.032999 +0595120V;Ecole Agricole Privée Institut Agricole;19 Rue De L'epee;59400;Cambrai;50.1735274;3.2293037 +0595124Z;Ecole Agricole Privée Institut Agricole;69 Rue Du Violon D'or;59190;Hazebrouck;50.7219064;2.5298525 +0623280D;Ecole Agricole Privée Institut Agricole;22 Ancienne Route Nationale;62690;Savy Berlette;50.3497901;2.557783 +0623279C;Ecole Agricole Privée Institut Agricole Saint Joseph;Route Nationale 43;62137;Coulogne;50.905437;1.9459807 +0593257V;Ecole Agricole Privée Institut De Genech;Rue De La Liberation;59242;Genech;50.5300906;3.2117313 +0501578U;Ecole Agricole Privée Institut L'abbaye;Route De Quineville;50310;Montebourg;49.4967815;-1.3685693 +0141723X;Ecole Agricole Privée Institut Lemonier;Rue D Herouville;14013;Caen;49.1935596;-0.3500571 +0030925P;Ecole Agricole Privée Institut Rural;;03110;Escurolles;46.143095;3.26744 +0400753B;Ecole Agricole Privée Ireo;Cassen;40360;Castelnau Chalosse;43.679287;-0.846823 +0382374Z;Ecole Agricole Privée La Maison;La Bonte;38620;St Geoire En Valdaine;45.4581366;5.6351566 +0133005W;Ecole Agricole Privée Lads Et Jockeys;Les Plaines De L Arbois;13480;Cabries;43.44489;5.320507 +0601613C;Ecole Agricole Privée Lads-jockeys;5 Rue Du Chauffour;60270;Gouvieux;49.1894825;2.4274189 +0641542D;Ecole Agricole Privée Lap Frantses Enia;Av Du Jai Alai;64220;St Jean Pied De Port;43.1663684;-1.2335137 +0781066K;Ecole Agricole Privée Le Logis;5 Rue De Port Royal;78470;St Lambert;48.7332837;2.0194432 +0150661M;Ecole Agricole Privée Letrt St Vincent;2 Rue Marcellin Boudet;15100;St Flour;45.0348129;3.0871023 +0623276Z;Ecole Agricole Privée Lycée Agricole Sainte Marie;52 Rue D'isbergues;62120;Aire Sur La Lys;50.6386462;2.4046454 +0593256U;Ecole Agricole Privée Lycée Privé D'anchin;Abbaye D'anchin;59146;Pecquencourt;50.375524;3.212056 +0595119U;Ecole Agricole Privée Lycée Privé Rural;5 Rue De La Chaussee Brunehaut;59570;Bavay;50.2871242;3.7776055 +0831109E;Ecole Agricole Privée Lycée Professionnel Agricole;Route De Barjols;83470;St Maximin La Ste Baume;43.472847;5.869149 +0623278B;Ecole Agricole Privée Lycée Rural Privé Catholique;Rue Druet;62116;Bucquoy;50.137547;2.70619 +0781022M;Ecole Agricole Privée Notre-dame De La Roche;Boite Postale 10;78320;Levis St Nom;48.722222;1.949444 +0150658J;Ecole Agricole Privée Saint Joseph;Avenue De La Gare;15600;Maurs;44.7084459;2.1994338 +0911551S;Ecole Agricole Privée St Antoine;Chateau;91460;Marcoussis;48.645942;2.220813 +0762284V;Ecole Agricole Privée St Joseph;Chateau De Mesnieres;76270;Mesnieres En Bray;49.7649649;1.380615 +0781069N;Ecole Agricole Privée Sully;22 Avenue De L'europe;78200;Magnanville;48.9665663;1.6800698 +0781023N;Ecole Agricole Privée Tecomah;Parc De Jouy;78350;Jouy En Josas;48.764757;2.163188 +0690194L;Ecole Catholique D'arts Et Metiers;40 Montee Saint Barthelemy;69321;Lyon 05;45.7624909;4.8246771 +0590349J;Ecole Centrale De Lille;Cite Scientifique;59651;Villeneuve D Ascq;50.623334;3.145 +0690187D;Ecole Centrale De Lyon;36 Avenue Guy De Collongue;69131;Dardilly;45.782627;4.7662635 +0440100V;Ecole Centrale De Nantes;1 Rue De La Noe;44321;Nantes;47.2482417;-1.5499091 +0754431J;Ecole Centrale D'electronique;53 Rue De Grenelle;75007;Paris 07;48.8545505;2.3256016 +0921225G;Ecole Centrale Des Arts Et Manufactures, Paris;Grande Voie Des Vignes;92295;Chatenay Malabry;48.7660651;2.2865429 +0951807Y;Ecole Commerciale Privée Fayol;4 Rue Revert;95300;Pontoise;49.052526;2.0952513 +0251480Y;Ecole D'agriculture Privé Saint Joseph;Place Cretin;25270;Levier;46.954174;6.116989 +0951820M;"ecole De Biologie Industrielle ""ebi""";32 Boulevard Du Port;95094;Cergy;49.0322975;2.0640348 +0501669T;Ecole De Formation Technique Ecole Des Fourriers;;50460;Querqueville;49.664767;-1.695441 +0595781N;Ecole De Formation Technique Ecole Reedu Prof Andre Maginot;35 Rue Du General Sarail;59056;Roubaix;50.6915213;3.1716011 +0130230E;Ecole De L'air;Base Aerienne 701;13300;Salon De Provence;43.603333;5.106389 +0440429C;Ecole De Metiers Ctre Formation Gaz De France;La Croix Gaudin;44360;St Etienne De Montluc;47.2834179;-1.7417426 +0741433F;Ecole De Production Privée E.c.a.u.t;301 Route De Bregny;74250;Viuz En Sallaz;46.147207;6.393383 +0470064P;Ecole De Tersac Ac;Tersac;47180;Meilhan Sur Garonne;44.495752;0.06094 +0312188K;Ecole Dentaire Francaise;20 Rue St Antoine Du T;31000;Toulouse;43.603794;1.4473808 +0251950J;Ecole Des Arts Et Techniques;11 Rue Nicolas Bruand;25000;Besancon;47.2493423;6.0227103 +0753742K;Ecole Des Hautes Etudes En Sciences Sociales;54 Boulevard Raspail;75006;Paris 06;48.8501959;2.3269388 +EHESS;Ecole Des Hautes Etudes En Sciences Sociales;54, 96,105 Boulevard Raspail;75005;Paris 05;48.8461933;2.3281331 +0750043P;Ecole D'ingenieurs De La Ville De Paris;57 Boulevard Saint Germain;75240;Paris 05;48.8504483;2.3463367 +0623921A;Ecole D'ingenieurs Du Pas De Calais;Campus De La Malassise;62967;Longuenesse;50.736744;2.243632 +0171435T;Ecole D'ingenieurs En Genie Des Systemes Industriels;26 Rue Vaux De Foletier;17011;La Rochelle;46.1398628;-1.1537103 +0171550T;Ecole Francaise Coiffure Et Esthetique;17-19 Avenue De Mulhouse;17000;La Rochelle;0.0;0.0 +0791162J;Ecole Francaise Coiffure Et Esthetique;7-27 Rue Marcel Paul;79000;Niort;0.0;0.0 +0753592X;Ecole Francaise D'electronique Et D'informatique;10 Rue Amyot;75005;Paris 05;48.8434614;2.3468998 +0941934S;Ecole Francaise D'electronique Et D'informatique;3 Rue Victor Hugo;94800;Villejuif;48.78874;2.3650286 +0951678H;Ecole Gen Et Technol Privée Compta-sup Marketing;1 Av De La Div Leclerc;95350;St Brice Sous Foret;48.9922397;2.3583183 +0830177S;Ecole Gen. Et Technol.privee Cours Renaissance;Le Mayol Rue Leon Reboul;83000;Toulon;43.1185381;5.937864 +0831372R;Ecole Gen. Et Technol.privee Guy Furet;702 Avenue Du 15e Corps;83600;Frejus;43.4394351;6.7435339 +0752593L;Ecole Gen.et Technol. Privé Nation Bauchat Ecole D La Lune;22 Rue Du Sergent Bauchat;75012;Paris 12;48.845549;2.3930299 +0753372H;Ecole Gen.et Technol. Privée Beth Sefer Yad Mordekhai;145 Rue Saint Maur;75011;Paris 11;48.8679559;2.375428 +0753819U;Ecole Gen.et Technol. Privée Inst Marais-charlemagne-polles;9 Rue Dieu;75010;Paris 10;48.8703187;2.3644784 +0061990F;Ecole Gen.et Technol.privee As Cannes Football;Bp 179;06152;Cannes;43.552847;7.017369 +0133566F;Ecole Gen.et Technol.privee Bac 2000;13 Rue De La Poudriere;13090;Aix En Provence;43.52038;5.4449743 +0752382G;Ecole Gen.et Technol.privee Clapeyron Europe Math;23 Rue Clapeyron;75008;Paris 08;48.8823236;2.3234815 +0755261L;Ecole Gen.et Technol.privee Cours Legendre;25 Rue Du Petit Musc;75004;Paris 04;48.8528745;2.3643336 +0693366J;Ecole Gen.et Technol.privee Cours Montesquieu;11 13 Rue Auguste Lacroix;69003;Lyon 03;45.7549248;4.8451646 +0781946S;Ecole Gen.et Technol.privee Cours Versaillais;28 Rue Albert Joly;78000;Versailles;48.8102821;2.139174 +9830416K;Ecole Gen.et Technol.privee Da Costa;Imm.galaxie 71 Av Benebig;98800;Noumea;0.0;0.0 +0311758T;Ecole Gen.et Technol.privee De Photo Et D'audiovisuel;7 Rue Eugene Labiche;31200;Toulouse;43.6283648;1.4348721 +0596808E;Ecole Gen.et Technol.privee Des Met Du Sport Professionnel;Grand Rue;59780;Camphin En Pevele;50.5934675;3.2630624 +9711174Z;Ecole Gen.et Technol.privee Ecole Jean Roumain;15 Rue De La Liberte;97150;St Martin;43.1913245;0.12305 +9830634X;Ecole Gen.et Technol.privee Epsilon Etudes;74 Bis Rue Rene Milliards;98800;Noumea;-22.2758;166.458 +0101094K;Ecole Gen.et Technol.privee Estac;11 Rue Marie Curie;10000;Troyes;48.2716992;4.0753397 +0442618G;Ecole Gen.et Technol.privee Etu.di.a;Plaine De Jeux De La Joneliere;44240;La Chapelle Sur Erdre;47.299576;-1.550638 +0752904Z;Ecole Gen.et Technol.privee Lafayette;16 Rue Du Mail;75002;Paris 02;48.8668132;2.3427 +0530936E;Ecole Gen.et Technol.privee Moliere;6 Bis Rue Ambroise Pare;53000;Laval;48.0703895;-0.7677541 +0783355Y;Ecole Gen.et Technol.privee Perceval;5 Av D Epremesnil;78400;Chatou;48.8872505;2.1584939 +0301767H;Ecole Gen.et Technol.privee Sports Etudes Concept;Chemin Des Hauts De Nimes;30900;Nimes;43.8632132;4.2957487 +0352337A;Ecole Louis De Broglie;Campus De Ker Lann;35170;Bruz;48.0477814;-1.7425205 +0171157R;Ecole Militaire Enseignement Technique;Base Aerienne 722 Saintes Air;17136;Saintes;45.744175;-0.633389 +0161158X;Ecole Militaire Preparatoire;Base Aerienne 709;16109;Cognac;45.657776;-0.3136109 +0171333G;Ecole Militaire Preparatoire;Base Aerienne 721;17133;Rochefort;44.5163889;4.8611111 +0131596P;Ecole Militaire Preparatoire Poilus (bd Des);13 Boulevard Des Poilus;13617;Aix En Provence;43.5267509;5.4595152 +0610055D;Ecole Nat Prof Des Haras Le Pin Au Haras;Le Pin Au Haras;61310;Le Pin Au Haras;48.738069;0.149685 +0755204Z;Ecole Nationale De Commerce;70 Bd Bessières;75017;Paris 17;0.0;0.0 +0312069F;Ecole Nationale De La Meteorologie;42 Avenue Gaspard Coriolis;31057;Toulouse;43.5775472;1.376741 +0350095N;Ecole Nationale De La Sante Publique;Av Du Professeur Leon Bernard;35043;Rennes;48.117507;-1.7000516 +0311256X;Ecole Nationale De L'aviation Civile De Toulouse;7 Avenue Edouard Belin;31055;Toulouse;43.565156;1.479281 +0753478Y;Ecole Nationale Des Chartes;19 Rue De La Sorbonne;75005;Paris 05;48.8486428;2.3430165 +0753493P;Ecole Nationale Des Mines De Paris;60 Bld Saint-michel;75006;Paris 06;48.8455131;2.339794 +0753501Y;Ecole Nationale Des Ponts Et Chaussees;28 Rue Des Saints Peres;75007;Paris 07;48.8556079;2.3310429 +0772517T;Ecole Nationale Des Ponts Et Chaussees;6 Av B Pascal Cite Descartes;77420;Champs Sur Marne;48.852775;2.602651 +0772496V;Ecole Nationale Des Sciences Geographiques;6-8 Av B Pascal Cite Descartes;77455;Champs Sur Marne;48.852775;2.602651 +0692587M;Ecole Nationale Des Travaux Maritimes;Rue Maurice Audin;69518;Vaulx En Velin;45.7790055;4.9221344 +0692566P;Ecole Nationale Des Travaux Publics De L'etat;Rue Maurice Audin;69518;Vaulx En Velin;45.7790055;4.9221344 +0290119X;Ecole Nationale D'ingenieurs De Brest;Technopole Brest Iroise;29608;Brest;48.3651848;-4.5489314 +0420093Y;Ecole Nationale D'ingenieurs De Saint-etienne;58 Rue Jean Parot;42023;St Etienne;45.430467;4.4237117 +0650048Z;Ecole Nationale D'ingenieurs De Tarbes;47 Avenue D'azereix;65016;Tarbes;43.224813;0.0526893 +0341652L;Ecole Nationale Du Genie Rural Des Eaux Et Des Forets;648 Rue Jean-francois Breton;34093;Montpellier;43.6462081;3.8767159 +0540137F;Ecole Nationale Du Genie Rural Des Eaux Et Des Forets;14 Rue Girardet;54042;Nancy;48.6937927;6.1878434 +0631920V;Ecole Nationale Du Genie Rural Des Eaux Et Des Forets;24 Avenue Des Landais;63172;Aubiere;45.7640856;3.1123259 +0753503A;Ecole Nationale Du Genie Rural Des Eaux Et Des Forets;19 Avenue Du Maine;75732;Paris 15;48.843847;2.320594 +0910684Z;Ecole Nationale Sup Des Industries Agricoles Et Alimentaires;1 Rue Des Olympiades;91300;Massy;48.732702;2.2953659 +0340131H;Ecole Nationale Superieure Agronomique De Montpellier;2 Place Pierre Viala;34060;Montpellier;43.6170101;3.8548718 +0350087E;Ecole Nationale Superieure Agronomique De Rennes;65 Rue De Saint Brieuc;35042;Rennes;48.1136527;-1.7053288 +0753472S;Ecole Nationale Supérieure D'art Et Métiers, Paris;151, Bld De L'hopital;75013;Paris 13;48.8339145;2.3573451 +0753237L;École Nationale Supérieure D'arts Et Métiers Paris;151, Bd De L'hopital;75013;Paris 13;48.8339145;2.3573451 +0410981U;Ecole Nationale Superieure De La Nature Et Du Paysage;5-7 Rue Des Grands Champs;41029;Blois;47.5660138;1.3272299 +0590339Y;Ecole Nationale Superieure Des Arts Et Metiers De Lille;8 Boulevard Louis Xiv;59046;Lille;50.6282777;3.0711917 +0420094Z;Ecole Nationale Superieure Des Mines De Saint Etienne;158 Cours Fauriel;42023;St Etienne;45.422755;4.408854 +0751878J;Ecole Nationale Supérieure Des Techniques Avancées Paris;32 Bld Victor;75015;Paris 15;48.8342821;2.2835648 +0753510H;Ecole Nationale Supérieure Des Télécommunications Paris;46 Rue Barrault;75634;Paris 13;48.8262704;2.3464541 +0290124C;Ecole Navale;Lanveoc Poulmic;29160;Lanveoc;48.279167;-4.448333 +0753455Y;Ecole Normale Superieure;45, Rue D'ulm;75005;Paris 05;48.8418453;2.3440345 +0940607Z;Ecole Normale Supérieure De Cachan;61, Avenue Du Président Wilson;94230;Cachan;0.0;0.0 +0920812H;Ecole Normale Superieure De Fontenay Saint-cloud;31, Avenue Lombard;92266;Fontenay Aux Roses;48.789776;2.287181 +0693259T;Ecole Normale Superieure De Sciences Lyon;47 Allee D'italie;69364;Lyon 07;45.7298048;4.8280041 +0951819L;Ecole Phys Math Industrielles;32 Boulevard Du Port;95092;Cergy;49.0322975;2.0640348 +0911568K;Ecole Polytechnique;;91128;Palaiseau;48.7057547;2.219477 +0920674H;Ecole Polytechnique Feminine;3 Bis Rue Lakanal;92330;Sceaux;48.7786376;2.3035209 +0260111E;Ecole Pr D'esthestique/coiffur Epec Beatrice Auger;13 Rue Mirabel Chambaud;26000;Valence;44.9310141;4.8878778 +0333124S;Ecole Pr Fc Girondins Bx Fc Girondins Bordeaux;Rue Joliot Curie;33187;Le Haillan;44.8776541;-0.672552 +0133147A;Ecole Pr Internat. Esthetique Francoise Morice;28 Rue Pierre Et Marie Curie;13100;Aix En Provence;43.5324645;5.4480192 +0331574G;Ecole Pr Pigier;23 Quai De Paludate;33800;Bordeaux;44.8292492;-0.5550825 +0753486G;Ecole Pratique Des Hautes Etudes;45 Rue Des Ecoles;75005;Paris 05;48.8497216;2.3442935 +0352267Z;Ecole Privée Coif Esthetique Academy Jacques Bedfert;24 Boulevard Solferino;35000;Rennes;48.1042709;-1.6691883 +0861398C;Ecole Privée D Esthetique Agnes Briat;12 Passage Boncenne;86000;Poitiers;46.5835089;0.3405425 +0371561W;Ecole Prof. De Coiffure;213 Rue Febvotte;37000;Tours;47.3754237;0.6818723 +0511678X;Ecole Prof. Ouvriere Agricole;1 Rue Du Levant;51600;Somme Suippe;49.1144052;4.5829985 +0693500E;Ecole Reconversion Prof. Georges Guynemer;37 Rue Challemel Lacour;69364;Lyon 07;45.7299676;4.836691 +0875057Z;Ecole Reg. Metiers De Coiffure;26 Bis Rue Ferdinand Buisson;87000;Limoges;45.8251395;1.2561492 +0022101Y;Ecole Secondaire Haute Picardi Haute Picardie;9 Rue Dachery;02100;St Quentin;49.842455;3.2941865 +0672656Y;Ecole Secondaire Privée Beth Hannah;59 Rue Du Faubourg De Pierre;67000;Strasbourg;48.5896482;7.7450122 +0780812J;Ecole Secondaire Privée British School Of Paris;38 Quai De L Ecluse;78290;Croissy Sur Seine;48.8737663;2.126996 +0671616T;Ecole Secondaire Privée Centre Eshel;19 Rue Schweighaeuser;67000;Strasbourg;48.5862803;7.7635035 +0061636W;Ecole Secondaire Privée Cours Albert Camus;3 Avenue Paderi;06200;Nice;43.6894342;7.2347465 +0061541T;Ecole Secondaire Privée Cours Du Prado;4 Avenue Prince De Galles;06400;Cannes;43.5583829;7.022587 +0061243U;Ecole Secondaire Privée Cours Frederic Mistral Ii;2 Boulevard Marechal Juin;06800;Cagnes Sur Mer;43.6624639;7.1505143 +0060687P;Ecole Secondaire Privée Cours Henri Iv;5 Rue Gustave Deloye;06000;Nice;43.6993942;7.2696539 +0332756S;Ecole Secondaire Privée Cours Polles;47 Rue Nicot;33300;Bordeaux;44.8337782;-0.5865275 +0831317F;Ecole Secondaire Privée Cours Privé Mirabeau;25 Rue Mirabeau;83000;Toulon;43.1274443;5.9298803 +0830102K;Ecole Secondaire Privée Cours Saint Dominique;Quartier Pre Tuillieres;83170;La Celle;43.393994;6.041015 +0672582T;Ecole Secondaire Privée Cours Saint-thomas D'aquin;Le Mullerhof Muhlbach/bruche;67280;Urmatt;48.52264;7.312944 +0332759V;Ecole Secondaire Privée Crs Secondaire Privé Peret;43 Bis Rue Du Perigord;33160;St Medard En Jalles;44.8992335;-0.6985045 +0920901E;Ecole Secondaire Privée D'aguesseau;16 Rue D Aguesseau;92100;Boulogne Billancourt;48.8443346;2.2363708 +0781901T;Ecole Secondaire Privée Dali;45 Rue De Montval;78160;Marly Le Roi;48.8738951;2.0902186 +0371442S;Ecole Secondaire Privée Du Perreux;Chateau Du Perreux;37530;Nazelles Negron;47.4127;0.950414 +0332808Y;Ecole Secondaire Privée Dwight Moody;247 Boulevard Marechal Leclerc;33000;Bordeaux;44.8279564;-0.5935838 +0911496G;Ecole Secondaire Privée E.s.e.d.;88 Rue De La Division Leclerc;91160;Saulx Les Chartreux;48.6897758;2.2623625 +0681792D;Ecole Secondaire Privée Ecole Mathias Grunewald;4 Rue Herzog;68124;Wintzenheim;48.0854575;7.3187032 +0061117G;Ecole Secondaire Privée Institut Mediterraneen D'etude;21 Rue Meyerbeer;06000;Nice;43.6967782;7.2605294 +0881710F;Ecole Secondaire Privée Institution St Dominique;184 Rue Du Clos Mariotte;88460;La Baffe;48.1596058;6.576138 +0831326R;Ecole Secondaire Privée Institution St Joseph;269 Avenue Alphonse Daudet;83300;Draguignan;43.5415395;6.4538543 +0061393G;Ecole Secondaire Privée International School Of Nice;15 Avenue Claude Debussy;06200;Nice;43.6854715;7.1997893 +0332919U;Ecole Secondaire Privée Jacques Prevert;173 Rue Du Jardin Public;33300;Bordeaux;44.8562027;-0.5732713 +0781902U;Ecole Secondaire Privée Jeanne D'arc;35 Rue Remilly;78000;Versailles;48.8115151;2.1400778 +0110840D;Ecole Secondaire Privée La Clarte-dieu;Le Cammazou;11270;Fanjeaux;43.195433;2.049444 +0573122J;Ecole Secondaire Privée L'etoile Du Matin;112 Route De Waldeck;57230;Eguelshardt;49.0251882;7.5131059 +0060692V;Ecole Secondaire Privée Lycée Privé Michelet;48 Rue Gioffredo;06000;Nice;43.6992502;7.272996 +0950757G;Ecole Secondaire Privée Montaigne;2-4 Rue Etienne Fourmont;95220;Herblay;48.9916461;2.1607171 +0061392F;Ecole Secondaire Privée Mougins School;615 Avenue Docteur Donat;06250;Mougins;43.6112691;7.0172605 +0022006V;Ecole Secondaire Privée Notre Dame Des Victoires;Rue Du Chateau;02120;Le Herie La Vieville;49.824039;3.647624 +0332601Y;Ecole Secondaire Privée Notre Dame Du Rosaire;Les Cordeliers;33490;St Macaire;44.561713;-0.231533 +0061229D;Ecole Secondaire Privée Or Torah;2 Av Villebois Mareuil;06000;Nice;43.7096465;7.272545 +0572029W;Ecole Secondaire Privée Pilatre De Rozier;6 Rue De L'ermitage;57160;Lessy;49.1192846;6.0963774 +0110912G;Ecole Secondaire Privée Saint Joseph Des Carmes;Les Carmes;11290;Montreal;43.2145699;2.1848932 +0922221P;Ecole Secondaire Privée St Bernard;1 Pl Des Trois Freres Rocquigny;92400;Courbevoie;48.8965949;2.2595784 +0782131T;Ecole Secondaire Privée St Dominique;18 20 Avenue Charles De Gaulle;78230;Le Pecq;48.8874253;2.1020683 +0360671J;Ecole Secondaire Privée St Michel;5 Rue Du Chateau;36250;Niherne;46.8363903;1.5639221 +0920930L;Ecole Secondaire Privée St Pie X;19 Rue Des Ecoles;92210;St Cloud;48.8417127;2.2178743 +0561404X;Ecole Secondaire Privée St Thomas D'aquin;Pontcalec;56240;Berne;47.99546;-3.393066 +0352236R;Ecole Secondaire Privée Ste Marie;Le Bois Martin;35430;St Pere;48.5885182;-1.9175387 +0910844Y;Ecole Secondaire Prof Privée Mg Formation - Escade;98 Allee Des Champs Elysees;91042;Evry;48.63019;2.4243841 +0021486E;Ecole Secondaire Prof.privee;3 Rue De La Mairie;02850;Courtemont Varennes;49.054245;3.5755585 +0910847B;Ecole Secondaire Prof.privee Air-france;Centre D Instruction Vilgenis;91300;Massy;48.730756;2.27137 +0060810Y;Ecole Secondaire Prof.privee Balzac;53 Boulevard Carnot;06400;Cannes;43.5586532;7.0166352 +0110063J;Ecole Secondaire Prof.privee Beaute Et Coiffure;10 Rue Du Luxembourg;11100;Narbonne;43.1809088;3.0021156 +0133404E;Ecole Secondaire Prof.privee C.n.a.t. (coiffure Esth.);Rue Paul Marcel Les Rayettes;13500;Martigues;43.404811;5.053728 +0512015N;Ecole Secondaire Prof.privee Capucine;28 Bis Rue De Courcelles;51100;Reims;49.2603658;4.0195699 +0780026E;Ecole Secondaire Prof.privee Ccip;25 Avenue Des Robaresses;78570;Andresy;48.9871366;2.0579322 +0711717F;Ecole Secondaire Prof.privee Chatelet Coiffure;6 Et 15 Rue St Georges;71100;Chalon Sur Saone;46.7818229;4.8552658 +0061637X;Ecole Secondaire Prof.privee Coiffure Ecole Balzac;28 Boulevard Carabacel;06000;Nice;43.7044827;7.2738384 +0133514Z;Ecole Secondaire Prof.privee Coiffure Esthetique A.f.q.a.;1 Rue Oswald Ortis;13500;Martigues;43.4018692;5.0578334 +0022026S;Ecole Secondaire Prof.privee Coiffure Et Esthetique;19 Bis Boulevard Leon Blum;02100;St Quentin;49.8412959;3.292847 +0133229P;Ecole Secondaire Prof.privee Coiffure Et Esthetique;3 Impasse De L Escouniere;13127;Vitrolles;43.450964;5.2466691 +0332588J;Ecole Secondaire Prof.privee Coiffure Et Esthetique;161 Rue Guillaume Leblanc;33000;Bordeaux;44.8312081;-0.5964205 +0831412J;Ecole Secondaire Prof.privee Coiffure Et Esthetique;1 Rue D Entraigues;83170;Brignoles;43.4059946;6.0612509 +0831393N;Ecole Secondaire Prof.privee Coiffure Performances;80 Avenue Marechal Foch;83000;Toulon;43.1265235;5.9242973 +0060701E;Ecole Secondaire Prof.privee Cours Azur;56 Av Marechal Gallieni;06400;Cannes;43.5586412;7.0176078 +0142159W;Ecole Secondaire Prof.privee Ctre Formation Stade Malherbe;23 Boulevard Georges Pompidou;14064;Caen;49.1808568;-0.3974758 +0371413K;Ecole Secondaire Prof.privee Ctre Promotion De La Coiffure;4 Rue Rene Besnard;37000;Tours;47.3891021;0.699764 +0731207R;Ecole Secondaire Prof.privee Ctre Tech Hotel L'arlequin;Route Nationale 6;73802;Montmelian;45.5068086;6.0799376 +0021592V;Ecole Secondaire Prof.privee D Hotellerie-dosnon;2 Rue De L Eglise;02220;Couvrelles;49.3389541;3.4900193 +0781692R;Ecole Secondaire Prof.privee De Coiffure Des Yvelines;Rue A Honegger Rce Des Forets;78100;St Germain En Laye;48.898908;2.093761 +0110958G;Ecole Secondaire Prof.privee De Prothese Dentaire;2 Rue Simon Castan;11100;Narbonne;43.1849396;2.9975438 +0110981G;Ecole Secondaire Prof.privee D'esthetique;10 Rue Du Luxembourg;11100;Narbonne;43.1809088;3.0021156 +0340927Y;Ecole Secondaire Prof.privee Duclaux;4 Avenue Georges Clemenceau;34000;Montpellier;43.6049943;3.8743094 +0190070Y;Ecole Secondaire Prof.privee Ec Application Travaux Pub;Av Des Papes Limousins;19300;Egletons;45.40101;2.0529799 +0331557N;Ecole Secondaire Prof.privee Ec Tech Esthetique;1 Allee De Chartres;33000;Bordeaux;44.8474789;-0.5712593 +0672682B;Ecole Secondaire Prof.privee Ec.privee Carrieres De La Mode;1 Bis Rue De La Course;67000;Strasbourg;48.5832604;7.7377959 +0332720C;Ecole Secondaire Prof.privee Ec.tech.privee Coiffure;71 Crs Anatole France;33000;Bordeaux;44.8350088;-0.587269 +0641807S;Ecole Secondaire Prof.privee Ecole Coiffure Centre Jorlis;Centre Jorlis;64600;Anglet;43.481402;-1.514699 +0840087N;Ecole Secondaire Prof.privee Ecole Des Arts De La Coiffure;6 Boulevard Saint Michel;84000;Avignon;43.942479;4.8093831 +0061766M;Ecole Secondaire Prof.privee Ecole Europeenne Esthetique;23 Boulevard Dubouchage;06000;Nice;43.7017687;7.2716067 +0681616M;Ecole Secondaire Prof.privee Ecole Vendome;5 Avenue De Colmar;68200;Mulhouse;47.7504281;7.3386179 +0381725U;Ecole Secondaire Prof.privee Elag;31 Rue De La Bajatiere;38100;Grenoble;45.1792916;5.7385752 +0301527X;Ecole Secondaire Prof.privee Espace Robert Bourgier;65 Avenue Jean Jaures;30900;Nimes;43.8276135;4.3533655 +0660756N;Ecole Secondaire Prof.privee Esthet.coiffure Giorgifont Ii;24 24bis Avenue Louis Torcatis;66000;Perpignan;42.7040138;2.890867 +0190834D;Ecole Secondaire Prof.privee Esthetique;22 Rue Andre Devaux;19100;Brive La Gaillarde;45.159555;1.533937 +0341651K;Ecole Secondaire Prof.privee Esthetique Coiffure Giorgifont;16 18 Rue Durand;34000;Montpellier;43.6048143;3.8783242 +0641900T;Ecole Secondaire Prof.privee Esthetique Cosmetique;5bis Et 45 Rue Marechal Joffre;64000;Pau;43.2952632;-0.3731234 +0870913V;Ecole Secondaire Prof.privee Esthetique Cosmetique;2 Bis Rue Gustave Nadaud;87000;Limoges;45.8351859;1.2538931 +0110820G;Ecole Secondaire Prof.privee Esthetique Cosmetique Cybele;8 Boulevard Commandant Roumens;11000;Carcassonne;43.2104305;2.3544704 +0371411H;Ecole Secondaire Prof.privee Etc Esthetique;49 Et 51 Rue Laponneraye;37000;Tours;47.3819579;0.6901009 +0340922T;Ecole Secondaire Prof.privee Etec Formatep;24 26 Avenue Du Pont Juvenal;34000;Montpellier;43.6062402;3.8851375 +0671634M;Ecole Secondaire Prof.privee Europeenne De Beaute;3 Rue De Turenne;67300;Schiltigheim;48.6126471;7.7301404 +0133218C;Ecole Secondaire Prof.privee Football Club;31 Chemin De Paradis;13500;Martigues;43.4077601;5.0489976 +0641911E;Ecole Secondaire Prof.privee Formation Coiffure;Rue Louis Barthou;64000;Pau;43.2944096;-0.3675756 +0211836G;Ecole Secondaire Prof.privee Futura;6 Rue Du Cap Vert;21800;Quetigny;47.3105834;5.0934395 +0382818G;Ecole Secondaire Prof.privee Grenoble Football 38;18 Chemin De La Poterne;38100;Grenoble;45.1726906;5.7475301 +0332868N;Ecole Secondaire Prof.privee I.p.s.o.;77 Rue Lecocq;33000;Bordeaux;44.834317;-0.5862338 +0370759Z;Ecole Secondaire Prof.privee I.s.t.;8 Rue Du Cygne;37000;Tours;47.3955648;0.6912651 +0951707P;Ecole Secondaire Prof.privee Igesa;23 Rue Du General Leclerc;95780;La Roche Guyon;49.0823097;1.6318458 +0131518E;Ecole Secondaire Prof.privee Igesec Ipec;64 Rue Seneque;13300;Salon De Provence;43.6422595;5.0935196 +0190071Z;Ecole Secondaire Prof.privee Inst Brivis Sup Adm Et Commerc;1 Rue Ernest Rupin;19100;Brive La Gaillarde;45.161126;1.5284506 +0831554N;Ecole Secondaire Prof.privee Inst Var Form Metiers Football;Avenue Aristide Briand;83200;Toulon;43.1263887;5.8994285 +0040496T;Ecole Secondaire Prof.privee Institut Avenir Provence;456 Bd St Joseph Z.i.st Joseph;04100;Manosque;43.835744;5.790916 +0542235L;Ecole Secondaire Prof.privee Institut Superieur Decoration;12 Rue De Tivoli;54400;Longwy;49.5293194;5.7649789 +0541921V;Ecole Secondaire Prof.privee Int Esthetique Haute Coiffure;19 Rue Saint Lambert;54000;Nancy;48.687104;6.1665792 +0131426E;Ecole Secondaire Prof.privee Jeanne Michaud;5 Rue Pisancon;13001;Marseille 01;43.2940239;5.3780345 +0370755V;Ecole Secondaire Prof.privee La Chaumette;39 Rue De La Chaumette;37304;Joue Les Tours;47.3579712;0.6500568 +0840090S;Ecole Secondaire Prof.privee Les Cigales;670 Rue Meyne Claire;84100;Orange;44.127041;4.8278237 +0741410F;Ecole Secondaire Prof.privee Maestris Beaute;2 Bis Avenue Zanaroli;74600;Seynod;45.8947154;6.1133921 +0830120E;Ecole Secondaire Prof.privee Objectif 3;154 Avenue Philippe Lebon;83000;Toulon;43.1238546;5.9401956 +0930960N;Ecole Secondaire Prof.privee Ort;39-45 Rue Raspail;93107;Montreuil;48.8528323;2.424645 +0100072Z;Ecole Secondaire Prof.privee Pariset;1 Rue Victorien Sardou;10000;Troyes;48.3001866;4.0506345 +0133211V;Ecole Secondaire Prof.privee Prothesistes Dentaires;11 Av Du General Brosset;13009;Marseille 09;43.2660279;5.4062018 +0341882L;Ecole Secondaire Prof.privee Ruffel;5 Rue Gabriel Peri;34200;Sete;43.4052616;3.6958817 +0341123L;Ecole Secondaire Prof.privee Sarl La Coquille;Route De Boujan;34500;Beziers;43.3663036;3.2315349 +0030867B;Ecole Secondaire Prof.privee Soins Esthetiques;3 Rue President Roosevelt;03200;Vichy;46.1245309;3.4216715 +0332935L;Ecole Secondaire Prof.privee Solange Lourie;8 Cours 30 Juillet;33000;Bordeaux;44.8432096;-0.5743866 +0331597G;Ecole Secondaire Prof.privee St Francois Xavier;181 Rue St F Xavier;33173;Gradignan;44.7753179;-0.5896844 +0341691D;Ecole Secondaire Prof.privee Sup Odontologique;13 Rue Claude Chappe;34000;Montpellier;43.6108708;3.9010094 +0780745L;Ecole Secondaire Tech.privee La Maison;1 Rue Louis Massotte;78530;Buc;48.7737404;2.1264434 +0753574C;Ecole Speciale De Mecanique Et D'electricite;4 Rue Blaise Desgoffe;75006;Paris 06;48.8459266;2.3242783 +0560068V;Ecole Speciale Militaire De Saint Cyr;St Cyr Coetquidan;56381;Guer;47.9394226;-2.1330897 +0772219U;Ecole Sup Infor Et Genie Telec;1 Rue Du Port De Valvins;77215;Avon;48.4247422;2.7446464 +0171523N;Ecole Sup. Innov. Concep. Simu Esics Formation;3 R Alfred Kastler Les Minimes;17000;La Rochelle;46.160329;-1.151139 +0180888T;Ecole Sup.esthetique Du Centre Estec;8d Cours Des Jacobins;18000;Bourges;47.0825143;2.3953601 +0492246A;Ecole Superieure Angevine D'informatique Et De Productique;18 Rue Du 8 Mai 1945;49180;St Barthelemy D Anjou;47.4636468;-0.4970921 +0753560M;Ecole Superieure D Informatique Electronique Automatique;9 Rue Vesale;75005;Paris 05;48.8379228;2.352546 +0490072M;Ecole Superieure D'agriculture D'angers;55 Rue Rabelais;49007;Angers;47.4586985;-0.5450618 +0310154Z;Ecole Superieure D'agriculture De Purpan Toulouse;75 Voie Du Toec;31076;Toulouse;43.6019615;1.4036581 +0951803U;Ecole Superieure De Chimie Organique Et Minerale Escom;13 Boulevard De L'hautil;95092;Cergy;49.03288;2.0788717 +0693623N;Ecole Superieure De Chimie Physique Electronique De Lyon;43 Bd Du 11 Novembre 1918;69616;Villeurbanne;45.7791704;4.8728736 +0920672F;Ecole Superieure De Fonderie;12 Rue Berthelot La Defense;92400;Courbevoie;48.8978987;2.2180596 +0595714R;Ecole Superieure De Metrologie;941 Rue Charles Bourseul;59508;Douai;50.3757844;3.0671778 +0753429V;Ecole Superieure De Physique Et De Chimie Industrielles;10 Rue Vauquelin;75005;Paris 05;48.8413208;2.3477955 +0011293A;Ecole Superieure De Plasturgie D'oyonnax;85 Rue Henri Becquerel;01100;Bellignat;46.242107;5.629371 +0351781W;Ecole Superieure D'electricite;Av De La Boulaie;35511;Cesson Sevigne;48.1230581;-1.6253834 +0911494E;Ecole Superieure D'electricite Privée Supelec;Plateau Du Moulon;91192;Gif Sur Yvette;48.7082355;2.1628765 +0573492L;Ecole Superieure D'electricite Supelec De Metz;2 Rue Edouard Belin;57078;Metz;49.1046825;6.2201426 +0490075R;Ecole Superieure D'electronique De L'ouest;4 Rue Merlet De La Boulaye;49009;Angers;47.478419;-0.563166 +0642012P;Ecole Superieure Des Affaires Groupe Esa Ipa;Rue Jean Zay;64000;Pau;43.3185461;-0.3226804 +0721575W;Ecole Superieure Des Geometres Topographes;1 Rue Pythagore;72000;Le Mans;48.0181117;0.1551443 +0941875C;Ecole Superieure Des Industries Du Caoutchouc;60 Rue Auber;94408;Vitry Sur Seine;48.8026632;2.3746261 +0880077F;Ecole Superieure Des Industries Textiles D'epinal;85 Rue D'alsace;88025;Epinal;48.166412;6.4466764 +0762378X;Ecole Superieure D'ingenieurs En Genie Electrique;1 Rue Du Marechal Juin;76131;Mont St Aignan;49.4630688;1.0638285 +0910725U;Ecole Superieure D'optique;Ctre Scientifique Bat 503;91403;Orsay;48.7068033;2.1741725 +0672881T;Ecole Superieure D'optique;26 Rue Des Magasins;67000;Strasbourg;48.591175;7.7408775 +0442278M;Ecole Superieure Du Bois;Rue Christian Pauc;44306;Nantes;47.2828202;-1.5157572 +0932078D;Ecole Superieure Du Soudage Et De Ses Applications;90 Rue Des Vanesses;95942;Roissy En France;48.9736945;2.5064275 +0492202C;Ecole Superieure Et D Application Du Genie;106 Rue Elbe;49041;Angers;47.4558692;-0.5656686 +0351842M;Ecole Superieure Et D'application Des Transmissions;Avenue De La Touraudais;35510;Cesson Sevigne;48.1212814;-1.6302067 +0921929X;Ecole Superieure Privée;3 Rue Jules Verne;92300;Levallois Perret;48.894939;2.2989649 +0133885C;Ecole Superieure Privée Studio M;29 Boulevard Charles Nedelec;13003;Marseille 03;43.3031966;5.3785285 +0312745R;Ecole Tech Priv D'esthetique Esther Mario;28 Rue De Metz;31000;Toulouse;43.6002386;1.4434844 +0561654U;Ecole Tech Privée Coif Estheti Scotto Di Cesare;2 Allee De Laroiseau;56000;Vannes;47.668463;-2.7838366 +0332918T;Ecole Tech Privée Creasud;8 Rue Du General Cheyron;33100;Bordeaux;44.8398802;-0.5563023 +0292186U;Ecole Techn Privée De Coiffure Chantal Conan;59 Rue De Brest;29800;Landerneau;48.4509184;-4.2574215 +0731479L;Ecole Technique De Coiffure E.c.m. Chambery;357 Faubourg Montmelian;73000;Chambery;45.5663336;5.9304253 +0501515A;Ecole Technique Preparatoire;Ec Tech Armt De La Marine;50100;Cherbourg Octeville;49.633998;-1.613426 +0781858W;Ecole Technique Privé Iscg De Commerce Et De Gestion;76 Rue Du Marechal Lyautey;78100;St Germain En Laye;48.8949513;2.0959937 +0290204P;Ecole Technique Privée Academie Bretagne Coiffure;52 Ter Rue De Douarnenez;29000;Quimper;47.9990273;-4.110778 +0383053M;Ecole Technique Privée Academy;21 Rue Boucher De Perthes;38000;Grenoble;45.1842117;5.7060419 +0271601U;Ecole Technique Privée Alm Evreux Basket;1 Avenue Aristide Briard;27040;Evreux;49.0270129;1.151361 +0292261A;Ecole Technique Privée Anaho Pigier;51 Rue Traverse;29200;Brest;48.3868325;-4.4923533 +0561862V;Ecole Technique Privée Anaho Pigier;87 Rue De Lanveur;56100;Lorient;47.7439993;-3.3914865 +0541973B;Ecole Technique Privée Asnl;Parc De Haye;54840;Velaine En Haye;48.704136;6.069881 +0352178C;Ecole Technique Privée Association Odorico;6 Rue Du Moulin Du Comte;35000;Rennes;48.1084383;-1.7099738 +0530074T;Ecole Technique Privée Beaute Coiffure Formation;38-40 Rue De Paris;53000;Laval;48.0712391;-0.7628592 +0261436V;Ecole Technique Privée Centre De Formation De La Coif;25 Rue Frederic Chopin;26000;Valence;44.9258887;4.9229792 +0791083Y;Ecole Technique Privée Chamois Niortais Football Club;66 Rue H Sellier;79001;Niort;46.3166545;-0.4930179 +0350805K;Ecole Technique Privée Chantal Le Cozic;52 Bd Villebois Mareuil;35000;Rennes;48.105558;-1.6507469 +0430988R;Ecole Technique Privée Coiffure Du Velay;99 Avenue Charles Dupuy;43700;Brives Charensac;45.0487048;3.9209091 +0541361L;Ecole Technique Privée Compagnie De Formation-pigier;43 Cours Leopold;54000;Nancy;48.6972591;6.1737517 +0281111F;Ecole Technique Privée De La Coiffure;15 Rue D'etampes;28000;Chartres;48.4471028;1.5129974 +0602084P;Ecole Technique Privée Des Jeunes Sportifs De L'oise;171 Avenue Marcel Dassault;60000;Beauvais;49.4489162;2.0963607 +0811312L;Ecole Technique Privée Des Lgt Et Lp Privés Notre Dam;Avenue D'hauterive;81101;Castres;43.599188;2.244541 +0312711D;Ecole Technique Privée Du Stade Toulousain;114 Rue Des Troenes;31022;Toulouse;43.620776;1.416174 +0672119P;Ecole Technique Privée Ec Internationle Tunon;15 Rue Du Fosse Des Treize;67000;Strasbourg;48.5879967;7.7473844 +0060907D;Ecole Technique Privée Ec Tech D'esthetique Gontard;47 Rue Hotel Des Postes;06000;Nice;43.6991792;7.2705291 +0331579M;Ecole Technique Privée Ec Tech Jamet Buffereau;67 Cours Pasteur;33800;Bordeaux;44.8326055;-0.5738464 +0641386J;Ecole Technique Privée Ec.d Esthetique;80 Rue De Madrid;64201;Biarritz;43.4673441;-1.5713779 +0261286G;Ecole Technique Privée Epseco;Le Forum 7 Avenue De Verdun;26000;Valence;44.9382727;4.8994976 +0131515B;Ecole Technique Privée Esthetique Parfumerie;149 Rue De Rome;13006;Marseille 06;43.2889789;5.3821494 +0382050X;Ecole Technique Privée Faugier Hays;38 Rue D'alembert;38000;Grenoble;45.1880988;5.7110881 +0881383A;Ecole Technique Privée Formapole-epinal;19 Rue Paul Oulmont;88005;Epinal;48.163398;6.4506308 +0710134K;Ecole Technique Privée Fpt-powertrain Technologies Fr;79, Avenue Puzenat;71140;Bourbon Lancy;46.6261932;3.7495169 +0161183Z;Ecole Technique Privée Hair Styl'mak'up Academy;8 Route De Bordeaux;16000;Angouleme;45.6522967;0.1584877 +0352550G;Ecole Technique Privée Iffdec;8 Quai Robinot De St Cyr;35000;Rennes;48.1055424;-1.717577 +0060777M;Ecole Technique Privée Ilec;12 Boulevard Dubouchage;06000;Nice;43.702245;7.2722329 +0060818G;Ecole Technique Privée Ilec;10 Rue Mozart;06400;Cannes;43.5581488;7.0181123 +0580083A;Ecole Technique Privée Institut Superieur Techniciens;5 Rue Des Francs Bourgeois;58000;Nevers;46.9912616;3.1626878 +0571998M;Ecole Technique Privée Isct;2 Rue Des Parmentiers;57000;Metz;49.1159983;6.177431 +0451468B;Ecole Technique Privée Loiret Orleans Judo;Rue Fernand Pelloutier;45016;Orleans;47.920072;1.888645 +0261392X;Ecole Technique Privée Maestris;19 Avenue Victor Hugo;26000;Valence;44.9294864;4.890829 +0830662U;Ecole Technique Privée Maestris;10 Rue Truguet;83000;Toulon;43.1250211;5.9349305 +0631039M;Ecole Technique Privée Michelin;15 Place Des Carmes;63040;Clermont Ferrand;45.7767324;3.0881041 +0561854L;Ecole Technique Privée Morriss 56;1 Bis Rue Francois D'argouges;56000;Vannes;47.6620426;-2.7567142 +0891118G;Ecole Technique Privée Moulin De Preuilly;Route De Vaux;89000;Auxerre;47.7833102;3.593685 +0301500T;Ecole Technique Privée Nimes Olympique Association;123 Avenue De La Bouvine;30023;Nimes;43.8173886;4.358485 +0292208T;Ecole Technique Privée Oscelorn;59 Rue De Brest;29800;Landerneau;48.4509184;-4.2574215 +0382170C;Ecole Technique Privée Paul Louis Merlin;41 Rue Henri Wallon;38400;St Martin D Heres;45.1738193;5.7608852 +0383347G;Ecole Technique Privée Pigier;5 Avenue Marcellin Berthelot;38000;Grenoble;45.1799156;5.7316907 +0381730Z;Ecole Technique Privée Pro' Style Formation;2 Rue Henri Chatelier;38000;Grenoble;45.1872052;5.7117155 +0783465T;Ecole Technique Privée Psa Peugeot Citroen Et3pc;1 A 3 Bd De L Europe;78300;Poissy;48.9347739;2.0451355 +0660655D;Ecole Technique Privée Rive Gauche;11 Rue Du Docteur Pous;66000;Perpignan;42.6992586;2.889932 +0340937J;Ecole Technique Privée Ruffel;Route Nationale 113;34290;Servian;43.397764;3.3692659 +0133369S;Ecole Technique Privée Saint Francois De Sales;20 Bd Madeleine Remuzat;13384;Marseille 13;43.296482;5.36978 +0131499J;Ecole Technique Privée Soins Esthet Et Coiffure;1 Boulevard Dugommier;13001;Marseille 01;43.2976149;5.3808487 +0341793P;Ecole Technique Privée Studio M;3320 Boulevard Paul Valery;34070;Montpellier;43.5907556;3.8548311 +0132309P;Ecole Technique Privée Susini Esthetique;Le California Bat D;13090;Aix En Provence;43.5388336;5.4044268 +0382896S;Ecole Technique Privée Univeria;27 Rue De Turenne;38100;Grenoble;45.1838167;5.720223 +0711856G;Ecole Technique Privée Univeria;71 Rue Jean Mace;71000;Macon;46.3163972;4.8245756 +0383383W;Ecole Technologique Privée;13 Place Du Triforium;38080;L Isle D Abeau;45.6208808;5.2221078 +0741692M;Ecole Technologique Privée;166 Route De Livron;74100;Vetraz Monthoux;46.1744216;6.2574083 +0731532U;Ecole Technologique Privée Agfp;25 Rue Croix D'or;73000;Chambery;45.5647917;5.9239496 +0693401X;Ecole Technologique Privée Arts Appliques Bellecour;3 Place Bellecour;69002;Lyon 02;45.7575832;4.8320746 +0750106H;Ecole Technologique Privée Ccip Ctre Format Industrielles;247 Avenue Gambetta;75020;Paris 20;48.8755533;2.4057617 +0383349J;Ecole Technologique Privée Charmilles Isf;8 Rue Du Tour De L Eau;38400;St Martin D Heres;45.1865579;5.7726517 +0651026M;Ecole Technologique Privée Concept Formation;54 Avenue Bertrand Barere;65000;Tarbes;43.2373153;0.0712894 +0693451B;Ecole Technologique Privée De Conde;5 Place Gensoul;69002;Lyon 02;45.7517842;4.8243867 +0740283F;Ecole Technologique Privée De Poisy;;74330;Poisy;45.921423;6.063557 +0061542U;Ecole Technologique Privée E.s.c.c.o.m.;22 Rue El Nouzah;06000;Nice;43.7077627;7.2773541 +0754101A;Ecole Technologique Privée Ec Dessin Techn Artist Sornas;108 Rue Saint Honore;75001;Paris 01;48.8615803;2.3418634 +0931906S;Ecole Technologique Privée Ecofih;79 Rue De Paris;93000;Bobigny;48.898946;2.4435126 +0840083J;Ecole Technologique Privée Ecole Hoteliere D Avignon;Allee Des Fenaisons;84032;Avignon;43.9272966;4.8473351 +0596455W;Ecole Technologique Privée Ecole Superieure De Tourisme;70 Rue De Bouvines;59000;Lille;50.6356752;3.0878986 +0160080A;Ecole Technologique Privée Estac;29 Rue Montalembert;16000;Angouleme;45.6465899;0.1677237 +0831631X;Ecole Technologique Privée Esthetique Veronique Harlaut;146 Boulevard Marechal Foch;83000;Toulon;43.1268203;5.9234125 +0763365V;Ecole Technologique Privée Fc Rouen;48 Avenue Des Canadiens;76140;Le Petit Quevilly;49.4115983;1.072111 +0312346G;Ecole Technologique Privée Gamma;1 Rue Felix Debax;31700;Blagnac;43.6323187;1.3975413 +0596770N;Ecole Technologique Privée I.s.c.o.m;7 Av De L'architecte Cordonnier;59000;Lille;50.6355544;3.0440254 +0595877T;Ecole Technologique Privée Institut De Gestion Hoteliere;17 Place Charles Roussel;59200;Tourcoing;50.7215161;3.1568082 +0062035E;Ecole Technologique Privée International Business School;1240 Route Des Dolines;06560;Valbonne;43.6252096;7.0403607 +0061992H;Ecole Technologique Privée Ispecc;21 Boulevard Grosso;06000;Nice;43.6958857;7.2516559 +0731527N;Ecole Technologique Privée Itcc;Place Maurice Mollard;73100;Aix Les Bains;45.688374;5.915667 +0755057P;Ecole Technologique Privée Itecom Inst Techn Communic;12 Rue Du 4 Septembre;75002;Paris 02;48.8695094;2.3374631 +0593035D;Ecole Technologique Privée Kienz;39 Avenue De Flandre;59700;Marcq En Baroeul;50.6653766;3.1073796 +0133283Y;Ecole Technologique Privée Leschi;34 Avenue Sainte Victoire;13100;Aix En Provence;43.5320321;5.4547984 +0383350K;Ecole Technologique Privée Lodima Rhone Alpes;25 Rue De Sassenage;38600;Fontaine;45.1974483;5.6830524 +0752877V;Ecole Technologique Privée Met De La Creation-la Ruche;14 Rue Lally Tollendal;75019;Paris 19;48.8830729;2.3742869 +0062012E;Ecole Technologique Privée Orbicom;2780 Route Nationale 7;06270;Villeneuve Loubet;43.6253366;7.131598 +0311189Z;Ecole Technologique Privée Ort Maurice Grynfogel;14 Rue Etienne Collongues;31770;Colomiers;43.601951;1.333155 +0383393G;Ecole Technologique Privée Performances Concept;;38590;St Etienne De St Geoirs;45.339389;5.34384 +0711884M;Ecole Technologique Privée Pigier;33 Rue De Lyon;71000;Macon;46.3002379;4.8268272 +0741690K;Ecole Technologique Privée Pigier;2 Rue De La Cesiere;74600;Seynod;45.8868104;6.1117354 +0783504K;Ecole Technologique Privée Sportifs De Haut Niveau;4 Bis Avenue Kennedy;78100;St Germain En Laye;48.9146897;2.0854407 +0383369F;Ecole Technologique Privée Sup Formation;22 Cours Senozan;38500;Voiron;45.3654833;5.5909482 +0690693D;Ecole Technologique Privée Supdemod;10 Rue Des Marronniers;69002;Lyon 02;45.7565575;4.8344883 +0312303K;Ecole Technologique Privée Toulouse Arts Appliques;24 Rue Ingres;31000;Toulouse;43.6142705;1.4428243 +0311768D;Ecole Technologique Privée Tunon;3 Rue Albert Lautmann;31000;Toulouse;43.6065141;1.4390729 +0590342B;Emd Douai;941 Rue Charles Bourseul;59508;Douai;50.3757844;3.0671778 +0670189S;Engees Strasbourg;1 Quai Koch;67070;Strasbourg;48.584972;7.7580286 +0492248C;Enihp Angers;2 Rue Le Notre;49045;Angers;47.4791574;-0.6058396 +0631786Z;Enita Clermont-ferrand;Rn 89 Marmilhat;63370;Lempdes;45.7796779;3.17838 +0441679L;Enitiaa Nantes;Rue De La Geraudiere;44322;Nantes;47.264745;-1.56533 +0590338X;Ens Arts Indust Textil Roubaix;2 Pl Martyrs De La Resistance;59070;Roubaix;50.6750202;3.1456381 +0670190T;Ens Arts Industries Strasbourg;24 Boulevard De La Victoire;67084;Strasbourg;48.5822995;7.7649338 +0870862P;Ens Ceramique Iindust Limoges;47 A 73 Av Albert Thomas;87065;Limoges;45.8365048;1.2425704 +0951376E;Ens Electroniq Applicat Cergy;6 Av Du Ponceau;95014;Cergy;49.039012;2.0713216 +0693817Z;Ens Lettres Lyon;15 Parvis Rene Descartes;69007;Lyon 07;0.0;0.0 +0311587G;Ensae Toulouse;10 Avenue Edouard Belin;31055;Toulouse;43.5665729;1.4751039 +0330201P;Ensam Centre D Enseignement Et De Recherche De Bordeaux;Esplanade Des Arts & Metiers;33405;Talence;44.802614;-0.588054 +0710091N;Ensam Centre D Enseignement Et De Recherche De Cluny;Rue Porte De Paris;71250;Cluny;46.434167;4.662638 +0510083N;Ensam Cer Chalons;Rue Saint Dominique;51006;Chalons En Champagne;48.9564418;4.3572928 +0573513J;Ensam Cer Metz;4 Rue Augustin Fresnel;57078;Metz;49.0963218;6.2255916 +9711101V;Enseignement Sup. Technique Formates;4, Rue Achille Rene Boisneuf;97110;Pointe A Pitre;0.0;0.0 +0492247B;Enshap Angers;2 Rue Le Notre;49045;Angers;47.4791574;-0.6058396 +0590341A;Ensiaa Cycle D'etudes En Industries Alimentaires;41 Rue Du Port;59046;Lille;50.6319132;3.0461578 +0310146R;Ensica Toulouse;1 Place Emile Blouin;31056;Toulouse;43.604652;1.444209 +0290125D;Ensieta Brest;2 Rue Francois Verny;29806;Guipavas;48.4198889;-4.4721985 +0920815L;Enspmr Rueil Malmaison;228 232 Av Napoleon Bonaparte;92506;Rueil Malmaison;48.8771709;2.1729069 +0692459Y;Enssib;17 21 Bd Du 11 Novembre 1918;69623;Villeurbanne;45.7795294;4.8611273 +0291811L;Enst Bretagne;Technopole Brest Iroise;29285;Brest;48.3651848;-4.5489314 +0352402W;Enst Bretagne;Rue De La Chataigneraie;35512;Cesson Sevigne;48.1209253;-1.6286523 +0300063F;Enstima Ales;6 Avenue De Clavieres;30319;Ales;44.1331218;4.0883578 +0811200P;Enstimac Albi-carmaux;Campus Jarlart Rte De Teillet;81013;Albi;43.9124307;2.1939809 +0442205H;Enstimn Nantes;4 Rue A Kastler La Chantrerie;44307;Nantes;47.218371;-1.553621 +0100735V;Eplefpa Ets Pub Local Enseig Form Prof Agric;Route De Vielaines;10120;St Pouange;48.221263;4.039121 +0641772D;Epseco;15 Rue Vauban;64100;Bayonne;43.4956281;-1.4801189 +0530939H;Esiea Laval;38 Rue Drs Calmette Et Guerin;53000;Laval;48.077863;-0.770138 +0932019P;Esiee Noisy Le Grand;2 Bd Blaise Pascal Cite Descart;93162;Noisy Le Grand;48.848579;2.55261 +0132396J;Esim Marseille;Technopole De Chateau Gombert;13451;Marseille 13;43.3417;5.43649 +0573389Z;Esitc Metz;6 Rue Marconi Technopole 2000;57070;Metz;49.1005629;6.2173198 +0271338H;Esitpa Rouen;Rue Grande;27106;Val De Reuil;49.2721551;1.2138012 +0753607N;Estp Paris;57 Boulevard Saint Germain;75240;Paris 05;48.8504483;2.3463367 +0783529M;Etab Ens Second Privé Sfef-completude;22 Rue Henri De Regnier;78000;Versailles;48.7934699;2.125999 +0212024L;Etab.national Enseignement Sup;26 Bd Dr Petitjean;21036;Dijon;47.3111605;5.0659413 +0755182A;Etablissement Experimental;48 Avenue Des Gobelins;75013;Paris 13;48.8342909;2.3533682 +0932377D;Etablissement Experimental Auto Ecole;12 Rue De La Liberte;93200;St Denis;48.9438959;2.3653605 +0754401B;Etablissement Experimental Lycée Autogere;393 Rue De Vaugirard;75015;Paris 15;48.8348375;2.2918472 +0772586T;Etablissement Experimental Micro Lycée De Senart;Rue Du Lycée;77552;Moissy Cramayel;0.0;0.0 +0932375B;Etablissement Experimental Nouvelles Chances;25 Rue Des Trois Noyers;93220;Gagny;48.8874111;2.5425454 +0932376C;Etablissement Experimental Nouvelles Chances;146 Av Henri Barbusse;93000;Bobigny;48.9083628;2.42744 +0932396Z;Etablissement Experimental Nouvelles Chances;10 Ave Charles De Gaulle;93152;Le Blanc Mesnil;48.9438125;2.4679399 +0870581J;Ets Public Local Enseignement Agricole Vaseix;Les Vaseix;87430;Verneuil Sur Vienne;45.836771;1.172854 +0762917H;Formavenir Formavenir;28 Place Saint Marc;76000;Rouen;49.4385493;1.1018603 +0341704T;Gpt Interet Public Reclus;17 Rue Abbe De L Epee;34000;Montpellier;43.621583;3.8741726 +0590348H;Hautes Etudes Industrielles;13 Rue De Toul;59046;Lille;50.6339335;3.0449176 +0352422T;I E S I E L;65 Rue De St Brieuc;35042;Rennes;48.1136527;-1.7053288 +0753488J;I.n.a.l.c.o. De Paris;2 Rue De Lille;75343;Paris 07;48.8577011;2.3325963 +0753483D;Inst Fr Froid Ind Genie Climat;292 Rue Saint Martin;75141;Paris 03;48.8667317;2.3542533 +0341013S;Inst Nat Recherche Agronomique;2 Place Pierre Viala;34060;Montpellier;43.6170101;3.8548718 +0332282B;Inst Sces Nature Agroalimentai De Bordeaux;Rue St Jean;33140;Villenave D Ornon;44.795055;-0.5725998 +0753558K;Inst Scien Math Et Econ Appli;14 Rue Corvisart;75013;Paris 13;48.833382;2.346573 +0161165E;Inst Sup Format Alternance Con Isfac;Z I 3 Espace Victor Hugo;16340;L Isle D Espagnac;45.663854;0.199192 +0171501P;Inst Sup Format Alternance Con Isfac;17 Rue Jean Perrin;17000;La Rochelle;46.1405865;-1.1584497 +0520939N;Inst. Rural D'educ. Et D'orien;Rue Du Baron De Beine;52000;Buxieres Les Villiers;48.106388;5.0360569 +0942034A;Inst.nat.rech.transp.securite;2 Av Du Gal Malleret Joinville;94114;Arcueil;48.805173;2.3442021 +0332473J;Instit Rurale Educ Orientation;Domaine De Lagron;33870;Vayres;44.8964799;-0.319015 +0110678C;Institut Agricole Privé Saint Joseph;Av Andre Chenier La Raque;11303;Limoux;43.053289;2.218142 +0691678Z;Institut Agricole Privé Sandar;392 Chemin De La Sabliere;69579;Limonest;45.8348979;4.7678854 +0590345E;Institut Catholique D'arts Et Metiers;6 Rue Auber;59046;Lille;50.6302567;3.0417529 +0442185L;Institut Catholique D'arts Et Metiers De Nantes;35 Av Champ De Manoeuvre;44470;Carquefou;47.2748684;-1.5086799 +0312421N;Institut Catholique Des Arts Et Metiers De Toulouse;75 Avenue De Grande Bretagne;31300;Toulouse;43.5999437;1.4151779 +0753431X;Institut D'etudes Politiques De Paris;27 Rue Saint Guillaume;75341;Paris 07;48.854072;2.3283597 +0912141H;Institut D'informatique D'entreprise;18 Allee Jean Rostand;91025;Evry;48.6269332;2.4313463 +0870997L;Institut D'ingenierie Informatique De Limoges;43 Rue Ste Anne;87015;Limoges;45.8189643;1.2712663 +0631833A;Institut FranÇais De Mecanique Avancee De Clermont-ferrand;Campus Des Cezeaux;63175;Aubiere;45.7690647;3.1153321 +0753465J;Institut National Agronomique Paris Grignon;16 Rue Claude Bernard;75005;Paris 05;48.839766;2.347641 +0690192J;Institut National Des Sciences Appliquees De Lyon;20 Avenue Albert Einstein;69621;Villeurbanne;45.7824151;4.8777701 +0350097R;Institut National Des Sciences Appliquees De Rennes;20 Avenue Des Buttes De Coesmes;35043;Rennes;48.1225975;-1.6377774 +0760165S;Institut National Des Sciences Appliquees De Rouen;Place Emile Blondel;76131;Mont St Aignan;49.458089;1.069146 +0310152X;Institut National Des Sciences Appliquees De Toulouse;135 Avenue De Rangueil;31077;Toulouse;43.5703224;1.4679205 +0910685A;Institut National Des Sciences Et Techniques Nucleaires;Centre D Etudes De Saclay;91191;Gif Sur Yvette;48.7099264;2.164881 +0911781S;Institut National Des Telecommunications;9 Rue Fourier;91000;Evry;48.6254354;2.4432663 +0381912X;Institut National Polytechnique De Grenoble;46 Avenue Felix Viallet;38031;Grenoble;45.1907112;5.7174034 +0541564G;Institut National Polytechnique De Nancy;2 Avenue De La Foret De Haye;54501;Vandoeuvre Les Nancy;48.6543335;6.1342988 +0311381H;Institut National Polytechnique De Toulouse;Place Des Hauts Murats;31006;Toulouse;43.5945193;1.4471792 +0352347L;Institut National Superieur De Formation Agro-alimentaire;65 Rue De St Brieuc;35042;Rennes;48.1136527;-1.7053288 +0900384D;Institut Polytechnique De Sevenans;Rue Du Chateau;90010;Belfort;47.6410762;6.8386557 +0763212D;Institut Rural De Hte Normandi Totes;11 Rue Du General Leclerc;76890;Totes;49.6775732;1.050514 +0611136D;Institut Sup De Plasturgie;Montfoulon;61250;Damigny;48.4450915;0.0590041 +0600071B;Institut Superieur Agricole De Beauvais;19 Rue Pierre Waguet;60026;Beauvais;49.4611762;2.0685274 +0753559L;Institut Superieur D Electronique De Paris;28 Rue Notre Dame Des Champs;75006;Paris 06;48.845356;2.328294 +0590343C;Institut Superieur D'agriculture;41 Rue Du Port;59046;Lille;50.6319132;3.0461578 +0692353H;Institut Superieur D'agriculture Rhone Alpes;31 Place Bellecour;69288;Lyon 02;45.7575832;4.8320746 +0292125C;Institut Superieur D'electronique De Bretagne;20 Rue Cuirasse Bretagne;29604;Brest;48.4066763;-4.4959757 +0831458J;Institut Superieur D'electronique De La Mediterranee;Place Georges Pompidou;83000;Toulon;43.120804;5.937864 +0590347G;Institut Superieur D'electronique Du Nord;41 Boulevard Vauban;59046;Lille;50.6342668;3.0487049 +0721484X;Institut Superieur Des Materiaux Du Mans;44 Av Bartholdi;72000;Le Mans;48.0199119;0.1574886 +0930603A;Institut Superieur Des Materiaux Et Construction Mecanique;3, Rue Fernand Hainaut;93430;Villetaneuse;48.9192718;2.3327857 +0061874E;Institut Superieur D'informatique Et D'automatique;Rue Claude Daunesse;06904;Valbonne;43.6156514;7.051952 +0130238N;Institut Superieur Du Beton Arme-ccimp Groupe Esim Imt;Imt Technopole Chateau Gombert;13451;Marseille 13;43.296482;5.36978 +0693364G;Institut Textile Et Chimique De Lyon;87 Chemin Des Mouilles;69134;Dardilly;45.7863735;4.7690268 +0171552V;Int Rur Educat Et Orientation De La Saintonge Et De L Aunis;15 Avenue De Saintes;17240;St Genis De Saintonge;45.4831161;-0.5674628 +0351780V;Ipssa;Les Hairies;35370;Etrelles;48.055495;-1.1761569 +0351949D;Ipssa;12 Avenue Du Mail;35130;La Guerche De Bretagne;47.9402114;-1.2314923 +0351963U;Ipssa;2 Allee De La Hodeyere;35504;Vitre;48.119326;-1.2031957 +0133347T;Ismea Marseille;Technopole De Chateau Gombert;13451;Marseille 13;43.3417;5.43649 +9830289X;Itfm Nouvelle Caledonie;Rue P Sauvan Anse Vata;98807;Noumea;-22.2976224;166.4471083 +0801885P;Iufm;49 Boulevard De Chateaudun;80044;Amiens;49.8826199;2.2835252 +0133393T;Iufm Aix Marseille;32 Rue Eugene Cas;13004;Marseille 04;43.3129449;5.3982588 +9710939U;Iufm Antilles Guyane;Morne Ferret;97159;Pointe A Pitre;16.241111;-61.533056 +0332826T;Iufm Aquitaine;160 Av De Verdun;33705;Merignac;44.8393097;-0.6294005 +0251762E;Iufm Besancon;Fort Griffon;25042;Besancon;47.242863;6.019874 +0142158V;Iufm Caen;186 Rue De La Delivrande;14053;Caen;49.1997374;-0.3587701 +0631821M;Iufm Clermont Ferrand;20 Avenue Raymond Bergougnan;63039;Clermont Ferrand;45.7833384;3.0703968 +7200164R;Iufm Corse;2 Rue De L'eglise;20250;Corte;42.3048203;9.1497992 +0941936U;Iufm Creteil;Route De Brevannes;94388;Bonneuil Sur Marne;48.7656805;2.5144979 +0211960S;Iufm Dijon;Maison Univ.esplanade Erasme;21009;Dijon;47.3141252;5.0663861 +9830491S;Iufm Du Pacifique;15 Rue De Verdun,bp Mga1;98802;Noumea;-22.2758;166.458 +0382955F;Iufm Grenoble;30 Avenue Marcelin Berthelot;38000;Grenoble;45.1763086;5.7317449 +9741061K;Iufm La Reunion;Allee Des Aigues Marines;97487;St Denis;-20.8939781;55.4425462 +0595851P;Iufm Lille;2 Bis Rue Parmentier;59650;Villeneuve D Ascq;50.6399807;3.1349929 +0871012C;Iufm Limoges;209 Bd De Vanteaux;87036;Limoges;45.823034;1.2270492 +0693480H;Iufm Lyon;5 Rue Anselme;69317;Lyon 04;45.77362;4.8210967 +0341818S;Iufm Montpellier;2 Place Marcel Godechot;34092;Montpellier;43.6198978;3.8692638 +0542255H;Iufm Nancy Metz;5 Rue Paul Richard;54320;Maxeville;48.7104439;6.1644578 +0442199B;Iufm Nantes;4 Chemin Launay Violette;44322;Nantes;47.2497938;-1.5579682 +0061758D;Iufm Nice;89 Avenue Georges V;06046;Nice;43.7135641;7.2699222 +0451482S;Iufm Orleans Tours;72 R Du Faubourg De Bourgogne;45044;Orleans;47.9022511;1.9264231 +0754445Z;Iufm Paris;10 Rue Molitor;75016;Paris 16;48.8452554;2.2652208 +0861249R;Iufm Poitiers;22 Rue De La Tranchee;86000;Poitiers;46.5764592;0.3333732 +0511935B;Iufm Reims;23 Rue Clement Ader;51685;Reims;49.2325574;4.0695783 +0352291A;Iufm Rennes;153 Rue De St Malo;35043;Rennes;48.1215596;-1.6831814 +0762952W;Iufm Rouen;2 Rue Du Tronquet;76131;Mont St Aignan;49.4703815;1.0767393 +0672635A;Iufm Strasbourg;200 Avenue De Colmar;67100;Strasbourg;48.558093;7.7482963 +0312299F;Iufm Toulouse;56 Avenue De L'urss;31078;Toulouse;43.5806199;1.4494941 +0781938H;Iufm Versailles;45 Avenue Des Etats Unis;78000;Versailles;48.8106931;2.1485567 +0470817H;L Professionnel Ste Genevieve Ctr.etu.fem.rur.ste Genevieve;19 Rue Felix;47220;Astaffort;44.0641604;0.6531217 +0441783Z;L.e.p.agricole Privé Lp Agric Pr La Marchanderie;;44150;Ancenis;47.365464;-1.177491 +0180098J;La Compagnie De Formation Pigier;29 Boulevard Gambetta;18000;Bourges;47.0880089;2.3899259 +0311429K;Laboratoire Automatique;7 Avenue Du Colonel Roche;31400;Toulouse;43.5637248;1.4765815 +0311430L;Laboratoire Ctre D'hematologie;Place Du Docteur Baylac;31300;Toulouse;43.6078649;1.3973901 +0311473H;Laboratoire De Biochimie Et Genetique Ce;118 Route De Narbonne;31062;Toulouse;43.5631577;1.4628392 +0311431M;Laboratoire De Pharmacologie Toxicologie;205 Route De Narbonne;31078;Toulouse;43.5564738;1.4656272 +0311274S;Laboratoire D'optique;29 Rue Jeanne Marvig;31055;Toulouse;43.5779576;1.4639799 +0090467J;Laboratoire Souterrain;;09200;Moulis;42.961685;1.091185 +0580055V;Legta De Nevers;243 Route De Lyon;58000;Challuy;46.9475361;3.1579337 +0331493U;Lg Pr Lyc Des Metiers St Genes;160 Rue De Saint Genes;33081;Bordeaux;44.8249282;-0.5813679 +0120024L;Lgt Lycée Des Metiers Alexis Monteil;14 Rue Carnus;12034;Rodez;44.3559366;2.5738937 +0671509B;Lgt Lycée Des Metiers Alphonse Heinrich;123 Route De Strasbourg;67504;Haguenau;48.8001884;7.7656679 +0730029K;Lgt Lycée Des Metiers Ambroise Croizat;244 Avenue De La Liberation;73604;Moutiers;45.4828426;6.5291499 +0601864A;Lgt Lycée Des Metiers Andre Malraux;1 Place Nelson Mandela;60160;Montataire;49.2597525;2.425907 +0060037H;Lgt Lycée Des Metiers Beau Site;38 Avenue D'estienne D'orves;06050;Nice;43.701575;7.245204 +0800001S;Lgt Lycée Des Metiers Boucher De Perthes;1 Rue Paul Delique;80142;Abbeville;50.1023384;1.8422737 +0460010L;Lgt Lycée Des Metiers Champollion;13 15 Av Fernand Pezet;46106;Figeac;44.6090478;2.0281224 +0460007H;Lgt Lycée Des Metiers Clement Marot;59 Rue Des Augustins;46005;Cahors;44.4499879;1.4392957 +0020050U;Lgt Lycée Des Metiers Condorcet;Rond Point Joliot Curie;02100;St Quentin;49.861713;3.293589 +0640044A;Lgt Lycée Des Metiers De La Chimie Albert Camus;Avenue Pierre Angot;64150;Mourenx;43.3703832;-0.6251249 +0570319M;Lgt Lycée Des Metiers De L'industrie;Les Grands Bois;57703;Hayange;49.3168954;6.0671076 +0641779L;Lgt Lycée Des Metiers Du Pays De Soule;Avenue Jean Monnet;64130;Cheraute;43.2265756;-0.8742488 +0570087K;Lgt Lycée Des Metiers Et Des Techno Innov. C. Jully;59 Rue Marechal Foch;57501;St Avold;49.1057612;6.6927566 +0880153N;Lgt Lycée Des Metiers Filiere Bois Andre Malraux;Rue De L'epinette;88204;Remiremont;48.021875;6.5831674 +0020031Y;Lgt Lycée Des Metiers Frederic Et Irene Joliot Curie;1 Place Du Pigeon Blanc;02500;Hirson;49.9200919;4.084192 +0570099Y;Lgt Lycée Des Metiers Henri Nomine;60 R Du Marechal Foch;57215;Sarreguemines;49.1165272;7.0802942 +0470003Y;Lgt Lycée Des Metiers Jean Baptiste De Baudre;5 Allee Pierre Pomarede;47916;Agen;44.203142;0.616363 +0600040T;Lgt Lycée Des Metiers Jean Calvin;Mont Saint Simeon;60400;Noyon;49.580638;3.019981 +0030038A;Lgt Lycée Des Metiers Jean Monnet;39 Place Jules Ferry;03401;Yzeure;46.5660374;3.3552285 +0801853E;Lgt Lycée Des Metiers Jean Racine;541 Rue Pasteur;80500;Montdidier;49.6533011;2.5807174 +0110007Y;Lgt Lycée Des Metiers Jules Fil;Boulevard Joliot Curie;11021;Carcassonne;43.2148527;2.3675002 +0630021F;Lgt Lycée Des Metiers La Fayette;21 Boulevard Robert Schuman;63002;Clermont Ferrand;45.7626229;3.1315173 +0320067Z;Lgt Lycée Des Metiers Le Garros;1 Bis Rue Darwin;32021;Auch;43.6317386;0.5885775 +0260113G;Lgt Lycée Des Metiers Les Catalins;24 Avenue Des Catalins;26216;Montelimar;44.5696163;4.7576998 +0060075Z;Lgt Lycée Des Metiers Les Eucalyptus;7 Avenue Des Eucalyptus;06200;Nice;43.6762904;7.2225405 +0810004P;Lgt Lycée Des Metiers Louis Rascol;10 Rue De La Republique;81012;Albi;43.9318293;2.1542966 +0600020W;Lgt Lycée Des Metiers Marie Curie;Boulevard Pierre De Coubertin;60180;Nogent Sur Oise;49.266627;2.464235 +0730016W;Lgt Lycée Des Metiers Monge;1 Avenue Marius Berroir;73000;Chambery;45.5660699;5.9341975 +0382203N;Lgt Lycée Des Metiers Pablo Neruda;35 Rue Henri Wallon;38400;St Martin D Heres;45.1728602;5.7587385 +0310017A;Lgt Lycée Des Metiers Paul Mathou;Avenue De Luchon;31210;Gourdan Polignan;43.0674443;0.5872643 +0880021V;Lgt Lycée Des Metiers Pierre Mendes France;2 Rue Du Haut Des Etages;88000;Epinal;48.186392;6.455177 +0230025C;Lgt Lycée Des Metiers Raymond Loewy;Place Filderstadt;23300;La Souterraine;46.239087;1.482986 +0630020E;Lgt Lycée Des Metiers Sidoine Apollinaire;20 Rue Jean Richepin;63037;Clermont Ferrand;45.7818105;3.0868666 +0310028M;Lgt Lycée Des Metiers Vincent Auriol;36 Route De Soreze;31250;Revel;43.4563296;2.0116845 +9830537S;Lp Agricole Do Neva (asee-agri);Ecole De Do Neva;98816;Houailou;0.0;0.0 +0672118N;Lp Agricole Privé Schattenmann;88 Grand'rue;67330;Bouxwiller;48.8272341;7.4782172 +0470823P;Lp Des Metiers Vie Rurale Ctr.etu.fem.rur.l Oustal;Rue Paul Sabatier;47300;Villeneuve Sur Lot;44.393685;0.733397 +0741689J;Lp Leap;Site De Chavanod;74650;Chavanod;45.890053;6.03928 +0330018R;Lp Lycée Des Metiers;24 Rue Du Clg Technique;33294;Blanquefort;44.9122244;-0.6202443 +0410718H;Lp Lycée Des Metiers;2 Avenue Jean Magnon;41110;St Aignan;47.266268;1.3718823 +0801534H;Lp Lycée Des Metiers;541 Rue Pasteur;80500;Montdidier;49.6533011;2.5807174 +0950688G;Lp Lycée Des Metiers;71 Avenue De Ceinture;95880;Enghien Les Bains;48.97192;2.2940159 +0421606T;Lp Lycée Des Metiers Adrien Testud;33 Boulevard D'auvergne;42502;Le Chambon Feugerolles;45.3930502;4.3204203 +0370040T;Lp Lycée Des Metiers Albert Bayet;50 Boulevard Preuilly;37058;Tours;47.3929843;0.6707489 +0010001W;Lp Lycée Des Metiers Alexandre Berard;223 Rue Alexandre Berard;01505;Amberieu En Bugey;45.972793;5.3518732 +0910630R;Lp Lycée Des Metiers Alexandre Denis;Ch Montmirault Av Carnot;91590;Cerny;48.483799;2.3326528 +0120037A;Lp Lycée Des Metiers Alexis Monteil;14 Rue Carnus;12034;Rodez;44.3559366;2.5738937 +0660026V;Lp Lycée Des Metiers Alfred Sauvy;Chateau Lagrange;66740;Villelongue Dels Monts;42.526599;2.9025059 +0050005D;Lp Lycée Des Metiers Alpes Et Durance;Quartier De La Robeyere;05200;Embrun;44.5604975;6.4881374 +0332445D;Lp Lycée Des Metiers Alphonse Beau De Rochas;Rue Jean Hameau;33300;Bordeaux;44.8667377;-0.5677874 +0730030L;Lp Lycée Des Metiers Ambroise Croizat;244 Avenue De La Liberation;73604;Moutiers;45.4828426;6.5291499 +0630024J;Lp Lycée Des Metiers Amedee Gasquet;12 Rue Jean Baptiste Torrilhon;63037;Clermont Ferrand;45.7782828;3.0776281 +0410031L;Lp Lycée Des Metiers Andre Ampere;2 Rue Ampere;41107;Vendome;47.8033433;1.082092 +0573211F;Lp Lycée Des Metiers Andre Citroen;5 Rue De 11eme D'aviation;57155;Marly;49.059828;6.154795 +0601870G;Lp Lycée Des Metiers Andre Malraux;1 Place Nelson Mandela;60160;Montataire;49.2597525;2.425907 +0670024M;Lp Lycée Des Metiers Andre Siegfried;12 Rue Des Dominicains;67504;Haguenau;48.8156319;7.7946128 +0870058R;Lp Lycée Des Metiers Antone De Saint Exupery;Route Du Palais;87000;Limoges;45.8499103;1.2977815 +0772244W;Lp Lycée Des Metiers Antonin Careme;1 Place Gustave Courbet;77176;Savigny Le Temple;48.5955639;2.578176 +0011120M;Lp Lycée Des Metiers Arbez Carme;1 Rue Pierre Et Marie Curie;01100;Bellignat;46.2508923;5.6331189 +0940138P;Lp Lycée Des Metiers Armand Guillaumin;Rue Pierre Corneille;94310;Orly;48.749033;2.404065 +0340069R;Lp Lycée Des Metiers Auto Cycles Moto Jacques Brel;15 Avenue De La Gare;34220;St Pons De Thomieres;43.4879072;2.7645737 +0560001X;Lp Lycée Des Metiers B. Du Guesclin;50 Rue Pierre Allio;56406;Auray;47.6840168;-2.9983391 +0820001F;Lp Lycée Des Metiers Batiment Et Topographie;578 Avenue De Gascogne;82500;Beaumont De Lomagne;43.8767017;0.9780313 +0310053P;Lp Lycée Des Metiers Bayard;150 Route De Launaguet;31021;Toulouse;43.6387232;1.4392188 +0060908E;Lp Lycée Des Metiers Beau Site;38 Avenue Estienne D Orves;06050;Nice;43.7002115;7.2489081 +0350102W;Lp Lycée Des Metiers Beaumont;10 Rue Du Lycée;35605;Redon;0.0;0.0 +0770943G;Lp Lycée Des Metiers Benjamin Franklin;Rue De La Foret La Rochette;77012;Melun;48.508725;2.663036 +0800063J;Lp Lycée Des Metiers Boucher De Perthes;1 Rue Paul Delique;80142;Abbeville;50.1023384;1.8422737 +0460032K;Lp Lycée Des Metiers Champollion;13 Avenue Fernand Pezet;46106;Figeac;44.6090478;2.0281224 +0600041U;Lp Lycée Des Metiers Charles De Bovelles;Mont Saint Simeon;60402;Noyon;49.580638;3.019981 +0312217S;Lp Lycée Des Metiers Charles De Gaulle;24 Avenue Charles De Gaulle;31604;Muret;43.4689165;1.3215619 +0340078A;Lp Lycée Des Metiers Charles De Gaulle;38 Rue Robespierre;34200;Sete;43.4110658;3.6790691 +0830661T;Lp Lycée Des Metiers Claret;202 Boulevard Trucy;83000;Toulon;43.1340433;5.9256355 +0930136T;Lp Lycée Des Metiers Claude-nicolas Ledoux;Avenue Du Quatorze Juillet;93320;Les Pavillons Sous Bois;48.9117268;2.4912496 +0460051F;Lp Lycée Des Metiers Clement Marot;59 Rue Des Augustins;46005;Cahors;44.4499879;1.4392957 +0020079A;Lp Lycée Des Metiers Condorcet;Rond Point Joliot Curie;02100;St Quentin;49.861713;3.293589 +0930130L;Lp Lycée Des Metiers Condorcet;31 Rue Desire Chevalier;93105;Montreuil;48.857903;2.446193 +0510069Y;Lp Lycée Des Metiers Croix Cordier;40 Rue Croix Cordier;51434;Tinqueux;49.2460676;3.985723 +0300047N;Lp Lycée Des Metiers D'art Georges Guynemer;Place De Verdun;30703;Uzes;44.0145529;4.4206567 +0240006B;Lp Lycée Des Metiers De L Alba;4 Rue Ch Gonthier;24100;Bergerac;44.8430899;0.493039 +0400019D;Lp Lycée Des Metiers De L Auto Frederic Esteve;Quart. St Medard Rue F.esteve;40010;Mont De Marsan;43.893485;-0.499782 +0640026F;Lp Lycée Des Metiers De L Habitat;25 Rue Louis Barthou;64110;Gelos;43.2838439;-0.3738321 +0470040N;Lp Lycée Des Metiers De L Habitat Louis Couffignal;Rue Du Rooy;47300;Villeneuve Sur Lot;44.396361;0.712915 +0310088C;Lp Lycée Des Metiers De L'ameublement;Plaine Du Laudot;31250;Revel;43.458611;2.004573 +0171571R;Lp Lycée Des Metiers De L'atlantique;2 Rue De Montreal;17200;Royan;45.6336048;-1.0180228 +0220059V;Lp Lycée Des Metiers Des Metiers Jean Moulin;3 Rue Du Vau Gicquel;22022;St Brieuc;48.5255564;-2.7908495 +0690048C;Lp Lycée Des Metiers Diderot;41 Cours General Giraud;69283;Lyon 01;45.7710133;4.8167669 +0120096P;Lp Lycée Des Metiers Du Batiment;2 Avenue Du Lycée;12110;Aubin;0.0;0.0 +0230019W;Lp Lycée Des Metiers Du Batiment;Les Granges Route D'aubussson;23500;Felletin;45.884543;2.173878 +0290130J;Lp Lycée Des Metiers Du Batiment;Rue De Kervern;29190;Pleyben;48.2217709;-3.970686 +0101022G;Lp Lycée Des Metiers Edouard Herriot;Rue De La Maladiere;10302;Ste Savine;48.2935455;4.0271421 +0280864M;Lp Lycée Des Metiers Elsa Triolet;2 Rue Des Ecoles;28110;Luce;48.439743;1.4632135 +0690105P;Lp Lycée Des Metiers Emile Bejuit (automobile);282 Route De Genas;69675;Bron;45.7514458;4.9054389 +0560008E;Lp Lycée Des Metiers Emile James;56 Rue Emile James;56410;Etel;47.6631024;-3.2049371 +0560070X;Lp Lycée Des Metiers Emile Zola;30 Rue Emile Zola;56704;Hennebont;47.8259374;-3.2514182 +0421489R;Lp Lycée Des Metiers Etienne Legrand;8 Boulevard Charles Gallet;42124;Le Coteau;46.0226751;4.0940381 +0080010T;Lp Lycée Des Metiers Etion;Rue Jean De La Fontaine;08000;Charleville Mezieres;49.7789551;4.701555 +0220071H;Lp Lycée Des Metiers Eugene Freyssinet;32 Rue Mansart;22023;St Brieuc;48.5195608;-2.777683 +0510038P;Lp Lycée Des Metiers Europe;71 Avenue De L'europe;51673;Reims;49.2495206;4.0658188 +0110013E;Lp Lycée Des Metiers Francois Andreossy;1 Rue Saint Francois;11494;Castelnaudary;43.3190962;1.9585015 +0370032J;Lp Lycée Des Metiers Francois Clouet;8 Rue Lepage;37081;Tours;47.4285682;0.6913718 +0630012W;Lp Lycée Des Metiers Francois Rabelais;13 Avenue De Charbonnier;63570;Brassac Les Mines;45.4134991;3.3218257 +0451304Y;Lp Lycée Des Metiers Francoise Dolto;125 Rue Claude Debussy;45160;Olivet;47.865765;1.8794481 +0020089L;Lp Lycée Des Metiers Frederic Et Irene Joliot Curie;1 Place Du Pigeon Blanc;02500;Hirson;49.9200919;4.084192 +0300058A;Lp Lycée Des Metiers Frederic Mistral;457 Ancienne Route De Generac;30913;Nimes;43.823195;4.3589827 +0640098J;Lp Lycée Des Metiers Gabriel Haure-place;6 Av Carmel Lasportes;64800;Coarraze;43.177514;-0.2400054 +0830960T;Lp Lycée Des Metiers Gallieni;Rue Marechal Lyautey;83600;Frejus;43.4454017;6.7551626 +0450064A;Lp Lycée Des Metiers Gaudier-brzeska;40 Avenue Denis Papin;45800;St Jean De Braye;47.9115438;1.9462399 +0730039W;Lp Lycée Des Metiers General Ferrie;39 Avenue De La Republique;73140;St Michel De Maurienne;45.2190734;6.4710134 +0010020S;Lp Lycée Des Metiers Georges Charpak;Avenue Charles De Gaulle;01400;Chatillon Sur Chalaronne;46.1254895;4.9607151 +0830058M;Lp Lycée Des Metiers Georges Cisson;272 Rue Andre Chenier;83100;Toulon;43.1292577;5.9614162 +0691626T;Lp Lycée Des Metiers Georges Lamarque;10 Route De Geneve;69140;Rillieux La Pape;45.8037404;4.8823896 +0510036M;Lp Lycée Des Metiers Gustave Eiffel;34 Rue De Neufchatel;51066;Reims;49.275193;4.0269257 +0380036H;Lp Lycée Des Metiers Guynemer;56 Avenue Marcelin Berthelot;38037;Grenoble;45.1705531;5.731918 +0310057U;Lp Lycée Des Metiers Helene Boucher;1 Rue Lucien Lafforgue;31901;Toulouse;43.6132367;1.4345703 +0931193S;Lp Lycée Des Metiers Helene Boucher;70 Avenue Gilbert Berger;93290;Tremblay En France;48.9502883;2.5784449 +0380104G;Lp Lycée Des Metiers Henri Fabre;35 Rue Henri Wallon;38400;St Martin D Heres;45.1728602;5.7587385 +0570123Z;Lp Lycée Des Metiers Henri Nomine;60 R Marechal Foch;57215;Sarreguemines;49.1165272;7.0802942 +0630041C;Lp Lycée Des Metiers Henri Sainte-claire Deville;Chemin Des Croizettes;63504;Issoire;45.5401316;3.2576825 +0421736J;Lp Lycée Des Metiers Hotelier;18 Rue Francois Gillet;42405;St Chamond;45.4546377;4.5045484 +0811144D;Lp Lycée Des Metiers Hotelier;45 Rue Lapeyrouse;81207;Mazamet;43.5031335;2.3830315 +0261148G;Lp Lycée Des Metiers Hotelier De L'hermitage;Rue Jean Monnet;26600;Tain L Hermitage;45.06968;4.860383 +0460529A;Lp Lycée Des Metiers Hoteliers Quercy-perigord;Avenue Roger Couderc;46200;Souillac;44.8950338;1.4700737 +0332194F;Lp Lycée Des Metiers Hotellerie Tourisme E Services;1 Av Roland Dorgeles;33120;Arcachon;44.6510118;-1.1604617 +0301270T;Lp Lycée Des Metiers Industriel Jules Raimu;12 Rue Jules Raimu;30908;Nimes;43.8195221;4.3290667 +0600003C;Lp Lycée Des Metiers J.b. Corot - Batiment;4 Et 6 Rue Henri Lebesgue;60000;Beauvais;49.42964;2.081875 +0332441Z;Lp Lycée Des Metiers Jacques Brel;Rue Jean Lurcat;33305;Lormont;44.8758255;-0.5124773 +0940141T;Lp Lycée Des Metiers Jacques Brel;90-100 Avenue D'alfortville;94600;Choisy Le Roi;48.7737868;2.4151804 +0690043X;Lp Lycée Des Metiers Jacques De Flesselles;15 Rue De Flesselles;69283;Lyon 01;45.7710983;4.8266437 +0771997C;Lp Lycée Des Metiers Jacques Prevert;7 Avenue Jean Jaures;77385;Combs La Ville;48.6273907;2.5860068 +0382271M;Lp Lycée Des Metiers Jean Claude Aubry;Chemin De Rosiere;38303;Bourgoin Jallieu;45.5958819;5.2972999 +0360026H;Lp Lycée Des Metiers Jean D Alembert;8 Rue De La Limoise;36105;Issoudun;46.9567081;2.0000772 +0180009M;Lp Lycée Des Metiers Jean De Berry;;18026;Bourges;47.060833;2.372222 +0650041S;Lp Lycée Des Metiers Jean Dupuy;1 Rue Aristide Berges;65016;Tarbes;43.229533;0.064581 +0400097N;Lp Lycée Des Metiers Jean Garnier;24 Rue Henri Barbusse;40110;Morcenx;44.0401193;-0.9135882 +0180025E;Lp Lycée Des Metiers Jean Guehenno;Rue Des Sables;18200;St Amand Montrond;46.733637;2.5082521 +0430024T;Lp Lycée Des Metiers Jean Monnet;45 Boulevard Bertrand;43000;Le Puy En Velay;45.0368885;3.8870656 +0290001U;Lp Lycée Des Metiers Jean Moulin;27 Rue De La Republique;29780;Plouhinec;48.0182717;-4.5302967 +0400027M;Lp Lycée Des Metiers Jean Taris;Avenue J Dupaya;40300;Peyrehorade;43.5493036;-1.0898504 +0080047H;Lp Lycée Des Metiers Jean-baptiste Clement;11 Rue J Jaures;08200;Sedan;49.6998064;4.9329191 +0931233K;Lp Lycée Des Metiers Jean-baptiste Clement;25 Rue Des Trois Noyers;93220;Gagny;48.8874111;2.5425454 +0880072A;Lp Lycée Des Metiers Jean-charles Pellerin;44 Rue Abel Ferry;88021;Epinal;48.1665516;6.4548634 +0511430C;Lp Lycée Des Metiers Joliot-curie;4 Rue Joliot-curie;51096;Reims;49.2313467;4.0037269 +0420045W;Lp Lycée Des Metiers Joseph Haubtmann;20 Rue Burdeau;42000;St Etienne;45.4457783;4.4108791 +0090006H;Lp Lycée Des Metiers Joseph Marie Jacquard;Rue Jacquard;09300;Lavelanet;42.923053;1.840868 +0670058Z;Lp Lycée Des Metiers Jules Verne;31 Rue Saint Nicolas;67703;Saverne;48.7345469;7.3714457 +0221595P;Lp Lycée Des Metiers La Fontaine Des Eaux;La Fontaine Des Eaux;22102;Dinan;48.4614665;-2.0384413 +0320068A;Lp Lycée Des Metiers Le Garros;1 Bis Rue Darwin;32021;Auch;43.6317386;0.5885775 +0870748R;Lp Lycée Des Metiers Le Mas Jambost;7 Allee Du Marechal Fayolle;87065;Limoges;45.833096;1.222058 +0021477V;Lp Lycée Des Metiers Leonard De Vinci;1 Espace Jean Guerland;02331;Soissons;49.3726602;3.3073808 +0341385W;Lp Lycée Des Metiers Leonard De Vinci;Rue Professeur Blayac;34085;Montpellier;43.6246951;3.8272737 +0693045K;Lp Lycée Des Metiers Les Canuts;2 Rue Ho Chi Minh;69120;Vaulx En Velin;45.7797002;4.9205183 +0260114H;Lp Lycée Des Metiers Les Catalins;24 Avenue Des Catalins;26216;Montelimar;44.5696163;4.7576998 +0060015J;Lp Lycée Des Metiers Les Coteaux;4/6 Che Morgon Av Des Coteaux;06400;Cannes;43.5662949;7.022452 +0060082G;Lp Lycée Des Metiers Les Eucalyptus;7 Avenue Des Eucalyptus;06200;Nice;43.6762904;7.2225405 +0080953T;Lp Lycée Des Metiers Louis Armand;;08440;Vivier Au Court;49.733206;4.830856 +0561507J;Lp Lycée Des Metiers Louis Armand;3 Rue Jean Moulin;56500;Locmine;47.8792681;-2.8294521 +0920171L;Lp Lycée Des Metiers Louis Bleriot;67 Rue De Verdun;92150;Suresnes;48.8742683;2.2303113 +0771171E;Lp Lycée Des Metiers Louis Lumiere;32 Avenue De L'europe;77500;Chelles;48.882983;2.6085512 +0810046K;Lp Lycée Des Metiers Louis Rascol;10 Rue De La Republique;81012;Albi;43.9318293;2.1542966 +0931735F;Lp Lycée Des Metiers Louise Michel;77 Rue De Saint Gratien;93800;Epinay Sur Seine;48.9620907;2.2997924 +0450066C;Lp Lycée Des Metiers Mal Leclerc De Hauteclocque;85 Avenue Georges Clemenceau;45140;St Jean De La Ruelle;47.8960395;1.8605698 +0190027B;Lp Lycée Des Metiers Marcel Barbanceys;Rue De L'artisanat;19160;Neuvic;45.382449;2.272544 +0630054S;Lp Lycée Des Metiers Marie Laurencin;1 Avenue Jean Monnet;63201;Riom;45.9006673;3.1039393 +0560027A;Lp Lycée Des Metiers Marie Le Franc;128 Boulevard Leon Blum;56321;Lorient;47.7456044;-3.3806542 +0370054H;Lp Lycée Des Metiers Martin Nadaud;67 Rue Jeanne Labourbe;37700;St Pierre Des Corps;47.394938;0.7418599 +0870004G;Lp Lycée Des Metiers Martin Nadaud;30 Avenue De La Liberation;87300;Bellac;46.1224081;1.0612588 +0280022X;Lp Lycée Des Metiers Maurice Viollette;Rue Pablo Neruda;28101;Dreux;48.736652;1.345303 +0340043M;Lp Lycée Des Metiers Mendes France;49 Bis Av Georges Clemenceau;34007;Montpellier;43.602191;3.8701314 +0020051V;Lp Lycée Des Metiers Metiers De L Ameublement;Rue Fleming;02100;St Quentin;49.8638229;3.303156 +0751710B;Lp Lycée Des Metiers Nicolas Louis Vauquelin;13-21 Avenue Boutroux;75013;Paris 13;48.8220969;2.3749517 +0060040L;Lp Lycée Des Metiers Pasteur;25 Rue Du Professeur Delvalle;06000;Nice;43.722971;7.2839949 +0670050R;Lp Lycée Des Metiers Paul Emile Victor;1a Avenue De Gail;67210;Obernai;48.4632396;7.4912864 +0310089D;Lp Lycée Des Metiers Paul Mathou;Avenue De Luchon;31210;Gourdan Polignan;43.0674443;0.5872643 +0670006T;Lp Lycée Des Metiers Philippe-charles Goulden;2 Rue De La Piscine;67241;Bischwiller;48.7749993;7.8502476 +0631480S;Lp Lycée Des Metiers Pierre Boulanger;85 Chemin Des Palisses;63430;Pont Du Chateau;45.792027;3.2478132 +0470015L;Lp Lycée Des Metiers Porte Du Lot;Porte Du Lot;47320;Clairac;44.358916;0.375807 +0180049F;Lp Lycée Des Metiers Rene Cassin;41 Bis Rue Charles Hurvoy;18108;Vierzon;47.2279543;2.0673471 +0420029D;Lp Lycée Des Metiers Rene Cassin;Le Marthoret;42800;Rive De Gier;45.522087;4.605514 +0400020E;Lp Lycée Des Metiers Robert Wlerick;6 Rue Jean Mace -;40005;Mont De Marsan;43.8969788;-0.5144499 +0630022G;Lp Lycée Des Metiers Roger Claustres;127 Rue Docteur Hospital;63039;Clermont Ferrand;45.795454;3.0877122 +0310052N;Lp Lycée Des Metiers Roland Garros;32 Rue Mathaly;31200;Toulouse;43.6290045;1.4298117 +0941604H;Lp Lycée Des Metiers Samuel De Champlain;61-91 Rue Des Bordes;94430;Chennevieres Sur Marne;48.7987853;2.5577377 +0281021H;Lp Lycée Des Metiers Sully;8 Rue Des Viennes;28404;Nogent Le Rotrou;48.3245558;0.8218301 +0690010L;Lp Lycée Des Metiers Tony Garnier;235 Boulevard Pinel;69676;Bron;45.7261136;4.8871026 +0770919F;Lp Lycée Des Metiers Uruguay France;1 Avenue Des Marronniers;77211;Avon;48.4103009;2.7355078 +0100004A;Lp Lycée Des Metiers Val More;Avenue Bernard Pieds;10110;Bar Sur Seine;48.1121812;4.3867353 +0631668W;Lp Lycée Des Metiers Vercingetorix;4 Rue De Laubize;63540;Romagnat;45.737373;3.106057 +0391131T;Lp Privé Institut Europeen De Form Compagnons Tour De France;2 Rue Leopold Alixant;39330;Mouchard;46.976214;5.797567 +9830401U;Lp Privé St Pierre Chanel La Conception (ddec);Rue Beautemps Beaupre;98874;Le Mont Dore;0.0;0.0 +0331740M;Lpa Viticulture Oenologie La Tour Blanche;Le Bourg;33210;Bommes;44.5613178;-0.3551972 +0731198F;Lpa Costa De Beauregard Site De Chambery Le Bocage;340 Rue Costa De Beauregard;73000;Chambery;45.5633945;5.9311119 +0731199G;Lpa Costa De Beauregard Site De La Ravoire Charmilles;148 Rue L Costa De Beauregard;73490;La Ravoire;45.5639798;5.9610297 +0400141L;Lpa De Sabres;Route De Luglon;40630;Sabres;44.1457175;-0.7411813 +0631669X;Lpo Lycée Des Metiers;Voie Romaine;63400;Chamalieres;45.7782726;3.0496304 +0240026Y;Lpo Lycée Des Metiers Albert Claveille;80 Rue V Hugo;24001;Perigueux;45.1894956;0.7130103 +0031043T;Lpo Lycée Des Metiers Albert Einstein;Rue Albert Einstein;03105;Montlucon;46.3508329;2.604608 +0670087F;Lpo Lycée Des Metiers Alexandre Dumas (hotelier);75 Rte Du Rhin;67404;Illkirch Graffenstaden;48.5734116;7.7971354 +0770938B;Lpo Lycée Des Metiers Andre Malraux;Avenue Du Lycée;77130;Montereau Fault Yonne;0.0;0.0 +0932129J;Lpo Lycée Des Metiers Application De L'enna;Place Du 8 Mai 1945;93203;St Denis;48.939438;2.355926 +0410001D;Lpo Lycée Des Metiers Augustin Thierry;13 Avenue De Chateaudun;41018;Blois;47.5966852;1.3290092 +0750681H;Lpo Lycée Des Metiers Boulle Esaa;9 Rue Pierre Bourdan;75012;Paris 12;48.8468966;2.3914855 +0560016N;Lpo Lycée Des Metiers Broceliande;Bellevue;56383;Guer;47.9401893;-2.1337777 +0640001D;Lpo Lycée Des Metiers Cantau;1 Allee De Cantau;64600;Anglet;43.4758828;-1.5121686 +0521032P;Lpo Lycée Des Metiers Charles De Gaulle;Avenue Christian Pineau;52903;Chaumont;48.0738585;5.1462257 +0681801N;Lpo Lycée Des Metiers Charles De Gaulle;14 Rte De Ruelisheim;68840;Pulversheim;47.8328666;7.3074998 +0772228D;Lpo Lycée Des Metiers Charles De Gaulle;6 Place Jean Mermoz;77230;Longperrier;49.0483392;2.6684576 +0430020N;Lpo Lycée Des Metiers Charles Et Adrien Dupuy;3 Avenue Docteur Durand;43003;Le Puy En Velay;45.0371683;3.8938789 +0560026Z;Lpo Lycée Des Metiers Colbert;117 Boulevard Leon Blum;56101;Lorient;47.7456671;-3.3807475 +0781578S;Lpo Lycée Des Metiers De Hotellerie Et De Tourisme;Place Francois Rabelais;78042;Guyancourt;48.762895;2.075323 +0332870R;Lpo Lycée Des Metiers De La Mer;Port De La Barbotiere;33470;Gujan Mestras;44.635731;-1.071189 +0470020S;Lpo Lycée Des Metiers De La Plasturgie V De Garonne;22 Rue Ejea De Los Caballeros;47207;Marmande;44.504404;0.1562185 +0290044R;Lpo Lycée Des Metiers De L'elorn;10 Place St Houardon;29207;Landerneau;48.4489257;-4.2496666 +0542262R;Lpo Lycée Des Metiers Du Batiment Et Tp E. Here;86 Blv Marechal Foch;54525;Laxou;48.6827791;6.1514542 +0260023J;Lpo Lycée Des Metiers Du Dauphine;Boulevard Remy Roure;26103;Romans Sur Isere;45.0500065;5.0436672 +0771658J;Lpo Lycée Des Metiers Du Gue A Tresmes;Domaine Du Gue A Tresmes;77440;Congis Sur Therouanne;49.007185;2.97453 +0951728M;Lpo Lycée Des Metiers Edmond Rostand;75 Rue De Paris St Ouen L'aumon;95312;St Ouen L Aumone;48.9004041;2.3043039 +0690128P;Lpo Lycée Des Metiers Edouard Branly;25 Rue De Tourvielle;69322;Lyon 05;45.7527217;4.7911202 +0681809X;Lpo Lycée Des Metiers Ettore Bugatti;8 Rue Des Jonquilles;68110;Illzach;47.7622552;7.3652658 +0932119Y;Lpo Lycée Des Metiers Eugene Henaff;55 Avenue Raspail;93170;Bagnolet;48.8746864;2.4310918 +0772312V;Lpo Lycée Des Metiers Flora Tristan;12 Avenue Du 8 Mai 1945;77130;Montereau Fault Yonne;48.3765466;2.9451169 +0511565Z;Lpo Lycée Des Metiers Francois Arago;1 Rue Francois Arago;51095;Reims;49.2288823;4.0051108 +0080008R;Lpo Lycée Des Metiers Francois Bazin;145 Avenue Charles De Gaulle;08013;Charleville Mezieres;49.7734776;4.7050401 +0940585A;Lpo Lycée Des Metiers Francois Mansart;25 Avenue De La Banque;94211;St Maur Des Fosses;48.7903039;2.4971131 +0932126F;Lpo Lycée Des Metiers Francois Rabelais;Rue Francois Rabelais;93440;Dugny;48.949219;2.417634 +0750695Y;Lpo Lycée Des Metiers Fresnel;31 Boulevard Pasteur;75015;Paris 15;48.8431925;2.3126765 +0921156G;Lpo Lycée Des Metiers Galilee;79 Avenue Chandon;92230;Gennevilliers;48.9207477;2.3027836 +0912142J;Lpo Lycée Des Metiers Gaspard Monge;1 Place Gaspard Monge;91600;Savigny Sur Orge;48.6851259;2.349282 +0460493L;Lpo Lycée Des Metiers Gaston Monnerville;Rue George Sand;46005;Cahors;44.463535;1.455082 +0772311U;Lpo Lycée Des Metiers Georges Cormier;6 Rue Des Templiers;77527;Coulommiers;48.824111;3.093208 +0470038L;Lpo Lycée Des Metiers Georges Leygues;Avenue D Agen;47307;Villeneuve Sur Lot;44.3939869;0.7092418 +0572590F;Lpo Lycée Des Metiers Gustave Eiffel;Cite Technique;57525;Talange;49.235;6.168168 +0681810Y;Lpo Lycée Des Metiers Gustave Eiffel;Rue Gustave Eiffel;68704;Cernay;47.805144;7.18261 +0672616E;Lpo Lycée Des Metiers Gutenberg;22 Rue Lixenbuhl;67404;Illkirch Graffenstaden;48.5294488;7.7224172 +0401002X;Lpo Lycée Des Metiers Haroun Tazieff;Bld St Vincent De Paul;40993;St Paul Les Dax;43.7296115;-1.0383382 +0672534R;Lpo Lycée Des Metiers Haut-barr;4 Rue Jean De Manderscheid;67703;Saverne;48.7317063;7.3513877 +0690040U;Lpo Lycée Des Metiers Hector Guimard;23 Rue Claude Veyron;69361;Lyon 07;45.7456711;4.8579449 +0220196U;Lpo Lycée Des Metiers Henri Avril;7 Et 9 Rue De Dinard;22402;Lamballe;48.4765006;-2.5018486 +0180036S;Lpo Lycée Des Metiers Henri Brisson;25 Avenue Henri Brisson;18100;Vierzon;47.2279808;2.0612978 +0410899E;Lpo Lycée Des Metiers Hotel.et Tourisme Val De Loire;174 Rue Albert 1er;41033;Blois;47.5720598;1.3057101 +0332192D;Lpo Lycée Des Metiers Hotel.tourisme Gascogne;15 Rue Francois Rabelais;33405;Talence;44.7970282;-0.5983577 +0171405K;Lpo Lycée Des Metiers Hotelier;Avenue Des Minimes;17030;La Rochelle;46.143788;-1.162168 +0871030X;Lpo Lycée Des Metiers Hotelier Jean Monnet;12 Rue Louis Armstrong;87065;Limoges;45.8526566;1.2911805 +0573320Z;Lpo Lycée Des Metiers Hotelier Raymond Mondon;2 Boulevard De La Defense;57070;Metz;49.1054834;6.2095119 +0132974M;Lpo Lycée Des Metiers Hotelier Regional;114 Avenue Zenatti;13266;Marseille 08;43.2473062;5.3818559 +0060034E;Lpo Lycée Des Metiers Hotelier Tourisme-paul Augier;163 Boulevard Rene Cassin;06203;Nice;43.6688186;7.212126 +0310046G;Lpo Lycée Des Metiers Hotellerie Et Tourisme;1 Rue De L'abbe Jules Lemire;31026;Toulouse;43.596917;1.4068498 +0831453D;Lpo Lycée Des Metiers Hotellerie Et Tourisme Tpm;Place Vatel;83098;Toulon;43.124228;5.928 +0020012C;Lpo Lycée Des Metiers Jean De La Fontaine;2 Av De Champagne;02401;Chateau Thierry;49.0525511;3.3881731 +0022008X;Lpo Lycée Des Metiers Jean Mace;7 Rue Andre Ternynck;02300;Chauny;49.6216079;3.2145425 +0940129E;Lpo Lycée Des Metiers Jean Mace;Rue Jules Ferry;94407;Vitry Sur Seine;48.8016614;2.4003992 +0783431F;Lpo Lycée Des Metiers Jules Verne;2 Rue De La Constituante;78500;Sartrouville;48.9427537;2.160041 +0221571N;Lpo Lycée Des Metiers Kerraoul;Kerraoul;22502;Paimpol;48.779782;-3.048828 +0770920G;Lpo Lycée Des Metiers Lafayette;Place Des Celestins;77811;Moret Sur Loing;48.4041358;2.7973142 +0681817F;Lpo Lycée Des Metiers Lazare De Schwendi;19 Rue De Turckheim;68040;Ingersheim;48.0776787;7.3464456 +0672198A;Lpo Lycée Des Metiers Le Corbusier;15 Rue Lixenbuhl;67404;Illkirch Graffenstaden;48.5303263;7.7218261 +0382440W;Lpo Lycée Des Metiers Leonard De Vinci;Boulevard De Villefontaine;38091;Villefontaine;45.615892;5.1484454 +0770934X;Lpo Lycée Des Metiers Leonard De Vinci;2 Bis Rue Edouard Branly;77011;Melun;48.5510247;2.6601886 +0932046U;Lpo Lycée Des Metiers Leonard De Vinci;115 Route Des Petits Ponts;93290;Tremblay En France;48.9668896;2.5735809 +0771336J;Lpo Lycée Des Metiers Les Pannevelles;Route De Chalautre;77487;Provins;48.5559405;3.3040064 +0380035G;Lpo Lycée Des Metiers Lesdiguieres;15 Avenue Beaumarchais;38034;Grenoble;45.1679451;5.708607 +0931779D;Lpo Lycée Des Metiers L'horticulture;16 Rue Paul Doumer;93512;Montreuil;48.8591808;2.4567758 +0940118T;Lpo Lycée Des Metiers Louis Armand;173 Boulevard De Strasbourg;94130;Nogent Sur Marne;48.8465135;2.4912988 +0670085D;Lpo Lycée Des Metiers Louis Couffignal;11 Route De La Federation;67025;Strasbourg;48.5621702;7.7496898 +0460028F;Lpo Lycée Des Metiers Louis Vicat;1 Rue Pierre Bourthoumieux;46200;Souillac;44.8945267;1.4803757 +0940112L;Lpo Lycée Des Metiers Louise Michel;7 Rue Pierre Marie Derrien;94507;Champigny Sur Marne;48.8170061;2.4949002 +0932074Z;Lpo Lycée Des Metiers Marcel Cachin;11-13 Rue Marcel Cachin;93400;St Ouen;48.9174351;2.3385731 +0171455P;Lpo Lycée Des Metiers Marcel Dassault;40 Avenue Marcel Dassault;17300;Rochefort;45.9469735;-0.9578103 +0561641E;Lpo Lycée Des Metiers Marcelin Berthelot;Avenue Roland Garros;56230;Questembert;47.6628885;-2.4619313 +0501850P;Lpo Lycée Des Metiers Maurice Marland;159 Rue Des Lycées;50400;Granville;0.0;0.0 +0940126B;Lpo Lycée Des Metiers Maximilien Perret;Place San Benedetto Del Tronto;94142;Alfortville;48.7871379;2.4256714 +0942130E;Lpo Lycée Des Metiers Montaleau;2 Bis Rue Pierre Semard;94372;Sucy En Brie;48.7698796;2.5168592 +0932291K;Lpo Lycée Des Metiers Nicolas-joseph Cugnot;55 Boulevard Louis Armand;93330;Neuilly Sur Marne;48.8620632;2.5203576 +0830923C;Lpo Lycée Des Metiers Paul Langevin;Boulevard De L'europe;83514;La Seyne Sur Mer;43.1141507;5.8566865 +0190018S;Lpo Lycée Des Metiers Pierre Caraminot;28 Avenue De Ventadour;19300;Egletons;45.4072622;2.0561062 +0350030T;Lpo Lycée Des Metiers Pierre Mendes France;34 Rue Bahon Rault;35069;Rennes;48.1336823;-1.6840215 +0180008L;Lpo Lycée Des Metiers Pierre-emile Martin;1 Avenue De Gionne;18026;Bourges;47.0736177;2.4059603 +0750691U;Lpo Lycée Des Metiers Raspail;5 Bis Avenue Maurice D Ocagne;75014;Paris 14;48.8246549;2.3135463 +0910620E;Lpo Lycée Des Metiers Robert Doisneau;95 Bd Jean Jaures;91107;Corbeil Essonnes;48.612295;2.4605384 +0382099A;Lpo Lycée Des Metiers Roger Deschaux;5 Rue Des Pies;38360;Sassenage;45.1992827;5.6681035 +0410030K;Lpo Lycée Des Metiers Ronsard;Rue Joliot Curie;41100;Vendome;47.807054;1.057515 +0922276Z;Lpo Lycée Des Metiers Santos Dumont;39 Rue Pasteur;92210;St Cloud;48.8414385;2.1996884 +0740047Z;Lpo Lycée Des Metiers Savoie Leman;40 Boulevard Carnot;74203;Thonon Les Bains;46.3726701;6.4752202 +0782587N;Lpo Lycée Des Metiers Viollet-le-duc;1 Route De Septeuil;78640;Villiers St Frederic;48.8143515;1.875185 +0290071V;Lpo Lycée Des Metiers Yves Thepot;28 Avenue Yves Thepot;29104;Quimper;47.9882623;-4.0884842 +0352072M;Lpo Privé Des Metiers Institution St Malo-providence;2 Rue Du College;35409;St Malo;48.6503421;-2.0254142 +0290340M;Lpo Privé Lycée Des Metiers Estran Fenelon;Ker Stears;29287;Brest;48.390394;-4.486076 +0220117H;Lpo Privé Sacre Coeur;2 Bd St Jean-bapt De La Salle;22003;St Brieuc;0.0;0.0 +0230018V;Lt Lycée Des Metiers Des Metiers Du Batiment;Les Granges Route D'aubusson;23500;Felletin;45.889542;2.177806 +0800011C;Lt Lycée Des Metiers Edouard Gand;70 Bd De St Quentin;80098;Amiens;49.8791607;2.2965051 +0350005R;Lt Lycée Des Metiers Hotellerie Et Restauration;33 Rue Des Ecoles;35803;Dinard;48.6254023;-2.0585228 +0650027B;Lt Lycée Des Metiers Jean Dupuy;1 Rue Aristide Berges;65016;Tarbes;43.229533;0.064581 +0021476U;Lt Lycée Des Metiers Leonard De Vinci;1 Espace Jean Guerland;02331;Soissons;49.3726602;3.3073808 +0641548K;Lyc Agricole Privé N Dame;Route D Oraas;64390;Sauveterre De Bearn;43.399781;-0.942935 +0211173L;Lyc Enst Gen Et Techno Agricol Felix Kir;85 Rue De Velars;21370;Plombieres Les Dijon;47.3416891;4.9660479 +0340128E;Lyc Enst Gen.techno Agricole Frederic Bazille - Agropolis;3224 Route De Mende;34093;Montpellier;43.6481537;3.864392 +0110857X;Lyc Enst Privé Agric Form Prof;8 Boulevard Des Pins;11170;Pezens;43.255745;2.2655503 +0660632D;Lyc Enst Prof Agricole Privé Beausoleil;Rue Beau Soleil;66403;Ceret;42.4881192;2.7500287 +0383208F;Lyc Gen Tech Pr Itec Boisfleur Itec Boisfleury Europe;118 Avenue De L'eygala;38700;Corenc;45.2122675;5.7577651 +0671639T;Lyc Gen Tech Privé Des Metiers Institution Sainte Clotilde;19 Rue De Verdun;67083;Strasbourg;48.5874674;7.7724234 +0801742J;Lyc Gen Tech Privé Des Metiers La Providence;146 Bd De Saint Quentin;80094;Amiens;49.8789518;2.3007807 +0021869W;Lyc Gen Tech Privé Des Metiers Saint Joseph;4 Place Thiers;02402;Chateau Thierry;49.0428865;3.4009902 +0801888T;Lyc Gen Tech Privé Des Metiers Saint Martin;68 Rue Delpech;80043;Amiens;49.8842672;2.29491 +0801479Y;Lyc Gen Tech Privé Des Metiers Saint Remi;4 Rue Des Sergents;80006;Amiens;49.8942788;2.2987524 +0740092Y;Lyc Gen Tech Privé Des Metiers Sainte Famille;Avenue Des Voirons;74805;La Roche Sur Foron;46.0773767;6.2971227 +0290168A;Lyc Gen Tech Privé Des Metiers St Gabriel;Rue Jean Lautredou;29125;Pont L Abbe;47.8619569;-4.216529 +0022108F;Lyc Gen Tech Privé Des Metiers St Vincent De Paul;13 Avenue De Reims;02205;Soissons;49.3754463;3.3353105 +0430113P;Lyc Gen Techno Prof Agricole;Domaine De Choumouroux;43200;Yssingeaux;45.142667;4.123734 +0430112N;Lyc Gen Techno Prof Agricole Bonnefont;;43100;Fontannes;45.288694;3.422626 +0110669T;Lyc Gen Techno Prof Agricole Charlemagne;Route De Saint Hilaire;11000;Carcassonne;43.1941938;2.3440721 +0780004F;Lyc Gen Techno Prof Agricole De Chambourcy;Rte Forestiere Des Princesses;78100;St Germain En Laye;48.898908;2.093761 +0150037J;Lyc Gen Techno Prof Agricole Enil - Georges Pompidou;21 Rue De Salers;15005;Aurillac;44.9356765;2.4448977 +0630984C;Lyc Gen Techno Prof Agricole Louis Pasteur;Marmilhat;63370;Lempdes;45.7796779;3.17838 +0300139N;Lyc Gen Techno Prof Agricole Marie Durand;Domaine De Donadille;30230;Rodilhan;43.8269904;4.4476196 +0110677B;Lyc Gen Techno Prof Agricole Pierre Paul Riquet;935 Avenue Du Docteur Laennec;11491;Castelnaudary;43.3077647;1.9414051 +0210036A;Lyc Gen Techno Prof Agricole Viticole;16 Av Charles Jaffelin;21206;Beaune;47.0243711;4.8287285 +0420976H;Lyc Gen.et Technol.prive Sainte Barbe;10 Rue Franklin;42028;St Etienne;45.4285207;4.3845892 +0300125Y;Lyc Polyval Privé Des Metiers De La Salle;17 Place Henri Barbusse;30106;Ales;44.1232736;4.0805697 +0560180S;Lyc Polyval Privé Des Metiers Des Metiers N.dame De La Paix;Beg Er Lann;56275;Ploemeur;47.7506666;-3.3973939 +0371532P;Lyc Polyval Privé Des Metiers Esthetique De Touraine;49 Et 51 Rue Laponneraye;37000;Tours;47.3819579;0.6901009 +0480680D;Lyc Polyval Privé Des Metiers Hotellerie Sacre-coeur;43 Avenue De La Gare;48200;St Chely D Apcher;44.8001253;3.273392 +0350795Z;Lyc Polyval Privé Des Metiers Jean-baptiste De La Salle;5 Rue De La Motte Brulon;35702;Rennes;48.125487;-1.6682656 +0931797Y;Lyc Polyval Privé Des Metiers Jean-baptiste De La Salle;6-8 Place De La Resistance;93200;St Denis;48.932793;2.3549513 +0350797B;Lyc Polyval Privé Des Metiers Jeanne D'arc;61 Rue La Fontaine;35708;Rennes;48.1186302;-1.6535782 +0560182U;Lyc Polyval Privé Des Metiers Jeanne D'arc-st Ivy;29 Rue Abbe Martin;56306;Pontivy;48.0716012;-2.9614094 +0693786R;Lyc Polyval Privé Des Metiers Jehanne De France;6 Rue De La Fraternelle;69009;Lyon 09;45.7693693;4.8036475 +0560105K;Lyc Polyval Privé Des Metiers La Mennais;2 Rue General Dubreton;56801;Ploermel;47.9316915;-2.3986823 +0422158T;Lyc Polyval Privé Des Metiers La Salesienne;35 Rue De La Richelandiere;42100;St Etienne;45.4387009;4.4019391 +0290186V;Lyc Polyval Privé Des Metiers Le Paraclet;35 Avenue Des Glenan;29018;Quimper;47.9955627;-4.1146056 +0860789R;Lyc Polyval Privé Des Metiers Le Porteau;62 Rue Du Porteau;86001;Poitiers;46.601507;0.3392301 +0350791V;Lyc Polyval Privé Des Metiers Marcel Callo;21 Avenue Etienne Gascon;35603;Redon;47.6544207;-2.0810479 +0772275E;Lyc Polyval Privé Des Metiers Maurice Rondeau;1 Place Du Clos Saint Georges;77600;Bussy St Georges;48.8420023;2.7042405 +0560200N;Lyc Polyval Privé Des Metiers Notre Dame Le Menimur;71 Rue De Metz;56000;Vannes;47.6669014;-2.7622223 +0370749N;Lyc Polyval Privé Des Metiers Sainte Marguerite;86 Avenue De Grammont;37041;Tours;47.3825627;0.6921553 +0860774Z;Lyc Polyval Privé Des Metiers St Jacques De Compostelle;2 Avenue De La Revolution;86036;Poitiers;46.5861427;0.3817243 +0511146U;Lyc Polyval Privé Des Metiers St J-b De La Salle;20 Rue De Contrai;51066;Reims;49.2510721;4.0364069 +0560181T;Lyc Polyval Privé Des Metiers St Joseph;42 Rue De Kerguestenen;56109;Lorient;47.751421;-3.3928125 +0560198L;Lyc Polyval Privé Des Metiers St Joseph;39 Boulevard Des Iles;56010;Vannes;47.642256;-2.7821005 +0341470N;Lyc Pr Tech Agric Hortic Paysa;Route De Pezenas;34150;Gignac;43.6459898;3.5428841 +0332380H;Lyc Prof Horticole Ville De Bordeaux;Domaine De Bel Air Cedex 1052;33185;Le Haillan;44.873167;-0.677914 +0440980B;Lyc Techn Et General Agric Pr;Briace;44430;Le Landreau;47.202598;-1.317968 +0260074P;Lyc Technol Privé Des Metiers Montplaisir;75 Rue Montplaisir;26000;Valence;44.9312156;4.9083899 +0731405F;Lyc Technol Privé Des Metiers Sainte Anne;39 Montee St Jean;73292;La Motte Servolex;45.5973189;5.8780976 +0022022M;Lyc Technol Privé Des Metiers Sainte Sophie;22 Rue Curie;02110;Bohain En Vermandois;49.9830328;3.4565714 +0331590Z;Lyc Technol Privé Des Metiers St Vincent De Paul;47 A 51 Rue Des Sablieres;33074;Bordeaux;44.825364;-0.5753274 +0331556M;Lyc Technol Privé Des Metiers Ste Famille Saintonge;12 Rue De Saintonge;33023;Bordeaux;44.8321381;-0.5798944 +0260791U;Lyc.gen.tech.agricole De Romans Sur Isere;Route De Tain;26105;Romans Sur Isere;45.0451381;5.0138158 +0561454B;Lycée Agric Et Horticole Privé De Kerplouz;Kerplouz;56404;Auray;47.657435;-2.971875 +0351047Y;Lycée Agric Et Horticole Privé La Lande Du Breil;Rue Fernand Robert;35042;Rennes;48.1322242;-1.7079189 +0801825Z;Lycée Agricole De La Haute Som;Rue De La Poste;80800;Ribemont Sur Ancre;49.9585849;2.566318 +0641547J;Lycée Agricole Pr St Christoph;Le Bourg;64310;St Pee Sur Nivelle;43.31283;-1.58032 +0120936C;Lycée Agricole Privé;Boulevard Penevayre;12200;Villefranche De Rouergue;44.3552133;2.0415109 +0421676U;Lycée Agricole Privé;1 Rue Du Petit Lavoir;42450;Sury Le Comtal;45.5340512;4.1820966 +0100734U;Lycée Agricole Privé;105 Route De Mery-sur-seine;10150;Ste Maure;48.349943;4.0570357 +0360686A;Lycée Agricole Privé;St Cyran Du Jambot;36700;St Cyran Du Jambot;47.017235;1.139101 +0280944Z;Lycée Agricole Privé De Nermont;2 Rue De Nermont;28202;Chateaudun;48.0643465;1.3301401 +0100891P;Lycée Agricole Privé Des Cordeliers;29 Rue Des Cordeliers;10700;Arcis Sur Aube;48.5349144;4.1402841 +0421090G;Lycée Agricole Privé Etienne Gautier;Ressins;42720;Nandax;46.096594;4.189123 +0080943G;Lycée Agricole Privé L.e.t.p.a.p;18 Rue Du Chateau;08260;Maubert Fontaine;49.8696137;4.4309129 +0560858D;Lycée Agricole Privé La Touche;La Touche;56801;Ploermel;47.9491412;-2.4040591 +0241033T;Lycée Agricole Privé Le Cluzeau;Le Cluzeau;24240;Sigoules;44.764981;0.419171 +0281101V;Lycée Agricole Privé Notre Dame (orph.app.auteuil);Chateau De Vaux;28240;La Loupe;47.786628;0.936139 +0511645L;Lycée Agricole Privé Reims-thillois;4 Rue Des Ecoles;51370;Thillois;49.2565382;3.9547396 +9741060J;Lycée Agricole Privé Sainte Suzanne;Chemin Des Trois Freres;97441;Ste Suzanne;-20.90902;55.6055231 +0271382F;Lycée Agricole Privé Tourville Sur Pont-audemer;Route De Lisieux;27504;Pont Audemer;49.3501675;0.5151331 +0721009F;Lycée Agricole Privé Val De Sarthe;Route Du Mans;72303;Sable Sur Sarthe;47.838713;-0.304241 +0641538Z;Lycée Agricole Rural Pr Ois;;64130;Mauleon Licharre;43.220131;-0.8895 +0131690S;Lycée Agricole Tech. Privé Institut De Fontlongue;Bd Theodore Aubanel;13148;Miramas;43.5881724;4.9977599 +0631162W;Lycée Agro-environ Privé Saint Joseph;Chateau De Saint Quentin;63340;Le Breuil Sur Couze;45.460253;3.273918 +0251477V;Lycée Agrotechnologique Privé Francois Xavier;5 Rue Du Chapitre;25000;Besancon;47.2333772;6.0305139 +0380097Z;Lycée Climatique Et Sportif Jean Prevost;470 Rue De Tintaine;38250;Villard De Lans;45.0619796;5.5561287 +0660057D;Lycée Climatique Et Sportif Pierre De Coubertin;5 Avenue Pierre De Coubertin;66120;Font Romeu Odeillo Via;42.5131055;2.0447502 +0050004C;Lycée Climatique Honore Romane;Route De Caleyere;05202;Embrun;44.5702302;6.4862717 +0650001Y;Lycée Climatique Rene Billeres;6 Avenue Marcel Lemettre;65402;Argeles Gazost;43.0017111;-0.0948255 +0390042J;Lycée Climatique Victor Considerant;Route D'ornans;39110;Salins Les Bains;46.9525457;5.8851415 +0671636P;Lycée D'ens. General Et Techn. Ort;14 Rue Sellenick;67083;Strasbourg;48.5906831;7.7508928 +0671685T;Lycée D'ens. General Et Technologique Agricole;44 Bld D'europe;67212;Obernai;48.46226;7.48168 +0431000D;Lycée D'enseig. Agricole Privé Site De Vals De L'isvt;72 Avenue De Vals;43750;Vals Pres Le Puy;45.0335241;3.8797025 +0520893N;Lycée D'enseignement Agricole Privé;Rue De La Motte;52220;Droyes;48.5130399;4.703696 +0021818R;Lycée D'enseignement Technolog Robert Schuman;10 Route D'ugny;02300;Chauny;49.6265626;3.1986091 +0280947C;Lycée Ens Prof Agri Privé Du Perche;35 Rue De La Touche;28400;Nogent Le Rotrou;48.3111788;0.8100851 +0180758B;Lycée Ens Prof Privé Agricole Bengy/lignieres;2 Rue Du Chanoine Volton;18520;Bengy Sur Craon;47.0017201;2.7487466 +0180757A;Lycée Ens Prof Privé Agricole Site De Lignieres;10 Rue Raymond Chassagne;18160;Lignieres;46.7505044;2.1756904 +0410855G;Lycée Ens Prof Rural Privé Boissay;;41120;Fougeres Sur Bievre;47.4478099;1.3411169 +0360710B;Lycée Ens Prof Rural Privé De Lignac;Rue Alienor D Aquitaine;36370;Lignac;46.4667777;1.2176906 +0410854F;Lycée Ens Prof Rural Privé Ste Cecile;12 Rue Saint Laurent;41800;Montoire Sur Le Loir;47.7521229;0.8632596 +0190056H;Lycée Ens. General Privé Inst Jeanne D Arc;Rue Du Jardin Public;19400;Argentat;45.093215;1.940286 +0230040U;Lycée Ens. General Privé Notre Dame;5 Av Du Docteur Manouvrier;23008;Gueret;46.167861;1.8743524 +0441787D;Lycée Ensei Agricole Privé Saint-exupery;9 Rue Pierre Landais;44430;Le Loroux Bottereau;47.2379078;-1.3457308 +0280967Z;Lycée Enseig Agricole Privé Efagrir;2 Rue Des Fleurs;28630;Mignieres;48.3595403;1.4288729 +0561463L;Lycée Enseignement Agric Privé Sts Anges Kerlebost;St Thuriau;56306;Pontivy;48.0565733;-2.9246647 +0870864S;Lycée Enseignement General Pri St Jean;26 Rue Eugene Varlin;87016;Limoges;45.8263601;1.2749309 +0400080V;Lycée Enseignement Prof. Jean Cassaigne;Avenue Kennedy;40280;St Pierre Du Mont;43.869494;-0.489719 +0781070P;Lycée Enseigt Privé Agricole Lepa Le Buat;21 Rue Du Buat;78580;Maule;48.906077;1.8409898 +0110984K;Lycée Enst Agricole Privé;8 Avenue Georges Clemenceau;11200;Lezignan Corbieres;43.200028;2.7645461 +0341477W;Lycée Enst Agricole Privé Bonne Terre;Route De Beziers;34120;Pezenas;43.448806;3.4123228 +0341474T;Lycée Enst Agricole Privé La Gardiole;2 Rue Du Couvent;34770;Gigean;43.4990181;3.7125009 +0660631C;Lycée Enst Agricole Privé Le Mas Blanc;Av Emmanuel Brousse;66760;Bourg Madame;42.4391763;1.9445521 +0341471P;Lycée Enst Agricole Privé Les Buissonnets;1 Avenue De Nissan;34310;Capestang;43.3272313;3.0496255 +0480502K;Lycée Enst Agricole Privé Terre Nouvelle;2 Av Martyrs De La Resistance;48100;Marvejols;44.554723;3.2925132 +0301324B;Lycée Enst Prof Agric Privé;9 Route De Bezouce;30840;Meynes;43.8791144;4.5539775 +0221676C;Lycée Enst Prof Agricole Privé Xavier Grall;8 Rue Lavergne;22605;Loudeac;48.1797422;-2.7566822 +0221680G;Lycée Enst Prof Privé Rural;La Guerche;22100;St Helen;48.468966;-1.986689 +0758513W;Lycée Etranger Privé Leonard De Vinci;12 Rue Sedillot;75007;Paris 07;48.8586491;2.3017117 +0592922F;Lycée Europeen Privé Therese D'avila;254 Rue Nationale;59003;Lille;50.6306982;3.0475752 +0791005N;Lycée Gen Et Tech Agricole;22 Rue De La Baritauderie;79302;Bressuire;46.838952;-0.4751457 +0160006V;Lycée Gen Et Tech Agricole Angouleme-l Oisellerie;Logis De L'oisellerie;16400;La Couronne;45.625025;0.103934 +0170087C;Lycée Gen Et Tech Agricole Georges Desclaude;Rue Georges Desclaude;17119;Saintes;45.7559394;-0.6430047 +0170393K;Lycée Gen Et Tech Agricole Ind.laitiere Agro-alimentaire;Avenue Francois Miterrand;17700;Surgeres;46.104913;-0.751411 +0791057V;Lycée Gen Et Tech Agricole Inst.rur.educ.orient.;47 Avenue De Poitiers;79170;Brioux Sur Boutonne;46.1527195;-0.2107398 +0790768F;Lycée Gen Et Tech Agricole Jacques Bujault;Route De La Roche;79500;Melle;46.2272777;-0.1514867 +0790706N;Lycée Gen Et Tech Agricole Les Sicaudieres;Boulevard De Nantes;79308;Bressuire;46.843654;-0.519819 +0491807Y;Lycée Gen Et Tech Agricole Pr La Verzee;1 Rue Du Chevalier D'avoynes;49520;Combree;47.705533;-1.025685 +0160980D;Lycée Gen Et Tech Agricole Roc Fleuri;6 Bd Des Grands Rocs;16700;Ruffec;46.0309545;0.2026405 +0171265H;Lycée Gen Et Tech Agricole Saint Antoine;2 Allee Saint Antoine;17240;Bois;45.476325;-0.5915032 +0860718N;Lycée Gen Et Tech Agricole Xavier Bernard;Venours;86480;Rouille;46.400326;0.072145 +0331683A;Lycée Gen Et Techno Agricole;Avenue De La Republique;33430;Bazas;44.4336384;-0.2244781 +0470662P;Lycée Gen Et Techno Agricole;Route De Francescas;47600;Nerac;44.1092632;0.3702156 +0780235G;Lycée Gen Et Technol Agricole Centre D Enseign Zootechnique;Parc Du Chateau;78120;Rambouillet;48.6748927;1.807541 +0290018M;Lycée Gen Et Technol Agricole De Brehoulou;Brehoulou;29170;Fouesnant;47.888817;-4.012919 +0290341N;Lycée Gen Et Technol Agricole De L'aulne;Rocade Du Parc De Bihan;29150;Chateaulin;48.195389;-4.094207 +0290126E;Lycée Gen Et Technol Agricole De Suscinio;Ploujean;29600;Morlaix;48.605845;-3.833374 +0120937D;Lycée Gen Et Technol Agricole Epla De Rodez;La Roque;12033;Rodez;44.3824285;2.5858486 +0220470S;Lycée Gen Et Technol Agricole Kernilien;Plouisy;22204;Guingamp;48.5763113;-3.1825986 +0560013K;Lycée Gen Et Technol Agricole Le Gros Chene;;56308;Pontivy;48.0565733;-2.9246647 +0221031B;Lycée Gen Et Technol Agricole Public;6 Rue Du Porhoet;22230;Merdrignac;48.190409;-2.4262933 +0350700W;Lycée Gen Et Technol Agricole Theodore Monod;55 Avenue De La Bouvardiere;35651;Le Rheu;48.0998905;-1.8040923 +0290181P;Lycée Gen Et Technol Privé Brest - Rive Droite - Javouhey;4 Rue Des Remparts;29238;Brest;48.3832192;-4.5023223 +0220094H;Lycée Gen Et Technol Privé Cordeliers-n-d De La Victoire;1 Place Des Cordeliers;22102;Dinan;48.4538527;-2.0443642 +0220102S;Lycée Gen Et Technol Privé Jacques Bossuet;4 Rue De La Bienfaisance;22304;Lannion;48.7287135;-3.4562299 +0220106W;Lycée Gen Et Technol Privé Jean Xxiii;1 Rue Du Seminaire;22800;Quintin;48.3991817;-2.9154743 +0290335G;Lycée Gen Et Technol Privé La Croix Rouge;2 Rue Mirabeau;29229;Brest;48.4075125;-4.4894481 +0290170C;Lycée Gen Et Technol Privé Le Likes;20 Place De La Tourbie;29196;Quimper;47.9996048;-4.1024423 +0290177K;Lycée Gen Et Technol Privé Nd Du Kreisker;2 Rue Cadiou;29250;St Pol De Leon;48.6825255;-3.9870981 +0220097L;Lycée Gen Et Technol Privé Notre Dame;21 Rue Des Capucins;22205;Guingamp;48.5613277;-3.1433799 +0220107X;Lycée Gen Et Technol Privé Notre Dame De Campostal;5 Place Du Bourg Coz;22110;Rostrenen;48.235163;-3.3159261 +0350769W;Lycée Gen Et Technol Privé Notre Dame Des Marais;9 Bis Rue Eugene Pacory;35304;Fougeres;48.3569757;-1.2046288 +0290164W;Lycée Gen Et Technol Privé Notre Dame Du Mur;10 Rue Guy Le Normand;29671;Morlaix;48.5731563;-3.8200636 +0560098C;Lycée Gen Et Technol Privé Notre Dame Du Voeu;Rue Du Champ De Foire;56704;Hennebont;47.8067327;-3.2789444 +0950759J;Lycée Gen Et Technol Privé Notre-dame De Bury;1 Av Georges Pompidou;95580;Margency;49.0026782;2.2838011 +0920928J;Lycée Gen Et Technol Privé Passy Buzenval;50 Avenue Otis Mygatt;92508;Rueil Malmaison;48.8591268;2.1832515 +0910808J;Lycée Gen Et Technol Privé St Charles;2 Rue G.anthonioz De Gaulle;91200;Athis Mons;48.7091074;2.3900057 +0781974X;Lycée Gen Et Technol Privé St Exupery;11 Rue Michael Faraday;78180;Montigny Le Bretonneux;48.7952039;2.0497303 +0221589H;Lycée Gen Et Technol Privé St Joseph;42 Rue De La Cheze;22600;Loudeac;48.174469;-2.7502399 +0291654R;Lycée Gen Et Technol Privé St Joseph;61 Rue Bayard;29187;Concarneau;47.8740713;-3.9214211 +0290156M;Lycée Gen Et Technol Privé St Louis;63 Grand'rue;29150;Chateaulin;48.1965682;-4.0868452 +0560101F;Lycée Gen Et Technol Privé St Louis;5 Rue Perault;56102;Lorient;47.7448536;-3.3636004 +0350777E;Lycée Gen Et Technol Privé St Martin;31 Rue D'antrain;35706;Rennes;48.1166798;-1.6785836 +0950762M;Lycée Gen Et Technol Privé St Martin De France;1 Avenue De Verdun;95300;Pontoise;49.042589;2.0905789 +0560117Y;Lycée Gen Et Technol Privé St Paul;12 Allee Gabriel Deshayes;56017;Vannes;47.6502167;-2.7624641 +0220111B;Lycée Gen Et Technol Privé St Pierre;16 Rue St Pierre;22015;St Brieuc;48.5128487;-2.7660968 +0350774B;Lycée Gen Et Technol Privé St Sauveur;16 Place St Sauveur;35603;Redon;47.6505318;-2.0844461 +0290159R;Lycée Gen Et Technol Privé St Sebastien;4 Rue H De Guebriant;29208;Landerneau;48.4510307;-4.2578666 +0350783L;Lycée Gen Et Technol Privé Ste Genevieve;14 Rue Ginguene;35006;Rennes;48.0990499;-1.6853943 +0560096A;Lycée Gen Et Technol Privé Ste Jeanne D'arc;2 Rue Penantraon;56110;Gourin;48.1392054;-3.6092159 +0290171D;Lycée Gen Et Technol Privé Ste Therese;56 Avenue De Remscheid;29337;Quimper;47.9811832;-4.0962165 +0920937U;Lycée Gen Et Technol Privé Suger;11 Sente De L Abbe Suger;92420;Vaucresson;48.84389;2.1624469 +0470019R;Lycée Gen Et Technol.agricole Etienne Restat;Route De Casseneuil;47110;Ste Livrade Sur Lot;44.4222789;0.6061423 +0594278E;Lycée Gen. Et Tech. Privé Lycée E.p.i.d.;20 Rue De Lille;59140;Dunkerque;51.0277456;2.3724543 +0381817U;Lycée Gen. Et Tech.agricole Grenoble St-ismier;1 Chemin Charvinieres;38332;St Ismier;45.2413573;5.8229581 +0271107G;Lycée Gen. Et Techn. Agricole;6 Rue Georges Politzer;27032;Evreux;49.0072785;1.150413 +0271016H;Lycée Gen. Et Techn. Agricole De Chambray;Gouville;27240;Gouville;48.8505833;0.9909485 +0500116E;Lycée Gen. Et Technol. Privé Germain;73 Rue D'ilkley;50208;Coutances;49.0608464;-1.4492908 +0520679F;Lycée Gen. Et Technol. Privé Immaculee Conception;1 Bis Rue Du Mal De Lattre;52115;St Dizier;48.6385221;4.9486945 +0500112A;Lycée Gen. Et Technol. Privé Notre Dame De La Providence;9 Rue Du Chanoine Beranger;50303;Avranches;48.686819;-1.3690481 +0070069M;Lycée Gen. Et Technol. Privé Sacre Coeur - Notre Dame;6 Boulevard De Vernon;07000;Privas;44.7359923;4.6003035 +0730763H;Lycée Gen. Et Technol. Privé Saint Ambroise;2 Rue Burdin;73025;Chambery;45.5703402;5.9246533 +0370737A;Lycée Gen. Et Technol. Privé Saint Denis;19 Av Du Gal De Gaulle;37601;Loches;47.127138;0.9929978 +0141164P;Lycée Gen. Et Technol. Privé Sainte Marie;8 Avenue De La Croix Guerin;14000;Caen;49.190183;-0.3565417 +0070066J;Lycée Gen. Et Technol. Privé Vernet;21 Avenue Notre Dame;07700;Bourg St Andeol;44.3730123;4.6450101 +0212082Z;Lycée Gen.et Tech. Agricole;7 Rue Du Champ De Foire;21140;Semur En Auxois;47.4953913;4.3360813 +0460727R;Lycée Gen.et Tech. Agricole;;46120;Lacapelle Marival;44.72784;1.925505 +0861392W;Lycée Gen.et Tech. Agricole Annexe Du Lycée De Venours;45 Avenue Maurice Bailly;86400;Civray;46.1358095;0.287391 +0480514Y;Lycée Gen.et Tech. Agricole Francois Rabelais;Civergols;48200;St Chely D Apcher;44.807858;3.302736 +0050012L;Lycée Gen.et Tech. Agricole Hautes-alpes (des);Les Emeyeres 127 Rte Valserres;05000;Gap;44.5416045;6.0907911 +0480657D;Lycée Gen.et Tech. Agricole Louis Pasteur;Chemin Du Fraissinet;48500;La Canourgue;44.4348979;3.242143 +0900246D;Lycée Gen.et Tech. Agricole Lucien Quelet;95 Rue De Turenne;90300;Valdoie;47.6785817;6.8404106 +0030094L;Lycée Gen.et Tech. Agricole Tourret;Neuville;03000;Neuvy;46.546982;3.287709 +0530081A;Lycée Gen.et Tech.agricole;321 Route De St Nazaire;53013;Laval;48.0476546;-0.7976282 +0850151C;Lycée Gen.et Tech.agricole Bel Air;1 Boulevard Hoche;85205;Fontenay Le Comte;46.4612234;-0.7962385 +0771436T;Lycée Gen.et Tech.agricole Bougainville;Domaine De Sansalle Rn 19;77257;Brie Comte Robert;48.6912267;2.6103697 +0442061B;Lycée Gen.et Tech.agricole Jules Rieffel;5 Rue De La Syonniere;44817;St Herblain;47.2468499;-1.6477727 +0720010V;Lycée Gen.et Tech.agricole La Germiniere;;72700;Rouillon;48.00385;0.135487 +0850152D;Lycée Gen.et Tech.agricole Lucon-petre;;85400;Ste Gemme La Plaine;46.483266;-1.113742 +0850144V;Lycée Gen.et Tech.prof.agricol Lycée Nature;Allee Des Druides;85035;La Roche Sur Yon;46.6641852;-1.3811329 +0890851S;Lycée Gen.et Techn.agricole Annexe;3 Rte De Champcevrais;89350;Champignelles;47.7807971;3.0679892 +0741219Y;Lycée Gen.et Techn.agricole H.b.de Saussure;;74920;Combloux;45.896937;6.641306 +0601700X;Lycée Gen.et Technol Privé Sevigne;20 Rue De La Sous Prefecture;60200;Compiegne;49.415931;2.8299869 +0382373Y;Lycée Gen.et Technol. Agricole Du Guiers Val D'ainan;6 Place Du Marche;38480;Le Pont De Beauvoisin;45.5367798;5.6698057 +0740082M;Lycée Gen.et Technol. Privé Saint Michel;27 Faubourg Des Balmettes;74007;Annecy;45.8918267;6.1237631 +0230030H;Lycée Gen.et Technol.agricole;Le Chaussadis;23150;Ahun;46.093558;2.030514 +0421088E;Lycée Gen.et Technol.agricole;Le Bourg;42600;Precieux;45.568298;4.081896 +0640220S;Lycée Gen.et Technol.agricole;Route De Pau;64121;Montardon;43.3616096;-0.3613466 +0690279D;Lycée Gen.et Technol.agricole;234 Route De Charly;69230;St Genis Laval;45.6727335;4.7911863 +0711067Z;Lycée Gen.et Technol.agricole;Poncetys;71960;Davaye;46.3067223;4.7375335 +0080863V;Lycée Gen.et Technol.agricole;27 Rue Du Muguet;08090;St Laurent;49.7642188;4.7790123 +0180636U;Lycée Gen.et Technol.agricole;Route De Barlieu;18260;Vailly Sur Sauldre;47.4622514;2.6472006 +0331424U;Lycée Gen.et Technol.agricole;84 Av Du General De Gaulle;33294;Blanquefort;44.9172959;-0.6343214 +0381819W;Lycée Gen.et Technol.agricole;57 Avenue Charles De Gaulle;38260;La Cote St Andre;45.3874636;5.2629127 +0400750Y;Lycée Gen.et Technol.agricole;Heugas;40180;Heugas;43.6302977;-1.0735488 +0501213X;Lycée Gen.et Technol.agricole;Route De Montmartin;50200;Coutances;49.047808;-1.4452609 +0601782L;Lycée Gen.et Technol.agricole;;60600;Airion;49.425303;2.416204 +0610790C;Lycée Gen.et Technol.agricole;Rue Du 11 Novembre 1918;61500;Sees;48.5959227;0.1730679 +0680003J;Lycée Gen.et Technol.agricole;8 Aux Remparts;68250;Rouffach;47.9555409;7.3007998 +0711068A;Lycée Gen.et Technol.agricole;10 Lieu Dit La Platiere;71150;Fontaines;46.859713;4.771907 +0820559M;Lycée Gen.et Technol.agricole;Dom Capou 1915 Rte De Bordeaux;82000;Montauban;44.0324358;1.3154167 +0881070K;Lycée Gen.et Technol.agricole;Avenue De Lattre De Tassigny;88500;Mirecourt;48.2920391;6.1305618 +0060793E;Lycée Gen.et Technol.agricole Agricole Et Horticole;1285 Avenue Jules Grec;06600;Antibes;43.5991017;7.1129973 +0830077H;Lycée Gen.et Technol.agricole Agricole Et Horticole;Carrefour Du Lycée;83408;Hyeres;0.0;0.0 +0381886U;Lycée Gen.et Technol.agricole Agrotec;Montee Bon Accueil;38206;Vienne;45.5465829;4.8648632 +0890045R;Lycée Gen.et Technol.agricole Auxerre-la-brosse;La Brosse;89290;Venoy;47.7928286;3.6535285 +0320039U;Lycée Gen.et Technol.agricole Beaulieu-lavacant;Route De Tarbes;32020;Auch;43.6233119;0.5770361 +0251263M;Lycée Gen.et Technol.agricole Besancon Granvelle;2 Rue Des Chanets;25410;Dannemarie Sur Crete;47.2020212;5.873984 +0190244M;Lycée Gen.et Technol.agricole Brive-objat;23 Rue Murat;19130;Voutezac;45.2831337;1.4250998 +0593395V;Lycée Gen.et Technol.agricole Communaute Urbaine De Lille;Rue De La Mitterie;59463;Lille;50.6487941;2.9947205 +9710804X;Lycée Gen.et Technol.agricole Convenance;Convenance;97122;Baie Mahault;16.2370417;-61.5986037 +9720616M;Lycée Gen.et Technol.agricole Croix Rivail;Croix Rivail;97232;Le Lamentin;14.6131612;-60.9523142 +0511196Y;Lycée Gen.et Technol.agricole De Chalons En Champagne;;51460;Somme Vesle;48.985722;4.590599 +0020060E;Lycée Gen.et Technol.agricole De Crezancy;1 Rue De Paris;02650;Crezancy;49.0498047;3.5155052 +0730813M;Lycée Gen.et Technol.agricole De La Motte Servolex;Domaine Reinach;73290;La Motte Servolex;45.596488;5.874112 +0080804F;Lycée Gen.et Technol.agricole De Rethel;Route De Novion;08300;Rethel;49.5285468;4.3743447 +0211135V;Lycée Gen.et Technol.agricole Dijon-quetigny;21 Bd Olivier De Serres;21800;Quetigny;47.3091823;5.1138121 +0593255T;Lycée Gen.et Technol.agricole Du Nord;458 Rue De La Motte Julien;59507;Douai;50.3892001;3.0651718 +0622257S;Lycée Gen.et Technol.agricole Du Pas-de-calais;Route De Cambrai;62217;Tilloy Les Mofflaines;50.274208;2.83736 +0681222J;Lycée Gen.et Technol.agricole Du Pflixbourg;2 Lieu Dit Saint Gilles;68920;Wintzenheim;48.073122;7.289782 +0251202W;Lycée Gen.et Technol.agricole Ecole D'industrie Laitiere;Grande Rue;25620;Mamirolle;47.196804;6.158412 +0390812W;Lycée Gen.et Technol.agricole Ecole D'industrie Laitiere;Rue De Versailles;39801;Poligny;46.83417;5.705002 +0390810U;Lycée Gen.et Technol.agricole Edgar Faure;614 Avenue Edgar Faure;39570;Montmorot;46.689511;5.5099358 +0010059J;Lycée Gen.et Technol.agricole Edouard Herriot;Domaine De Cibeins;01600;Miserieux;45.9973531;4.8236968 +9740078S;Lycée Gen.et Technol.agricole Emile Boyer De La Giroday;165 Route De Mafate;97460;St Paul;0.0;0.0 +0810121S;Lycée Gen.et Technol.agricole Epla D Albi;Fonlabour Route De Toulouse;81000;Albi;43.9185017;2.1151666 +0312354R;Lycée Gen.et Technol.agricole Epla D Ondes;Rue Castelnau;31330;Ondes;43.782461;1.316476 +0460490H;Lycée Gen.et Technol.agricole Epla De Figeac;La Vinadie;46100;Figeac;44.573023;2.028841 +0090022A;Lycée Gen.et Technol.agricole Epla De Pamiers;Route De Belpech;09100;Pamiers;43.12866;1.6549179 +0311262D;Lycée Gen.et Technol.agricole Epla De Toulouse Auzeville;2 Rte De Narbonne;31326;Castanet Tolosan;43.5348091;1.4844659 +0120621K;Lycée Gen.et Technol.agricole Epla Villefranche De Rouergue;Beauregard Cote Mas De Bonnet;12204;Villefranche De Rouergue;44.349746;2.035381 +0240023V;Lycée Gen.et Technol.agricole Et Agro-alimentaire;Avenue Winston Churchill;24660;Coulounieix Chamiers;45.1872222;0.6922222 +0700827U;Lycée Gen.et Technol.agricole Etienne Munier;Le Grand Montmarin;70014;Vesoul;47.619788;6.15428 +0660699B;Lycée Gen.et Technol.agricole Federico Garcia Lorca;Route Nationale 114;66200;Theza;42.6020137;2.9695758 +0100687T;Lycée Gen.et Technol.agricole Forestier De Crogny;Rue Des Etangs;10210;Les Loges Margueron;48.0863362;4.0991274 +0740927F;Lycée Gen.et Technol.agricole Industrie Laitiere;;74805;La Roche Sur Foron;46.0666904;6.3112656 +0623277A;Lycée Gen.et Technol.agricole Institut Agricole Saint Eloi;36 Rue Marcellin Gaudefroy;62452;Bapaume;50.1027923;2.8464795 +0650139Y;Lycée Gen.et Technol.agricole Jean Monnet Epla Vic-en-big;11 Bis Promenade Des Acacias;65500;Vic En Bigorre;43.3823572;0.0457979 +0211217J;Lycée Gen.et Technol.agricole La Barotte;La Barotte Rte De Langres;21403;Chatillon Sur Seine;47.85948;4.614945 +0690250X;Lycée Gen.et Technol.agricole La Bruyere;26 Chemin De La Bruyere;69570;Dardilly;45.813486;4.769397 +0450094H;Lycée Gen.et Technol.agricole Le Chesnoy;Le Chesnoy;45200;Amilly;47.971965;2.7392439 +0801272Y;Lycée Gen.et Technol.agricole Le Paraclet;Le Paraclet Route De Cottenchy;80440;Boves;49.8249135;2.3942367 +0260765R;Lycée Gen.et Technol.agricole Le Valentin;Route Nationale 7;26500;Bourg Les Valence;44.9965985;4.8429604 +0180585N;Lycée Gen.et Technol.agricole Legta;Rue Theophile Gauthier;18028;Bourges;47.1059678;2.412406 +0280706R;Lycée Gen.et Technol.agricole Legta;La Saussaye;28630;Sours;48.406038;1.556544 +0360017Y;Lycée Gen.et Technol.agricole Legta;Route De Velles;36018;Chateauroux;46.7822046;1.6898082 +0360600G;Lycée Gen.et Technol.agricole Legta;16 Rue H De Latouche;36400;La Chatre;46.5790648;1.9898776 +0370781Y;Lycée Gen.et Technol.agricole Legta;La Petite Plaine;37230;Fondettes;47.399712;0.6381559 +0410018X;Lycée Gen.et Technol.agricole Legta;Rue De La Valle Du Loir;41106;Vendome;47.8004267;1.0920294 +0451535Z;Lycée Gen.et Technol.agricole Les Barres;Les Barres;45290;Nogent Sur Vernisson;47.838247;2.756901 +0010819K;Lycée Gen.et Technol.agricole Les Sardieres;79 Avenue De Jasseron;01000;Bourg En Bresse;46.2116792;5.2565142 +0570112M;Lycée Gen.et Technol.agricole Lycée Agricole Departemental;40 Route De Strasbourg;57170;Chateau Salins;48.8187729;6.515628 +0570086J;Lycée Gen.et Technol.agricole Metz-courcelles-chaussy;1 3 Av D Urville;57530;Courcelles Chaussy;49.1086404;6.38191 +0331863W;Lycée Gen.et Technol.agricole Montagne;Le Bourg;33570;Montagne;44.971429;-0.141059 +0541207U;Lycée Gen.et Technol.agricole Nancy-pixerecourt;Pixerecourt;54220;Malzeville;48.7304099;6.1804006 +0071125K;Lycée Gen.et Technol.agricole Olivier De Serres;;07205;Aubenas;44.2136489;3.999401 +0550752U;Lycée Gen.et Technol.agricole Philippe De Vilmorin;Chemin De Popey;55000;Bar Le Duc;48.7618197;5.1849786 +0623723K;Lycée Gen.et Technol.agricole Radinghem;Rue Neuve;62310;Radinghem;50.55078;2.117642 +0421078U;Lycée Gen.et Technol.agricole Roanne-cherve;;42120;Perreux;46.0376199;4.122751 +6200190M;Lycée Gen.et Technol.agricole Sartene;Route De Levie;20100;Sartene;41.621822;8.97472 +0500060U;Lycée Gen.et Technol.agricole St Lo There;;50620;Le Hommet D Arthenay;49.189776;-1.188463 +0950786N;Lycée Gen.et Technol.prive St Rosaire;53 Rue Pierre Brossolette;95200;Sarcelles;48.9919938;2.3747125 +9830504F;Lycée Gen.et Technol.prive Apollinaire Anova (ddec);24 C Rte Bretelle Savexpress;98890;Paita;0.0;0.0 +0690539L;Lycée Gen.et Technol.prive Assomption Bellevue;39 Quai Jean Jacques Rousseau;69350;La Mulatiere;45.7310895;4.8137961 +0690522T;Lycée Gen.et Technol.prive Aux Lazaristes;24 Montee Saint Barthelemy;69321;Lyon 05;45.7638385;4.826226 +0940822H;Lycée Gen.et Technol.prive Batiment Et Travaux Publics;18 Rue De Belfort;94307;Vincennes;48.8501369;2.4311329 +0880099E;Lycée Gen.et Technol.prive Beau Jardin;9 Rue Du Beau Jardin;88104;St Die Des Vosges;48.2892768;6.9523158 +0870081R;Lycée Gen.et Technol.prive Beaupeyrat;9 Ter Rue Petiniaud Beaupeyrat;87036;Limoges;45.8280651;1.2550534 +0300077W;Lycée Gen.et Technol.prive Bellevue;14 Rue Alfred De Musset;30100;Ales;44.1324998;4.0852321 +0690536H;Lycée Gen.et Technol.prive Belmont-capdepon;43 Rue Pasteur;69007;Lyon 07;45.7526828;4.8397656 +0641907A;Lycée Gen.et Technol.prive Bernat Etxepare;12 Bis Av Mounede;64100;Bayonne;43.4986058;-1.4580377 +9830261S;Lycée Gen.et Technol.prive Blaise Pascal (ddec);22 Rue B. Pascal A. Vata;98807;Noumea;-22.2980465;166.4424651 +0783282U;Lycée Gen.et Technol.prive Blanche De Castille;1 Avenue De La Breteche;78150;Le Chesnay;48.8295148;2.1317755 +0772324H;Lycée Gen.et Technol.prive Campus Sainte Therese;Av Erasme - Rond Pt De L'europe;77330;Ozoir La Ferriere;48.763827;2.671739 +9741256X;Lycée Gen.et Technol.prive Catholique Levavasseur;4 Rue Fenelon;97400;St Denis;48.7876977;2.5414736 +0442083A;Lycée Gen.et Technol.prive Cens;La Joneliere;44244;La Chapelle Sur Erdre;47.2794482;-1.5401117 +0430056C;Lycée Gen.et Technol.prive Cevenol;;43400;Le Chambon Sur Lignon;45.061316;4.3021269 +0490952U;Lycée Gen.et Technol.prive Champ Blanc;Champ Blanc;49710;Le Longeron;47.016606;-1.067741 +0691654Y;Lycée Gen.et Technol.prive Champagnat;250 Rue Chanoine Pavailler;69590;St Symphorien Sur Coise;45.6291908;4.4521899 +0542408Z;Lycée Gen.et Technol.prive Charles De Foucauld;1 Rue Jeannot;54000;Nancy;48.6894923;6.1881706 +0690514J;Lycée Gen.et Technol.prive Charles De Foucauld;6 Rue Bara;69003;Lyon 03;45.7514974;4.871623 +0754086J;Lycée Gen.et Technol.prive Charles De Foucauld;5 Rue De La Madone;75018;Paris 18;48.8927022;2.3612027 +0753890W;Lycée Gen.et Technol.prive Charles Peguy;54 Avenue De La Republique;75011;Paris 11;48.8649983;2.375791 +0530049R;Lycée Gen.et Technol.prive D'avesnieres;51 Rue D Avesnieres;53012;Laval;48.0625624;-0.7676675 +0441982R;Lycée Gen.et Technol.prive De Bretagne;15 Rue Du Landreau;44300;Nantes;47.2324995;-1.5206435 +0570311D;Lycée Gen.et Technol.prive De La Salle;2 Rue St Maximin;57070;Metz;49.1048183;6.1935928 +0690542P;Lycée Gen.et Technol.prive Des Chassagnes;13 Chemin Des Chassagnes;69600;Oullins;45.7227891;4.8097245 +0541309E;Lycée Gen.et Technol.prive Des Recollets;44 Rue Du General Pershing;54400;Longwy;49.5170129;5.7657012 +0060773H;Lycée Gen.et Technol.prive Don Bosco;40 Place Don Bosco;06046;Nice;43.7086006;7.2813297 +0211068X;Lycée Gen.et Technol.prive Du Saint Coeur;3 Rue Du Faubourg St Nicolas;21200;Beaune;47.0279594;4.8395949 +0672050P;Lycée Gen.et Technol.prive Ecole Commerciale Privée;19 Rue De Wissembourg;67000;Strasbourg;48.591122;7.741682 +0300080Z;Lycée Gen.et Technol.prive Emmanuel D Alzon;11 Rue Sainte Perpetue;30020;Nimes;43.834299;4.3718 +0010069V;Lycée Gen.et Technol.prive Ensemble Scolaire Lamartine;41 Rue Georges Girerd;01302;Belley;45.7612095;5.6848602 +0941719H;Lycée Gen.et Technol.prive Epin;19 Avenue Eugene Pelletan;94400;Vitry Sur Seine;48.7942096;2.3847676 +9730010Y;Lycée Gen.et Technol.prive Externat Saint Joseph;15 Rue Lallouette;97326;Cayenne;4.9393005;-52.3308137 +0690521S;Lycée Gen.et Technol.prive Externat Sainte Marie;4 Montee Saint Barthelemy;69321;Lyon 05;45.7655825;4.8268167 +0060674A;Lycée Gen.et Technol.prive Fenelon;7 Avenue Yves Emmanuel Baudouin;06130;Grasse;43.6589192;6.9208425 +0761708U;Lycée Gen.et Technol.prive Fenelon;2 Rue D'alsace;76501;Elbeuf;49.29007;1.0141247 +0932036H;Lycée Gen.et Technol.prive Francoise Cabrini;20 Rue Du Docteur Sureau;93167;Noisy Le Grand;48.8461618;2.5491228 +0593007Y;Lycée Gen.et Technol.prive Frederic Ozanam;50 Rue Saint Gabriel;59045;Lille;50.6393334;3.0867728 +0440175B;Lycée Gen.et Technol.prive Gabriel Deshayes;Route De Redon;44530;St Gildas Des Bois;47.5223759;-2.044929 +0631068U;Lycée Gen.et Technol.prive Godefroy De Bouillon;14 Rue Godefroy De Bouillon;63037;Clermont Ferrand;45.781122;3.0929055 +0370727P;Lycée Gen.et Technol.prive Grand St Gregoire;3 Quai Paul Bert;37081;Tours;47.4014833;0.685655 +0940821G;Lycée Gen.et Technol.prive Gregor Mendel;205 Rue De Fontenay;94300;Vincennes;48.8488616;2.4272778 +0601702Z;Lycée Gen.et Technol.prive Guynemer;8 Bis Av De La Foret;60205;Compiegne;49.4087315;2.840105 +0910823A;Lycée Gen.et Technol.prive Ile De France;7 Rue Du Baron De Niviere;91140;Villebon Sur Yvette;48.7000351;2.2570051 +0640130U;Lycée Gen.et Technol.prive Immac.concept.beau Frene;Bd Edouard Herriot;64051;Pau;43.3050247;-0.3782249 +0530048P;Lycée Gen.et Technol.prive Immaculee Conception;15 Rue Crossardiere;53000;Laval;48.0734452;-0.7680567 +0592965C;Lycée Gen.et Technol.prive Industriel Et Commercial Privé;27 Rue Du Dragon;59202;Tourcoing;50.7191345;3.1488557 +9720070U;Lycée Gen.et Technol.prive Institut Martiniquais D'etudes;74 Rue Du Pr Raymond Garcin;97200;Fort De France;14.609371;-61.07256 +0595657D;Lycée Gen.et Technol.prive Institut Saint Vincent De Paul;6 Rue Du Marechal Joffre;59120;Loos;50.6162933;3.0052777 +0500120J;Lycée Gen.et Technol.prive Institut Saint-lo;18 Rue De L'oratoire;50180;Agneaux;49.1184709;-1.1060995 +0681917P;Lycée Gen.et Technol.prive Institution Don Bosco;60 Rue D'ensisheim;68272;Wittenheim;47.8136036;7.3394336 +0671610L;Lycée Gen.et Technol.prive Institution Notre Dame;6 Rue Des Bonnes Gens;67082;Strasbourg;48.5880447;7.7433479 +0671602C;Lycée Gen.et Technol.prive Institution Ste Philomene;19a Boulevard Hanauer;67504;Haguenau;48.8113152;7.7888563 +0622150A;Lycée Gen.et Technol.prive Jean Bosco;19 Place Du Marechal Foch;62340;Guines;50.869395;1.8697706 +0761721H;Lycée Gen.et Technol.prive Jean Xxiii;16 Rue De La Gare;76191;Yvetot;49.6223882;0.7592408 +0850076W;Lycée Gen.et Technol.prive Jean Xxiii;Avenue Des Sables;85505;Les Herbiers;46.8650254;-1.0292923 +0690671E;Lycée Gen.et Technol.prive Jean-baptiste De La Salle;1 Rue Neyret;69283;Lyon 01;45.7715543;4.8280213 +0761715B;Lycée Gen.et Technol.prive Jean-baptiste De La Salle;84 Rue Saint-gervais;76042;Rouen;49.4481293;1.08442 +0541320S;Lycée Gen.et Technol.prive Jean-baptiste Vatelot;6 Rue De La Republique;54203;Toul;48.6729066;5.8919085 +0132300E;Lycée Gen.et Technol.prive Jeanne D Arc;5 Chemin Saint Roch;13632;Arles;43.6743556;4.6432771 +0610694Y;Lycée Gen.et Technol.prive Jeanne D Arc;10 Rue Du College;61203;Argentan;48.7461287;-0.0164951 +0680151V;Lycée Gen.et Technol.prive Jeanne D Arc;15 Rue Du Chanoine Brun;68090;Mulhouse;47.7434603;7.3370328 +0910815S;Lycée Gen.et Technol.prive Jeanne D Arc;11 Bd Henri Iv;91150;Etampes;48.4358438;2.1576582 +0920918Y;Lycée Gen.et Technol.prive Jeanne D Arc;44 Rue Gabriel Peri;92120;Montrouge;48.8187323;2.3185306 +0921663H;Lycée Gen.et Technol.prive Jeanne D Arc;9 Boulevard De Valmy;92700;Colombes;48.9248684;2.2526402 +0390091M;Lycée Gen.et Technol.prive Jeanne D'arc;10 Rue Du Sauget;39303;Champagnole;46.7456228;5.9058307 +0710112L;Lycée Gen.et Technol.prive Jeanne D'arc;17 Rue Pasteur;71600;Paray Le Monial;46.4526677;4.1219752 +0880097C;Lycée Gen.et Technol.prive Jeanne D'arc;6 Rue Du Canton;88202;Remiremont;48.0192476;6.5872824 +0690535G;Lycée Gen.et Technol.prive Jeanne De Lestonnac;132 Rue Vendome;69006;Lyon 06;45.7648477;4.8456527 +0761716C;Lycée Gen.et Technol.prive Join-lambert;39 Rue De L'avalasse;76044;Rouen;49.4489928;1.0980641 +0070064G;Lycée Gen.et Technol.prive Jules Froment;3 Rue Albert Seibel;07201;Aubenas;44.6172525;4.3886254 +0331495W;Lycée Gen.et Technol.prive L Assomption;370 Bd President Wilson;33000;Bordeaux;44.8520362;-0.5944037 +0783283V;Lycée Gen.et Technol.prive L Ermitage;18 Rue Des Cotes;78600;Maisons Laffitte;48.9430565;2.1455384 +0430063K;Lycée Gen.et Technol.prive La Bruyere;1 Boulevard Des Passementiers;43140;St Didier En Velay;45.2992188;4.2760259 +0132828D;Lycée Gen.et Technol.prive La Cadenelle;134 Bd Des Liberateurs;13012;Marseille 12;43.3020176;5.4572235 +0430055B;Lycée Gen.et Technol.prive La Chartreuse;Rue Du Pont De La Chartreuse;43700;Brives Charensac;45.0529219;3.9138113 +0690520R;Lycée Gen.et Technol.prive La Favorite Sainte Therese;62 Rue De La Favorite;69322;Lyon 05;45.7578457;4.8120645 +0601703A;Lycée Gen.et Technol.prive La Maison Francaise;La Chesnoye;60350;Cuise La Motte;49.372412;2.996137 +9710062R;Lycée Gen.et Technol.prive La Perseverance Mixte;Boissard;97182;Pointe A Pitre;16.2386048;-61.5386617 +0021872Z;Lycée Gen.et Technol.prive La Providence;2 Rue Clerjot;02001;Laon;49.5636589;3.6226864 +0761707T;Lycée Gen.et Technol.prive La Providence;Chemin Des Vertus;76371;Dieppe;49.905225;1.074298 +0761713Z;Lycée Gen.et Technol.prive La Providence;6 Rue De Neuvillette;76240;Le Mesnil Esnard;49.4064428;1.1403376 +0763112V;Lycée Gen.et Technol.prive La Providence;35 Rue Queue De Renard;76400;Fecamp;49.7565044;0.3888415 +0920916W;Lycée Gen.et Technol.prive La Source;11 Rue Ernest Renan;92190;Meudon;48.8188276;2.2268149 +0340871M;Lycée Gen.et Technol.prive La Trinite;6 Avenue Jean Moulin;34503;Beziers;43.3467234;3.219544 +0640111Y;Lycée Gen.et Technol.prive Largente;42 Avenue Jacques Loeb;64115;Bayonne;43.4776655;-1.4841692 +0541305A;Lycée Gen.et Technol.prive L'assomption;1 Rue Foch;54150;Briey;49.2499091;5.9385613 +0783297K;Lycée Gen.et Technol.prive Le Bon Sauveur;6 Rue Henri Cloppet;78110;Le Vesinet;48.8873186;2.1463532 +0040034R;Lycée Gen.et Technol.prive Le Sacre Coeur;2 Avenue Des Thermes;04000;Digne Les Bains;44.0917238;6.2407469 +0251024C;Lycée Gen.et Technol.prive Les Augustins;1 Rue Du Fbg St Etienne;25300;Pontarlier;46.9020595;6.3592476 +0783293F;Lycée Gen.et Technol.prive Les Chataigniers;11 Bis Avenue Jean Jaures;78000;Versailles;48.8174674;2.1442539 +0860755D;Lycée Gen.et Technol.prive Les Feuillants;15 Rue Des Feuillants;86035;Poitiers;46.5844278;0.3491711 +0753820V;Lycée Gen.et Technol.prive Les Francs Bourgeois;21 Rue Saint Antoine;75004;Paris 04;48.8535159;2.3657551 +9710991A;Lycée Gen.et Technol.prive Les Perseverants;28 Rue Lardenoy;97100;Basse Terre;15.9932345;-61.7242003 +0753815P;Lycée Gen.et Technol.prive Les Petits Champs;19 Rue Voltaire;75011;Paris 11;48.8525604;2.3915183 +0840059H;Lycée Gen.et Technol.prive Louis Pasteur;13 Rue Du Pont Trouca;84071;Avignon;43.9478109;4.8132766 +0754965P;Lycée Gen.et Technol.prive Lucien De Hirsch;70 Avenue Secretan;75019;Paris 19;48.8797122;2.3764183 +9840017W;Lycée Gen.et Technol.prive Lycée-college La Mennais;Papeete;98714;Papeete;-17.535022;-149.569594 +0921365J;Lycée Gen.et Technol.prive Madeleine Danielou;61 63 Rue Du General Miribel;92500;Rueil Malmaison;48.8614432;2.1711774 +9710054G;Lycée Gen.et Technol.prive Maitrise De Massabielle;29 Faubourg Victor Hugo;97157;Pointe A Pitre;16.241111;-61.533056 +0592980U;Lycée Gen.et Technol.prive Marie Noel;31 Rue De Renaix;59200;Tourcoing;50.7228956;3.1689614 +0801743K;Lycée Gen.et Technol.prive Montalembert;3 Route D Amiens;80600;Doullens;50.153554;2.3391028 +0331506H;Lycée Gen.et Technol.prive Montesquieu;31 Rue Jules Simon;33500;Libourne;44.9133394;-0.2462284 +0490828J;Lycée Gen.et Technol.prive N-d De Bonnes Nouvelles;3 Rue Mongazon;49600;Beaupreau;47.1982814;-0.9966793 +0850079Z;Lycée Gen.et Technol.prive Nd De La Tourteliere;Rue Buffon;85700;Pouzauges;46.773501;-0.835248 +0720834R;Lycée Gen.et Technol.prive Nd De Ste Croix;25 Rue Antoine De St Exupery;72000;Le Mans;48.0000944;0.2133997 +0950761L;Lycée Gen.et Technol.prive N-dame De La Compassion;8 Place Nicolas Flamel;95300;Pontoise;49.0520594;2.0991487 +0920919Z;Lycée Gen.et Technol.prive N-dame De Ste Croix;30 Avenue Du Roule;92200;Neuilly Sur Seine;48.8819566;2.2806369 +0783351U;Lycée Gen.et Technol.prive N-dame Du Grandchamp;97 Rue Royale;78000;Versailles;48.7933549;2.1239896 +0280665W;Lycée Gen.et Technol.prive Notre Dame;2 Avenue Bethouart;28000;Chartres;48.4511272;1.4921613 +0720837U;Lycée Gen.et Technol.prive Notre Dame;23 Av Francois Mitterrand;72000;Le Mans;48.0038785;0.1997462 +0783288A;Lycée Gen.et Technol.prive Notre Dame;3 Rue De Temara;78100;St Germain En Laye;48.8940298;2.0754316 +0783289B;Lycée Gen.et Technol.prive Notre Dame;5 Rue De La Sangle;78200;Mantes La Jolie;48.9886407;1.7215203 +0783344L;Lycée Gen.et Technol.prive Notre Dame;106 Grande Rue;78480;Verneuil Sur Seine;48.9826866;1.9733268 +0830100H;Lycée Gen.et Technol.prive Notre Dame;29 Boulevard Abbe Duploye;83100;Toulon;43.1279722;5.956097 +0920897A;Lycée Gen.et Technol.prive Notre Dame;1 Avenue Charles De Gaulle;92100;Boulogne Billancourt;48.8467426;2.2368542 +0880101G;Lycée Gen.et Technol.prive Notre Dame - Saint Joseph;23 Rue Thiers;88012;Epinal;48.1701687;6.4513835 +0693267B;Lycée Gen.et Technol.prive Notre Dame De Bel Air;5 Avenue Des Belges;69170;Tarare;45.8904997;4.4380599 +0660059F;Lycée Gen.et Technol.prive Notre Dame De Bon Secours;39 Avenue Julien Panchot;66028;Perpignan;42.6915814;2.8827875 +0340881Y;Lycée Gen.et Technol.prive Notre Dame De La Merci;62 Cours Gambetta;34965;Montpellier;43.6082615;3.8686748 +0570313F;Lycée Gen.et Technol.prive Notre Dame De La Providence;22 Place Notre Dame;57126;Thionville;49.3576246;6.1602375 +0950753C;Lycée Gen.et Technol.prive Notre Dame De La Providence;7 Boulevard Sadi Carnot;95880;Enghien Les Bains;48.967214;2.3122446 +0601701Y;Lycée Gen.et Technol.prive Notre Dame De La Tilloye;1 Avenue De La Liberation;60204;Compiegne;49.4062037;2.8305249 +0170103V;Lycée Gen.et Technol.prive Notre Dame De Recouvrance;88 Cours Genet;17100;Saintes;45.7486442;-0.65005 +0910816T;Lycée Gen.et Technol.prive Notre Dame De Sion;1 Avenue De Ratisbonne;91000;Evry;48.6437303;2.4357094 +0900029T;Lycée Gen.et Technol.prive Notre Dame Des Anges;46 Bis Faubourg De Montbeliard;90000;Belfort;47.6334827;6.8570422 +0410675L;Lycée Gen.et Technol.prive Notre Dame Des Aydes;7 Rue Franciade;41000;Blois;47.5897279;1.3293085 +0381683Y;Lycée Gen.et Technol.prive Notre Dame Des Victoires;1 Rue De La Terrasse;38506;Voiron;45.367053;5.59632 +0430058E;Lycée Gen.et Technol.prive Notre Dame Du Chateau;Montee Du Prince;43120;Monistrol Sur Loire;45.292432;4.172183 +0470063N;Lycée Gen.et Technol.prive Notre Dame La Compassion;1 Ter Rue De Langeot;47200;Marmande;44.4978288;0.1723321 +0480025S;Lycée Gen.et Technol.prive Notre-dame;Quartier Fontanilles;48000;Mende;44.517611;3.501873 +0920906K;Lycée Gen.et Technol.prive Notre-dame;65 Avenue Du General Leclerc;92340;Bourg La Reine;48.7817469;2.3162608 +0920917X;Lycée Gen.et Technol.prive Notre-dame;24 Rue Alexandre Guilmant;92190;Meudon;48.8157744;2.2403627 +0950785M;Lycée Gen.et Technol.prive Notre-dame;106 Bd Charles De Gaulle;95110;Sannois;48.9730488;2.2543252 +0592916Z;Lycée Gen.et Technol.prive Notre-dame De Grace;Quai Des Nerviens;59602;Maubeuge;50.2764329;3.9688679 +0753842U;Lycée Gen.et Technol.prive Notre-dame De Sion;61 R Notre Dame Des Champs;75006;Paris 06;48.84375;2.3310368 +0593036E;Lycée Gen.et Technol.prive Notre-dame Du Tilleul;48 Place De L'industrie;59600;Maubeuge;50.2706855;3.9481957 +0951940T;Lycée Gen.et Technol.prive Ozar Hatorah;1 Rue Jean Lurcat;95200;Sarcelles;48.9827985;2.3890294 +0754666P;Lycée Gen.et Technol.prive Passy-saint-honore;117 Avenue Victor Hugo;75016;Paris 16;48.8682833;2.282365 +9710055H;Lycée Gen.et Technol.prive Pensionnat De Versailles;8 Rue Victor Hugues;97100;Basse Terre;16.1985639;-61.5901551 +0341521U;Lycée Gen.et Technol.prive Pierre Rouge;85 Rue Lunaret;34090;Montpellier;43.6207134;3.8837999 +0381666E;Lycée Gen.et Technol.prive Pierre Termier;5 Bis Rue Joseph Fourier;38028;Grenoble;45.1889828;5.7349988 +0341765J;Lycée Gen.et Technol.prive Rabelais;36 Rue Des Aiguerelles;34000;Montpellier;43.6026039;3.883383 +0920898B;Lycée Gen.et Technol.prive Rambam;11 Rue Des Abondances;92100;Boulogne Billancourt;48.8431743;2.2294952 +0761718E;Lycée Gen.et Technol.prive Rey;15 Rue Verte;76000;Rouen;49.4479795;1.0934318 +0941720J;Lycée Gen.et Technol.prive Robert Schuman;5-6 Rue De L'eglise;94340;Joinville Le Pont;48.8192804;2.4664616 +0381678T;Lycée Gen.et Technol.prive Robin;Place Saint Pierre;38204;Vienne;45.5234014;4.8708709 +0430065M;Lycée Gen.et Technol.prive Sacre Coeur;11 Place Charles De Gaulle;43200;Yssingeaux;45.1433694;4.1235441 +0492015Z;Lycée Gen.et Technol.prive Sacre Coeur;2 Rue Millet;49101;Angers;47.4727929;-0.556212 +0801207C;Lycée Gen.et Technol.prive Sacre Coeur;1 Rue De L Oratoire;80007;Amiens;49.8933658;2.3044821 +0801226Y;Lycée Gen.et Technol.prive Sacre Coeur;36 Boulevard Des Anglais;80200;Peronne;49.9264177;2.9342576 +0910824B;Lycée Gen.et Technol.prive Sacre Coeur;Passage De Graville;91620;La Ville Du Bois;48.6609531;2.2674756 +0131862D;Lycée Gen.et Technol.prive Sacre Coeur (le);29 Rue Manuel;13100;Aix En Provence;43.5282729;5.453657 +0761719F;Lycée Gen.et Technol.prive Sacre-coeur;31 32 Rue Blaise Pascal;76176;Rouen;49.4297216;1.0808737 +0631075B;Lycée Gen.et Technol.prive Saint Alyre;20 Rue Sainte George;63037;Clermont Ferrand;45.7834986;3.0818986 +0030084A;Lycée Gen.et Technol.prive Saint Benoit;4 Rue Achille Roche;03008;Moulins;46.5632283;3.3286114 +0690563M;Lycée Gen.et Technol.prive Saint Bruno-saint Louis;16 Rue Des Chartreux;69283;Lyon 01;45.7719779;4.8230673 +0382854W;Lycée Gen.et Technol.prive Saint Charles;1 Place Des Capucins;38217;Vienne;45.5275795;4.8766132 +0450110A;Lycée Gen.et Technol.prive Saint Charles;24 Rue Des Grands Champs;45058;Orleans;47.9055842;1.8998948 +0790058J;Lycée Gen.et Technol.prive Saint Charles;1 Rue Jules Michelet;79101;Thouars;46.9785341;-0.2100378 +0580062C;Lycée Gen.et Technol.prive Saint Cyr;22 Rue Jeanne D Arc;58000;Nevers;46.991543;3.1530512 +0071126L;Lycée Gen.et Technol.prive Saint Denis;1 Chemin De La Muette;07104;Annonay;45.2411669;4.6779477 +0601699W;Lycée Gen.et Technol.prive Saint Esprit;68 Rue De Pontoise;60026;Beauvais;49.4192451;2.086629 +0593136N;Lycée Gen.et Technol.prive Saint Jacques;58 Rue De La Sous Prefecture;59190;Hazebrouck;50.7232207;2.5301757 +0251028G;Lycée Gen.et Technol.prive Saint Jean;1 Rue De L'esperance;25000;Besancon;47.2589558;6.0204846 +0133822J;Lycée Gen.et Technol.prive Saint Jean De Garguier;Chemin Du Puits;13420;Gemenos;43.2845374;5.6190148 +0141173Z;Lycée Gen.et Technol.prive Saint Jean Eudes;2 Avenue De La Gare;14503;Vire;48.8457455;-0.8844612 +0010075B;Lycée Gen.et Technol.prive Saint Joseph;101 Rue Henri Grobon;01705;Miribel;45.825157;4.9512247 +0021871Y;Lycée Gen.et Technol.prive Saint Joseph;2 Chaussee De Fontaine;02140;Fontaine Les Vervins;49.8365787;3.906547 +0030445T;Lycée Gen.et Technol.prive Saint Joseph;11 Rue Du Fg De La Gironde;03100;Montlucon;46.3411588;2.6080734 +0371306U;Lycée Gen.et Technol.prive Saint Joseph;1 Place Saint Mexme;37502;Chinon;47.167917;0.24509 +0740100G;Lycée Gen.et Technol.prive Saint Joseph;5 Avenue Du Leman;74200;Thonon Les Bains;46.3774405;6.4835126 +0830098F;Lycée Gen.et Technol.prive Saint Joseph;2229 Route De Faveyrolles;83190;Ollioules;43.139436;5.8845344 +0572340J;Lycée Gen.et Technol.prive Saint Joseph-la Providence;2 Avenue Du General Passaga;57600;Forbach;49.1853591;6.9006303 +0592926K;Lycée Gen.et Technol.prive Saint Jude;18 Rue Lamartine;59280;Armentieres;50.6867725;2.8756446 +0430053Z;Lycée Gen.et Technol.prive Saint Julien;7 Place Du Valla;43101;Brioude;45.2911231;3.3878825 +0170098P;Lycée Gen.et Technol.prive Saint Louis;73 Avenue Andre Malraux;17250;Pont L Abbe D Arnoult;45.829429;-0.8705604 +0550047C;Lycée Gen.et Technol.prive Saint Louis;28 Rue Voltaire;55001;Bar Le Duc;48.7719202;5.1608526 +0160062F;Lycée Gen.et Technol.prive Saint Paul;101 Rue De Beaulieu;16007;Angouleme;45.65054;0.1489427 +0251021Z;Lycée Gen.et Technol.prive Saint Paul;8 Boulevard Diderot;25000;Besancon;47.2453023;6.0351931 +0622114L;Lycée Gen.et Technol.prive Saint Paul;38 Route De La Bassee;62301;Lens;50.4365139;2.8216873 +0420973E;Lycée Gen.et Technol.prive Saint Paul Forez;13 Rue Du College;42603;Montbrison;45.6084481;4.0659129 +0010070W;Lycée Gen.et Technol.prive Saint Pierre;7 Rue Villeneuve;01001;Bourg En Bresse;46.1996249;5.2230636 +0030072M;Lycée Gen.et Technol.prive Saint Pierre;26 Allee Pierre Berthomier;03304;Cusset;46.1342557;3.4580004 +0622110G;Lycée Gen.et Technol.prive Saint Pierre;72 Rue Arago;62100;Calais;50.9413829;1.8641701 +0801206B;Lycée Gen.et Technol.prive Saint Pierre;24 Place Clemenceau;80103;Abbeville;50.1087199;1.8347016 +0021874B;Lycée Gen.et Technol.prive Saint Remy;8 Rue St Jean;02204;Soissons;49.3763018;3.3235831 +0801874C;Lycée Gen.et Technol.prive Saint Riquier;50 Chaussee Jules Ferry;80094;Amiens;49.8829134;2.3258793 +0690543R;Lycée Gen.et Technol.prive Saint Thomas D'aquin Veritas;56 Rue Du Perron;69600;Oullins;45.711866;4.8086923 +0601149Y;Lycée Gen.et Technol.prive Saint Vincent;30 Rue De Meaux;60304;Senlis;49.203401;2.5889031 +0271053Y;Lycée Gen.et Technol.prive Saint-adjutor;54 Rue De Marzelles;27200;Vernon;49.0859189;1.4575149 +0680149T;Lycée Gen.et Technol.prive Saint-andre;19 Rue Rapp;68025;Colmar;48.0805738;7.3597557 +0271282X;Lycée Gen.et Technol.prive Saint-anselme;11/13 R Leprevost De Beaumont;27300;Bernay;49.0942864;0.6045856 +0420974F;Lycée Gen.et Technol.prive Sainte Anne;4 Rue Saint Alban;42300;Roanne;46.0363817;4.0633804 +0550049E;Lycée Gen.et Technol.prive Sainte Anne;14 Rue Mautrote;55104;Verdun;49.1604635;5.3812032 +0801209E;Lycée Gen.et Technol.prive Sainte Famille;13 Rue De Castille;80017;Amiens;49.8882835;2.3096003 +0550048D;Lycée Gen.et Technol.prive Sainte Jeanne D'arc;23 Rue Poincare;55200;Commercy;48.7606796;5.5902284 +0592928M;Lycée Gen.et Technol.prive Sainte Jeanne D'arc;157 Rue De L'hotel De Ville;59620;Aulnoye Aymeries;50.2001783;3.8323903 +0060670W;Lycée Gen.et Technol.prive Sainte Marie;4 Avenue Windsor;06400;Cannes;43.5514185;7.0307566 +0390070P;Lycée Gen.et Technol.prive Sainte Marie Fenelon;84 Rue St Desire;39000;Lons Le Saunier;46.6706079;5.5489595 +0030073N;Lycée Gen.et Technol.prive Sainte Procule;22 Rue Des Augustins;03800;Gannat;46.0996289;3.1938761 +0631070W;Lycée Gen.et Technol.prive Sainte Thecle;7 Rue Amelie Murat;63402;Chamalieres;45.7771931;3.0670184 +0592929N;Lycée Gen.et Technol.prive Sainte Therese;4 Place Guillemin;59362;Avesnes Sur Helpe;50.1225956;3.9322081 +0753844W;Lycée Gen.et Technol.prive Sainte-genevieve;64 Rue D'assas;75006;Paris 06;48.8458567;2.331337 +0753852E;Lycée Gen.et Technol.prive Sainte-jeanne Elisabeth;8 Rue Maurice De La Sizeranne;75007;Paris 07;48.8473086;2.3147319 +0900030U;Lycée Gen.et Technol.prive Sainte-marie;40 Fbg Des Ancetres;90006;Belfort;47.6405457;6.8573468 +0271045P;Lycée Gen.et Technol.prive Saint-francois De Sales;8 Rue Portevin;27025;Evreux;49.0165487;1.1504096 +0761710W;Lycée Gen.et Technol.prive Saint-joseph;207 Rue Felix Faure;76072;Le Havre;49.5005512;0.1056686 +0681793E;Lycée Gen.et Technol.prive Saint-joseph De Cluny;53 Avenue Roger Salengro;68100;Mulhouse;47.7509394;7.347119 +0271049U;Lycée Gen.et Technol.prive Saint-ouen;30 Rue Sadi Carnot;27500;Pont Audemer;49.3571648;0.5145026 +0060677D;Lycée Gen.et Technol.prive Sasserno;1 Place Sasserno;06000;Nice;43.7026106;7.2711176 +9720829U;Lycée Gen.et Technol.prive Seminaire College Ste Marie;27 Rue Martin Luther King;97247;Fort De France;14.604913;-61.075087 +0911935J;Lycée Gen.et Technol.prive Senart-enseignement;9 Rue De L Industrie;91210;Draveil;48.6837846;2.4333977 +0421034W;Lycée Gen.et Technol.prive Sevigne;29 Rue Michelet;42000;St Etienne;45.434245;4.3898936 +0631033F;Lycée Gen.et Technol.prive Sevigne Saint Louis;2 Avenue De La Liberation;63500;Issoire;45.5411893;3.2487329 +0920907L;Lycée Gen.et Technol.prive Sophie Barat;50 Rue Des Grillons;92290;Chatenay Malabry;48.7579109;2.2704913 +0470060K;Lycée Gen.et Technol.prive St Caprais;8 Rue Raspail;47000;Agen;44.2073871;0.6188656 +0640131V;Lycée Gen.et Technol.prive St Dominique;30 Av Fouchet;64000;Pau;43.3139559;-0.3757428 +0920921B;Lycée Gen.et Technol.prive St Dominique;23 Quater Bd D Argenson;92200;Neuilly Sur Seine;48.8881037;2.2648464 +0131391S;Lycée Gen.et Technol.prive St Eloi;9 Avenue Jules Isaac;13626;Aix En Provence;43.5348838;5.4513323 +0783286Y;Lycée Gen.et Technol.prive St Erembert;5 Rue Salomon Reinach;78100;St Germain En Laye;48.8958802;2.0996683 +0781899R;Lycée Gen.et Technol.prive St Francois D Assise;6 Place Paul Claudel;78180;Montigny Le Bretonneux;48.7790268;2.0410595 +0920894X;Lycée Gen.et Technol.prive St Gabriel;21 Rue De La Lisette;92220;Bagneux;48.7925201;2.3011991 +0401016M;Lycée Gen.et Technol.prive St Jacques De Compostelle;32 Rue Lahargou;40100;Dax;43.6951039;-1.057711 +0783350T;Lycée Gen.et Technol.prive St Jean Hulst;26 R Mal De Lattre De Tassigny;78000;Versailles;48.8179183;2.1368041 +0021873A;Lycée Gen.et Technol.prive St Jean La Croix;25 Rue Antoine Lecuyer;02105;St Quentin;49.85011;3.2855838 +0240079F;Lycée Gen.et Technol.prive St Joseph;23 Avenue Georges Pompidou;24000;Perigueux;45.1889317;0.7230677 +0440149Y;Lycée Gen.et Technol.prive St Joseph;66 Rue Du College;44153;Ancenis;47.3665584;-1.1794334 +0440151A;Lycée Gen.et Technol.prive St Joseph;40 Rue Michel Grimault;44141;Chateaubriant;47.7156138;-1.3765267 +0440201E;Lycée Gen.et Technol.prive St Joseph;14 Rue Des Capucins;44270;Machecoul;46.9932154;-1.8232442 +0541308D;Lycée Gen.et Technol.prive St Joseph;413 Avenue De Boufflers;54524;Laxou;48.6934935;6.1381862 +0640137B;Lycée Gen.et Technol.prive St Joseph;650 Rue Hiribehere;64480;Ustaritz;43.4045104;-1.462666 +0840072X;Lycée Gen.et Technol.prive St Joseph;62 Rue Des Lices;84000;Avignon;43.9455461;4.8107194 +9720063L;Lycée Gen.et Technol.prive St Joseph De Cluny;22 Route De Cluny;97200;Fort De France;14.627187;-61.0738508 +0131339K;Lycée Gen.et Technol.prive St Joseph De La Madeleine;172 B Bd De La Liberation;13248;Marseille 04;43.3020176;5.3944529 +0331502D;Lycée Gen.et Technol.prive St Joseph De Tivoli;40 Av D Eysines;33073;Bordeaux;44.8538365;-0.5966724 +0601831P;Lycée Gen.et Technol.prive St Joseph Du Moncel;8 Place De L Eglise;60722;Pont Ste Maxence;49.3158812;2.6215379 +0131331B;Lycée Gen.et Technol.prive St Joseph Les Maristes;24 Rue Sainte Victoire;13006;Marseille 06;43.2862953;5.382018 +0771237B;Lycée Gen.et Technol.prive St Laurent-la Paix Notre Dame;43 Rue Alfred Brebion;77400;Lagny Sur Marne;48.8734226;2.7086692 +0490838V;Lycée Gen.et Technol.prive St Louis;47 Rue D Alsace;49400;Saumur;47.2588834;-0.0854513 +0133314G;Lycée Gen.et Technol.prive St Louis - Ste Marie;Allee Saint Louis - Rn 568;13180;Gignac La Nerthe;43.390916;5.233463 +0430059F;Lycée Gen.et Technol.prive St Louis Notre Dame De France;5 Rue Latour Maubourg;43010;Le Puy En Velay;45.042305;3.8789494 +0910826D;Lycée Gen.et Technol.prive St Louis St Clement;1 Rue Margot;91170;Viry Chatillon;48.6718555;2.372288 +0530046M;Lycée Gen.et Technol.prive St Michel;5 Rue Henri Dunant;53204;Chateau Gontier;47.8312371;-0.7100198 +0922353H;Lycée Gen.et Technol.prive St Nicolas;19 Rue Victor Hugo;92130;Issy Les Moulineaux;48.8264404;2.2758007 +0280667Y;Lycée Gen.et Technol.prive St Pierre St Paul;16 Boulevard Dubois;28109;Dreux;48.7375877;1.3745466 +0640134Y;Lycée Gen.et Technol.prive St Thomas D Aquin;Rue Biscarbidea;64500;St Jean De Luz;43.388051;-1.663055 +0920908M;Lycée Gen.et Technol.prive St Thomas De Villeneuve;1646 Avenue Roger Salengro;92370;Chaville;48.8075482;2.1862517 +0131403E;Lycée Gen.et Technol.prive St Vincent De Paul;30 Rue Stanislas Torrents;13006;Marseille 06;43.287572;5.3782713 +0060673Z;Lycée Gen.et Technol.prive Stanislas;1 Place Stanislas;06403;Cannes;43.5468601;6.9368506 +0490819Z;Lycée Gen.et Technol.prive Ste Agnes;7 Rue Volney;49000;Angers;47.464216;-0.5472721 +0920889S;Lycée Gen.et Technol.prive Ste Genevieve;19 Rue De La Station;92600;Asnieres Sur Seine;48.9068047;2.2853198 +0132810J;Lycée Gen.et Technol.prive Ste Marie;13 Rue Jeu De Ballon;13400;Aubagne;43.2916499;5.5711486 +0920875B;Lycée Gen.et Technol.prive Ste Marie;24 Boulevard Victor Hugo;92200;Neuilly Sur Seine;48.8850578;2.2811906 +0331501C;Lycée Gen.et Technol.prive Ste Marie De La Bastide;45 Rue De Dijon;33100;Bordeaux;44.8425946;-0.5526472 +0920904H;Lycée Gen.et Technol.prive Ste Marie La Croix;2 Rue De L Abbaye;92160;Antony;48.7539117;2.2999629 +0180561M;Lycée Gen.et Technol.prive Ste Marie St Dominique;38 Rue Jean Baffier;18020;Bourges;47.077094;2.4032838 +0240076C;Lycée Gen.et Technol.prive Ste Marthe - St Front;74 Av Pasteur;24100;Bergerac;44.8601889;0.4951121 +0781664K;Lycée Gen.et Technol.prive Ste Therese;7 Rue Beziel;78120;Rambouillet;48.6454729;1.8336261 +0370731U;Lycée Gen.et Technol.prive Ste Ursule;26 Rue Emile Zola;37009;Tours;47.3938697;0.6906468 +0940878U;Lycée Gen.et Technol.prive Teilhard De Chardin;2 Place D'armes;94100;St Maur Des Fosses;48.8125041;2.4720666 +0420979L;Lycée Gen.et Technol.prive Tezenas Du Montcel;14 Place Girodet;42000;St Etienne;45.4480193;4.3809644 +0501299R;Lycée Gen.et Technol.prive Thomas Helye;37 Rue Emile Zola;50100;Cherbourg Octeville;49.6370758;-1.6291084 +0951048Y;Lycée Gen.et Technol.prive Torat Emet;14 Av Charles Peguy;95200;Sarcelles;48.9811624;2.3754349 +0860903P;Lycée Gen.et Technol.prive Union Chretienne;2 Plan Sainte Croix;86011;Poitiers;46.5792977;0.3487512 +0951221L;Lycée Gen.et Technol.prive Vauban;23 Place Du Petit Martroy;95300;Pontoise;49.0513181;2.0969748 +0671611M;Lycée Gen.prive Instit.la Doctrine Chretienne;14 Rue Brulee;67000;Strasbourg;48.584854;7.752502 +9830484J;Lycée Gen.tech.prof.agricole De Pouembout (agri);Route Munipale 2;98825;Pouembout;0.0;0.0 +0580584V;Lycée Gen.techn.agricole Les Cottereaux;66 Rue Jean Monnet;58200;Cosne Cours Sur Loire;47.3910652;2.9324174 +0190609J;Lycée Gen.technolo Agricole Ecole Forestiere;Meymac;19250;Meymac;45.5362265;2.14547 +0190087S;Lycée Gen.technolo. Agricole Henri Queuille;;19160;Neuvic;45.382449;2.272544 +0422132P;Lycée General;16 Rue Arquilliere;42130;Boen;45.745278;4.004247 +0694069Y;Lycée General;Les Grands Champs;69210;Sain Bel;45.822538;4.599975 +0922615T;Lycée General;13 17 Avenue D Alsace;92400;Courbevoie;48.8908791;2.2503094 +0590212K;Lycée General;80 Boulevard Gambetta;59208;Tourcoing;50.7133268;3.1588623 +0140004D;Lycée General Alain Chartier;Place De La Lombarderie;14402;Bayeux;49.278971;-0.712644 +0180005H;Lycée General Alain Fournier;50 Rue Stephane Mallarme;18016;Bourges;47.1000872;2.4108852 +0590063Y;Lycée General Albert Chatelet;357 Rue Marceline;59508;Douai;50.3738413;3.0855523 +0260022H;Lycée General Albert Triboulet;55 Avenue Gambetta;26102;Romans Sur Isere;45.0453925;5.0568298 +0140043W;Lycée General Andre Maurois;10 Boulevard Cornuche;14800;Deauville;49.3628042;0.0747539 +0290069T;Lycée General Auguste Brizeux;6 Rue Bourg Les Bourgs;29191;Quimper;47.9930436;-4.1116997 +0590182C;Lycée General Baudelaire;23 Avenue Le Notre;59100;Roubaix;50.6804598;3.1673908 +0720030S;Lycée General Bellevue;2 Rue Abbaye St Vincent;72001;Le Mans;48.0121822;0.2021824 +0810005R;Lycée General Bellevue;98 Rue Du Roc;81011;Albi;43.9221442;2.1574435 +0870045B;Lycée General Bernard Palissy;11 R Leon Jouhaux;87400;St Leonard De Noblat;45.8344936;1.4931518 +0240024W;Lycée General Bertran De Born;1 Rue Ch Mangold;24001;Perigueux;45.1802367;0.7199966 +0030044G;Lycée General Blaise De Vigenere;51 Avenue Pasteur;03500;St Pourcain Sur Sioule;46.3112153;3.2927033 +0630018C;Lycée General Blaise Pascal;36 Avenue Carnot;63037;Clermont Ferrand;45.7762478;3.0934525 +0750693W;Lycée General Buffon;16 Boulevard Pasteur;75015;Paris 15;48.8434549;2.3117609 +0101028N;Lycée General Camille Claudel;28 Rue Des Terrasses;10026;Troyes;48.2918918;4.0787778 +0860035W;Lycée General Camille Guerin;33 Rue De La Gibauderie;86022;Poitiers;46.5687695;0.3635822 +0760093N;Lycée General Camille Saint-saens;22 Rue Saint-lo;76005;Rouen;49.4422597;1.0937285 +0750694X;Lycée General Camille See;11 Rue Leon Lhermitte;75015;Paris 15;48.8430219;2.2972982 +0260035X;Lycée General Camille Vernet;160 Rue Faventines;26021;Valence;44.9243431;4.9036578 +0060011E;Lycée General Carnot;90 Boulevard Carnot;06408;Cannes;43.5636838;7.0164947 +0750704H;Lycée General Carnot;145 Boulevard Malesherbes;75017;Paris 17;48.8845308;2.3078305 +0380027Y;Lycée General Champollion;Cours La Fontaine;38026;Grenoble;45.1878714;5.7248743 +0080006N;Lycée General Chanzy;13 Rue Delvincourt;08000;Charleville Mezieres;49.7725131;4.730864 +0480007X;Lycée General Chaptal;Avenue Paulin Daude;48001;Mende;44.520214;3.508123 +0570106F;Lycée General Charlemagne;17 Avenue Clemenceau;57100;Thionville;49.3603724;6.1679017 +0750652B;Lycée General Charlemagne;14 Rue Charlemagne;75004;Paris 04;48.8541627;2.3609691 +0142107P;Lycée General Charles De Gaulle;39 Rue D'hastings;14000;Caen;49.1844909;-0.3794536 +0211928G;Lycée General Charles De Gaulle;25 Av General Touzet Du Vigier;21000;Dijon;47.3495084;5.0418001 +0390012B;Lycée General Charles Nodier;6 Grande Rue;39107;Dole;47.0907375;5.4933764 +0693446W;Lycée General Cite Scolaire Internationale;2 Place De Montreal;69361;Lyon 07;0.0;0.0 +0420041S;Lycée General Claude Fauriel;28 Avenue De La Liberation;42007;St Etienne;45.4373521;4.3929933 +0880020U;Lycée General Claude Gellee;44 Rue Abel Ferry;88021;Epinal;48.1665516;6.4548634 +0740003B;Lycée General Claude Louis Berthollet;9 Boulevard Du Lycée;74008;Annecy;0.0;0.0 +0750683K;Lycée General Claude Monet;1 Rue Du Docteur Magnan;75013;Paris 13;48.8274487;2.3622395 +0511901P;Lycée General Colbert;56 Rue Du Dr Schweitzer;51100;Reims;49.2803364;4.0213284 +0750673Z;Lycée General Colbert;27 Rue De Chateau Landon;75010;Paris 10;48.88268;2.3634216 +0750667T;Lycée General Condorcet;8 Rue Du Havre;75009;Paris 09;48.8747904;2.3269255 +0190011J;Lycée General D Arsonval;Place Du 15 Aout 1944;19100;Brive La Gaillarde;45.1605553;1.530861 +0320025D;Lycée General D'artagnan;Avenue Des Pyrenees;32110;Nogaro;43.754576;-0.029898 +0490001K;Lycée General David D Angers;1 Rue Paul Langevin;49035;Angers;47.4653426;-0.5453201 +0290007A;Lycée General De Kerichen;Rue Prince De Joinville;29801;Brest;48.4043354;-4.4826021 +0290010D;Lycée General De L'harteloire;1 Rue Du Guesclin;29213;Brest;48.4284698;-4.5611577 +0290009C;Lycée General De L'iroise;7 Place De Strasbourg;29223;Brest;48.4018349;-4.4663852 +0332846P;Lycée General Des Graves;238 Cours Du Gl De Gaulle;33173;Gradignan;44.774267;-0.618945 +0590024F;Lycée General Des Nerviens;Place Charles De Gaulle;59570;Bavay;50.2991014;3.7947585 +0370035M;Lycée General Descartes;10 Rue Des Minimes;37010;Tours;47.3914217;0.6900424 +0090018W;Lycée General Du Couserans;Esplanade Mendes France;09201;St Girons;42.9780405;1.1514912 +0260008T;Lycée General Du Diois;Rue Maurice Faure;26150;Die;44.757877;5.367689 +0690026D;Lycée General Du Parc;1 Boulevard Anatole France;69458;Lyon 06;45.7714439;4.8568367 +0490040C;Lycée General Duplessis Mornay;1 Rue Duruy;49408;Saumur;47.255425;-0.077033 +0940117S;Lycée General Edouard Branly;8 Rue Bauyn De Perreuse;94130;Nogent Sur Marne;48.8349733;2.4828212 +0150646W;Lycée General Emile Duclaux;16 Avenue Henri Mondor;15005;Aurillac;44.9269037;2.439407 +0260034W;Lycée General Emile Loubet;2 Rue Du Lycée;26021;Valence;0.0;0.0 +0141112H;Lycée General Et Tech Agricole Le Robillard;Le Robillard;14170;St Pierre Sur Dives;48.98834;-0.005216 +0810068J;Lycée General Et Techno. Privé D'amboise;13 Boulevard Carnot;81000;Albi;43.9227213;2.1450617 +0741469V;Lycée General Et Techno. Privé Demotz De La Salle;2 Rue Du College;74150;Rumilly;45.867922;5.9444106 +0121438Y;Lycée General Et Techno. Privé Francois D'estaing;22 Boulevard Denys Puech;12056;Rodez;44.3491284;2.5776399 +0820044C;Lycée General Et Techno. Privé Institut Familial;1 Allee De Mortarieu;82005;Montauban;44.0151243;1.356479 +0120051R;Lycée General Et Techno. Privé Jeanne D'arc;3 Place Du Mandarous;12104;Millau;44.0996885;3.0784006 +0810079W;Lycée General Et Techno. Privé Jeanne D'arc;23 Rue De La Vanne;81207;Mazamet;43.4847739;2.3737673 +0320051G;Lycée General Et Techno. Privé Oratoire Sainte-marie;50 Bis Rue Victor Hugo;32002;Auch;43.6505336;0.5776322 +0312355S;Lycée General Et Techno. Privé Ort Maurice Grynfogel;14 Rue Etienne Collongues;31770;Colomiers;43.601951;1.333155 +0311147D;Lycée General Et Techno. Privé Sainte-marie De Nevers;10 Rue Du Perigord;31070;Toulouse;43.6072684;1.4432532 +0311146C;Lycée General Et Techno. Privé Sainte-marie Des Champs;169 Avenue Jean Rieux;31506;Toulouse;43.5912753;1.4691653 +0460038S;Lycée General Et Techno. Privé Saint-etienne;49 Rue Des Soubirous;46000;Cahors;44.4504769;1.4412933 +0120059Z;Lycée General Et Techno. Privé Saint-gabriel;23 Rue Lamartine;12402;St Affrique;43.956919;2.890886 +0120061B;Lycée General Et Techno. Privé Saint-joseph;Avenue Etienne Soulie;12200;Villefranche De Rouergue;44.357108;2.0390698 +0120104Y;Lycée General Et Techno. Privé Saint-joseph;1 Rue Sarrus;12000;Rodez;44.3451427;2.5715885 +0311145B;Lycée General Et Techno. Privé Saint-joseph;85 Rue De Limayrac;31079;Toulouse;43.593452;1.4734636 +0311133N;Lycée General Et Technol Privé Le Caousou;42 Avenue Camille Pujol;31079;Toulouse;43.6010623;1.4610488 +0810103X;Lycée General Et Technol Privé Notre-dame;Avenue D'hauterive;81101;Castres;43.599188;2.244541 +0312744P;Lycée General Et Technologique;Chemin De La Cepette;31860;Pins Justaret;43.4754398;1.3911496 +9750001C;Lycée General Et Technologique;Rm Bonin;97500;St Pierre;0.0;0.0 +0090013R;Lycée General Et Technologique;Route De Limoux;09500;Mirepoix;43.0849235;1.8953166 +0120006S;Lycée General Et Technologique;Avenue Leo Lagrange;12300;Decazeville;44.5676272;2.2523499 +0670007U;Lycée General Et Technologique;4 Place Du Chateau;67330;Bouxwiller;48.8259746;7.4840954 +0910625K;Lycée General Et Technologique;2 Place De L Europe;91230;Montgeron;48.7050057;2.4537264 +9720694X;Lycée General Et Technologique Acajou 1;Quartier Acajou;97285;Le Lamentin;0.0;0.0 +0610001V;Lycée General Et Technologique Alain;27 Boulevard Mezeray;61014;Alencon;48.438637;0.0813898 +0782568T;Lycée General Et Technologique Alain;25 Route De La Cascade;78110;Le Vesinet;48.8913223;2.1225849 +0260015A;Lycée General Et Technologique Alain Borne;10 Place Du Theatre;26216;Montelimar;44.5872248;4.7021409 +0580753D;Lycée General Et Technologique Alain Colas;Rue D'estutt De Tracy;58000;Nevers;46.98518;3.1285249 +0560051B;Lycée General Et Technologique Alain Rene Lesage;20 Rue Winston Churchill;56017;Vannes;47.6477281;-2.7703646 +0060031B;Lycée General Et Technologique Albert Calmette;5 Avenue Marechal Foch;06050;Nice;43.7040719;7.2711532 +0300023M;Lycée General Et Technologique Albert Camus;51 Avenue Georges Pompidou;30911;Nimes;43.8363398;4.3463446 +0420013L;Lycée General Et Technologique Albert Camus;32 Bis Rue De La Loire;42704;Firminy;45.3883922;4.2830196 +0440288Z;Lycée General Et Technologique Albert Camus;11 Rue Etienne Coutan;44100;Nantes;47.2051598;-1.6079868 +0692517L;Lycée General Et Technologique Albert Camus;Avenue Des Nations;69140;Rillieux La Pape;45.8212079;4.9071706 +0920132U;Lycée General Et Technologique Albert Camus;131 Rue Pierre Joigneaux;92270;Bois Colombes;48.9106181;2.2595527 +0620166U;Lycée General Et Technologique Albert Chatelet;Rue Cassin;62165;St Pol Sur Ternoise;50.38609;2.3445988 +0911346U;Lycée General Et Technologique Albert Einstein;Av De La Liberte;91706;Ste Genevieve Des Bois;48.6400356;2.3486662 +0680031P;Lycée General Et Technologique Albert Schweitzer;8 Bld De La Marne;68068;Mulhouse;47.7459702;7.3241297 +0930830X;Lycée General Et Technologique Albert Schweitzer;11 Allee Valere Lefebvre;93340;Le Raincy;48.898921;2.5108023 +0420033H;Lycée General Et Technologique Albert Thomas;20 Rue Albert Thomas;42328;Roanne;46.0440474;4.0740983 +0442309W;Lycée General Et Technologique Alcide D'orbigny;Place De L Edit De Nantes;44830;Bouaye;47.2154657;-1.5678387 +0920801W;Lycée General Et Technologique Alexandre Dumas;112 Bd De La Republique;92210;St Cloud;48.8493852;2.2138325 +0620161N;Lycée General Et Technologique Alexandre Ribot;42 Rue Gambetta;62505;St Omer;50.7478277;2.2586747 +0061760F;Lycée General Et Technologique Alexis De Tocqueville;22 Che De L Orme;06131;Grasse;43.6545971;6.9414071 +0500017X;Lycée General Et Technologique Alexis De Tocqueville;34 Avenue Henri Poincare;50100;Cherbourg Octeville;49.6304753;-1.6077873 +0370016S;Lycée General Et Technologique Alfred De Vigny;Rue Paul Delvaux;37600;Loches;47.1286731;0.976984 +0590060V;Lycée General Et Technologique Alfred Kastler;123 Rue Paul Elie Casanova;59723;Denain;50.3293842;3.4072591 +0680015X;Lycée General Et Technologique Alfred Kastler;5 Rue Du Luspel;68502;Guebwiller;47.9070545;7.2048104 +0850027T;Lycée General Et Technologique Alfred Kastler;Boulevard Guitton;85020;La Roche Sur Yon;46.659238;-1.4360472 +0951399E;Lycée General Et Technologique Alfred Kastler;26 Avenue De La Palette;95011;Cergy;49.0319888;2.0841331 +0540030P;Lycée General Et Technologique Alfred Mezieres;3 Avenue Andre Malraux;54401;Longwy;49.5216573;5.7601943 +0860038Z;Lycée General Et Technologique Alienor D Aquitaine;41 Rue Pierre De Coubertin;86034;Poitiers;46.5883777;0.3652348 +0130164H;Lycée General Et Technologique Alphonse Daudet;1 Boulevard Jules Ferry;13150;Tarascon;43.8021567;4.6635779 +0300021K;Lycée General Et Technologique Alphonse Daudet;3 Boulevard Victor Hugo;30039;Nimes;43.8358168;4.3578013 +0630077S;Lycée General Et Technologique Ambroise Brugiere;44 Rue Des Planchettes;63039;Clermont Ferrand;45.7997921;3.1075266 +0530010Y;Lycée General Et Technologique Ambroise Pare;17 Rue Du Lycée;53013;Laval;0.0;0.0 +9740019C;Lycée General Et Technologique Ambroise Vollard;3 Avenue De Soweto;97448;St Pierre;-21.3417856;55.4892561 +0060020P;Lycée General Et Technologique Amiral De Grasse;20 Avenue Sainte Lorette;06130;Grasse;43.6523288;6.9209759 +9740471U;Lycée General Et Technologique Amiral Pierre Bouvet;76 Rue Joseph Hubert;97470;St Benoit;0.0;0.0 +0290008B;Lycée General Et Technologique Amiral Ronarc'h;3 Rue Mozart;29231;Brest;48.3959427;-4.5224841 +0690023A;Lycée General Et Technologique Ampere;31 Rue De La Bourse;69289;Lyon 02;45.7650005;4.8368975 +0330010G;Lycée General Et Technologique Anatole De Monzie;12 Cours Gambetta;33430;Bazas;44.4332382;-0.213939 +0620120U;Lycée General Et Technologique Anatole France;64 Boulevard De Paris;62190;Lillers;50.5559013;2.4778684 +0381603L;Lycée General Et Technologique Andre Argouges;61 Rue Leon Jouhaux;38029;Grenoble;45.1753906;5.7445124 +0931585T;Lycée General Et Technologique Andre Boulloche;18 Boulevard Gutenberg;93190;Livry Gargan;48.9125431;2.5194358 +0271580W;Lycée General Et Technologique Andre Malraux;59 Avenue Francois Mitterand;27607;Gaillon;49.160734;1.33341 +0620042J;Lycée General Et Technologique Andre Malraux;314 Rue Jules Massenet;62408;Bethune;50.5200303;2.6516077 +0721548S;Lycée General Et Technologique Andre Malraux;3 Rue De Beau Soleil;72700;Allonnes;47.9559769;0.1669132 +0670005S;Lycée General Et Technologique Andre Maurois;1 Rue Du Lycée;67242;Bischwiller;0.0;0.0 +0760029U;Lycée General Et Technologique Andre Maurois;1 Rue De Lorraine;76503;Elbeuf;49.2906662;1.0159498 +0860009T;Lycée General Et Technologique Andre Theuriet;;86400;Civray;46.148901;0.295258 +0352686E;Lycée General Et Technologique Anita Conti;Esplanade Du Lycée;35174;Bruz;0.0;0.0 +0690031J;Lycée General Et Technologique Antoine De Saint Exupery;82 Rue Henon;69316;Lyon 04;45.7797287;4.8219853 +0570023R;Lycée General Et Technologique Antoine De Saint-exupery;11 Avenue Saint Exupery;57290;Fameck;49.2470951;6.1087402 +9830507J;Lycée General Et Technologique Antoine Kela;Rte Provinciale 3;98822;Poindimie;0.0;0.0 +9740787M;Lycée General Et Technologique Antoine Roussin;25 Rue Leconte De Lisle;97450;St Louis;0.0;0.0 +0132733A;Lycée General Et Technologique Antonin Artaud;25 Ch N D De La Consolation;13013;Marseille 13;43.3402529;5.4230519 +0750680G;Lycée General Et Technologique Arago;4 Place De La Nation;75012;Paris 12;48.847931;2.3943586 +0011119L;Lycée General Et Technologique Arbez Carme;1 Rue Pierre Et Marie Curie;01100;Bellignat;46.2508923;5.6331189 +0840026X;Lycée General Et Technologique Arc (de L');346 Av Des Etudiants;84106;Orange;44.1415649;4.8019433 +0141274J;Lycée General Et Technologique Arcisse De Caumont;3 Rue Baron Gerard;14402;Bayeux;49.2843359;-0.7097585 +0050007F;Lycée General Et Technologique Aristide Briand;20 Avenue Commandant Dumont;05007;Gap;44.5662436;6.0836082 +0270016W;Lycée General Et Technologique Aristide Briand;2 Rue Pierre Semard;27031;Evreux;49.0168441;1.1602102 +0440069L;Lycée General Et Technologique Aristide Briand;10 Bd Pierre De Coubertin;44606;St Nazaire;47.2693774;-2.2334295 +0382780R;Lycée General Et Technologique Aristides Berges;10 Avenue Aime Bouchayer;38171;Seyssinet Pariset;45.1846564;5.6945758 +0250058C;Lycée General Et Technologique Armand Peugeot;30 Rue Des Carrieres;25702;Valentigney;47.4583749;6.8706804 +0240032E;Lycée General Et Technologique Arnaud Daniel;Rue Couleau;24600;Riberac;45.245019;0.336231 +0132495S;Lycée General Et Technologique Arthur Rimbaud;Quartier Des Salles;13808;Istres;43.513006;4.987968 +0595885B;Lycée General Et Technologique Arthur Rimbaud;1075 Rue Paul Foucaut;59450;Sin Le Noble;50.3496401;3.1081053 +0540044E;Lycée General Et Technologique Arthur Varoquaux;Rue Jean Moulin;54510;Tomblaine;48.6955865;6.2138163 +0590073J;Lycée General Et Technologique Auguste Angellier;Bd Republique -fr. Mitterrand;59942;Dunkerque;51.0465854;2.4108807 +0620109G;Lycée General Et Technologique Auguste Behal;6 Rue Paul Eluard;62300;Lens;50.4456289;2.8230467 +0610014J;Lycée General Et Technologique Auguste Chevalier;7 Place Du Champ De Foire;61700;Domfront;48.5921466;-0.6422106 +0492061Z;Lycée General Et Technologique Auguste Et Jean Renoir;15 Impasse Ampere;49035;Angers;47.4751203;-0.5682534 +0690035N;Lycée General Et Technologique Auguste Et Louis Lumiere;50 Boulevard Des Etats Unis;69372;Lyon 08;45.7361542;4.8620526 +0220018A;Lycée General Et Technologique Auguste Pavie;13 Rue Anatole Le Braz;22205;Guingamp;48.5585676;-3.146937 +0060009C;Lycée General Et Technologique Auguste Renoir;18 Avenue Marcel Pagnol;06802;Cagnes Sur Mer;43.718958;7.1656502 +0870017W;Lycée General Et Technologique Auguste Renoir;119 Rue Sainte-claire;87036;Limoges;45.8235722;1.2409963 +0920131T;Lycée General Et Technologique Auguste Renoir;137 Rue Du Menil;92600;Asnieres Sur Seine;48.9221719;2.2821664 +0140014P;Lycée General Et Technologique Augustin Fresnel;77 Rue Eustache Restout;14020;Caen;49.1638565;-0.3552215 +0270003G;Lycée General Et Technologique Augustin Fresnel;14 Rue Kleber Mercier;27301;Bernay;49.0846704;0.5939843 +9710003B;Lycée General Et Technologique Baimbridge;Boulevard Des Heros;97139;Les Abymes;16.2498118;-61.5230947 +0370036N;Lycée General Et Technologique Balzac;36 Rue D Entraigues;37013;Tours;47.3878353;0.6872794 +0261099D;Lycée General Et Technologique Barthelemy De Laffemas;Rue Barthelemy De Laffemas;26901;Valence;44.917059;4.915496 +0680007N;Lycée General Et Technologique Bartholdi;9 Rue Du Lycée;68025;Colmar;0.0;0.0 +0350022J;Lycée General Et Technologique Beaumont;10 Rue Du Lycée;35605;Redon;0.0;0.0 +0590093F;Lycée General Et Technologique Beaupre;Avenue De Beaupre;59481;Haubourdin;50.6018;2.9906864 +0420018S;Lycée General Et Technologique Beauregard;4 Avenue Paul Cezanne;42605;Montbrison;45.6137248;4.0570332 +0830050D;Lycée General Et Technologique Beaussier;Qua Beaussier Place Galilee;83512;La Seyne Sur Mer;43.1000479;5.8780319 +9741046U;Lycée General Et Technologique Bellepierre;Avenue Gaston Monnerville;97475;St Denis;48.830502;2.597217 +0170058W;Lycée General Et Technologique Bellevue;1 Chemin Des Cotieres;17100;Saintes;45.7370327;-0.6435025 +9720003W;Lycée General Et Technologique Bellevue;Rue Marie Therese Gertrude;97262;Fort De France;14.6040646;-61.084565 +0561534N;Lycée General Et Technologique Benjamin Franklin;1 Rue De La Foret;56408;Auray;47.6712271;-2.9796708 +0170060Y;Lycée General Et Technologique Bernard Palissy;1 Rue De Gascogne;17107;Saintes;45.7360174;-0.6152519 +0450029M;Lycée General Et Technologique Bernard Palissy;9 Rue Du 32e R I;45502;Gien;47.699878;2.6321739 +0470001W;Lycée General Et Technologique Bernard Palissy;164 Boulevard De La Liberte;47000;Agen;44.1992878;0.6215667 +0190038N;Lycée General Et Technologique Bernart De Ventadour;Boulevard De La Jaloustre;19200;Ussel;45.541611;2.315461 +0350053T;Lycée General Et Technologique Bertrand D'argentre;15 Rue Du College;35506;Vitre;48.1200837;-1.211593 +0490782J;Lycée General Et Technologique Blaise Pascal;2 Rue Du Lycée;49502;Segre;0.0;0.0 +0570030Y;Lycée General Et Technologique Blaise Pascal;5 Rue Paul Ney;57608;Forbach;49.1806835;6.8958864 +0622803K;Lycée General Et Technologique Blaise Pascal;Rue Roger Salengro;62967;Longuenesse;50.7396525;2.2532221 +0693518Z;Lycée General Et Technologique Blaise Pascal;2 Avenue Bergeron;69751;Charbonnieres Les Bains;45.7751178;4.7452169 +0910626L;Lycée General Et Technologique Blaise Pascal;18 A 20 Rue Alexandre Fleming;91406;Orsay;48.6959013;2.1868463 +0831243A;Lycée General Et Technologique Bonaparte;Avenue W. Churchill;83097;Toulon;43.1249494;5.9263291 +0810959C;Lycée General Et Technologique Borde Basse;Cite Scolaire;81108;Castres;43.606214;2.241295 +0320009L;Lycée General Et Technologique Bossuet;42 Rue Jules Ferry;32100;Condom;43.9617419;0.3733974 +0820021C;Lycée General Et Technologique Bourdelle;3 Boulevard Edouard Herriot;82003;Montauban;44.0128398;1.3704149 +0350028R;Lycée General Et Technologique Brequigny;7 Avenue Georges Graff;35205;Rennes;48.0836213;-1.6920985 +0060013G;Lycée General Et Technologique Bristol;10 Avenue St Nicolas;06405;Cannes;43.5554966;7.0188911 +0630052P;Lycée General Et Technologique C. Et P. Virlogeux;1 Rue Du General Chapsal;63201;Riom;45.8868933;3.1143602 +0410959V;Lycée General Et Technologique Camille Claudel;10 Rue Albert Camus;41018;Blois;47.5764443;1.3027224 +0442207K;Lycée General Et Technologique Camille Claudel;14 Boulevard Jules Verne;44130;Blain;47.4812962;-1.7638862 +0590083V;Lycée General Et Technologique Camille Claudel;Cite Scolaire Rue Paul Lafargue;59613;Fourmies;50.0115008;4.0428865 +0711137A;Lycée General Et Technologique Camille Claudel;Route De Roanne;71160;Digoin;46.4693907;4.002237 +0772243V;Lycée General Et Technologique Camille Claudel;Place Anyama;77347;Pontault Combault;48.801255;2.607598 +0911938M;Lycée General Et Technologique Camille Claudel;17 Rue Robespierre;91120;Palaiseau;48.7189784;2.2340194 +0383069E;Lycée General Et Technologique Camille Corot;454 Rue Paul Claudel;38510;Morestel;45.674976;5.474023 +0330023W;Lycée General Et Technologique Camille Jullian;29 Rue De La Croix Blanche;33074;Bordeaux;44.8446226;-0.5901585 +0951922Y;Lycée General Et Technologique Camille Saint-saens;18 Au 22 Rue Guynemer;95170;Deuil La Barre;48.9734465;2.3360722 +0680008P;Lycée General Et Technologique Camille See;42 Avenue De L'europe;68025;Colmar;48.0804554;7.325755 +0441993C;Lycée General Et Technologique Carcouet;115 Bd Du Massacre;44184;Nantes;47.2316043;-1.5925015 +0210015C;Lycée General Et Technologique Carnot;16 Boulevard Thiers;21000;Dijon;47.3236983;5.0492348 +0420034J;Lycée General Et Technologique Carnot;35 Avenue Carnot;42300;Roanne;46.0419603;4.0699904 +0600013N;Lycée General Et Technologique Cassini;11 Rue Henri Breuil;60607;Clermont;49.3845904;2.4076904 +0891200W;Lycée General Et Technologique Catherine Et Raymond Janot;1 Place Lech Walesa;89094;Sens;48.20065;3.28268 +0590121L;Lycée General Et Technologique Cesar Baggio;Boulevard D'alsace;59000;Lille;50.6177376;3.0676499 +0220058U;Lycée General Et Technologique Chaptal;6 Allee Chaptal;22015;St Brieuc;48.5125105;-2.7377027 +0750663N;Lycée General Et Technologique Chaptal;45 Boulevard Des Batignolles;75008;Paris 08;48.8817015;2.3196312 +0160004T;Lycée General Et Technologique Charles A Coulomb;Avenue Joachim Du Bellay;16016;Angouleme;45.6303964;0.1570913 +0561627P;Lycée General Et Technologique Charles De Gaulle;23 Avenue Paul Cezanne;56017;Vannes;47.6764559;-2.772802 +0601863Z;Lycée General Et Technologique Charles De Gaulle;Rue Jacques Daguerre;60321;Compiegne;49.387482;2.7840919 +0781898P;Lycée General Et Technologique Charles De Gaulle;10 Rue Gustave Eiffel;78306;Poissy;48.9483064;2.0613496 +0932031C;Lycée General Et Technologique Charles De Gaulle;102 Rue Lavoisier;93110;Rosny Sous Bois;48.8615917;2.4910337 +0400018C;Lycée General Et Technologique Charles Despiau;637 Route Du Houga;40010;Mont De Marsan;43.8821356;-0.4870724 +0590049H;Lycée General Et Technologique Charles Deulin;89 Rue De La Chaussiette;59163;Conde Sur L Escaut;50.4598956;3.5860573 +0500026G;Lycée General Et Technologique Charles Francois Lebrun;2 Place Georges Davy;50207;Coutances;49.047808;-1.4452609 +0300046M;Lycée General Et Technologique Charles Gide;Place Adolphe Bosc;30700;Uzes;44.016096;4.416498 +0771763Y;Lycée General Et Technologique Charles Le Chauve;4 Rue J. Bodin De Boismortier;77680;Roissy En Brie;48.8016537;2.6384353 +0451526P;Lycée General Et Technologique Charles Peguy;1 Cours Victor Hugo;45074;Orleans;47.8891246;1.897411 +0740017S;Lycée General Et Technologique Charles Poncet;1 Avenue Charles Poncet;74302;Cluses;46.0625378;6.579908 +0692800U;Lycée General Et Technologique Charlie Chaplin;13 Rue Francisco Ferrer;69152;Decines Charpieu;45.7737096;4.9761696 +0490003M;Lycée General Et Technologique Chevrollier;2 Rue Adrien Recouvreur;49035;Angers;47.4543325;-0.5571808 +0370037P;Lycée General Et Technologique Choiseul;78 Rue Des Douets;37095;Tours;47.435967;0.6806159 +0100022V;Lycée General Et Technologique Chrestien De Troyes;3 Rue De Quebec;10009;Troyes;48.2688104;4.0777951 +0790036K;Lycée General Et Technologique Cite Scolaire Jean Moulin;Rue Albert Buisson;79101;Thouars;46.9937436;-0.2131568 +0590149S;Lycée General Et Technologique Cite Scolaire Pierre Forest;Boulevard Charles De Gaulle;59605;Maubeuge;50.2784762;3.9816647 +0860005N;Lycée General Et Technologique Cite Technique Edouard Branly;2 Rue Edouard Branly;86106;Chatellerault;46.8021551;0.5431246 +0750698B;Lycée General Et Technologique Claude Bernard;1 Avenue Du Parc Des Princes;75016;Paris 16;48.842485;2.254794 +0410017W;Lycée General Et Technologique Claude De France;9 11 Avenue De Paris;41206;Romorantin Lanthenay;47.3693756;1.738879 +0420040R;Lycée General Et Technologique Claude Lebois;8 Boulevard Alamagny;42403;St Chamond;45.4685576;4.5111391 +0760174B;Lycée General Et Technologique Claude Monet;267 Rue Felix Faure;76085;Le Havre;49.5009436;0.110217 +0251711Z;Lycée General Et Technologique Claude Nicolas Ledoux;14 Rue Alain Savary;25006;Besancon;47.2507592;5.9961549 +0820883P;Lycée General Et Technologique Claude Nougaro;;82300;Monteils;44.1741667;1.5661111 +0440021J;Lycée General Et Technologique Clemenceau;1 Rue Georges Clemenceau;44042;Nantes;47.2196647;-1.5456315 +0590214M;Lycée General Et Technologique Colbert;2 Parvis Jean Baptiste Colbert;59208;Tourcoing;50.724993;3.16207 +0690042W;Lycée General Et Technologique Colbert;20 Rue Louis Jouvet;69372;Lyon 08;45.7460272;4.8608427 +0720048L;Lycée General Et Technologique Colbert De Torcy;Rue St Denis;72305;Sable Sur Sarthe;47.838785;-0.342691 +0601865B;Lycée General Et Technologique Condorcet;Chemin Du Tour De Ville;60111;Meru;49.232327;2.140134 +0620108F;Lycée General Et Technologique Condorcet;25 Rue Etienne Dolet;62303;Lens;50.4322132;2.836371 +0693478F;Lycée General Et Technologique Condorcet;29 Rue Edmond Rostand;69802;St Priest;45.6943551;4.9411916 +0900002N;Lycée General Et Technologique Condorcet;13 Avenue Roosevelt;90016;Belfort;47.6333787;6.8506415 +0930122C;Lycée General Et Technologique Condorcet;31 Rue Desire Chevalier;93105;Montreuil;48.857903;2.446193 +0170042D;Lycée General Et Technologique Cordouan;28 Rue Henri Dunant;17200;Royan;45.6214498;-1.0029599 +0940121W;Lycée General Et Technologique D'arsonval;65 Rue Du Pont De Creteil;94107;St Maur Des Fosses;48.8032591;2.4718505 +0620143U;Lycée General Et Technologique D'artois;Rue Jules Mousseron;62290;Noeux Les Mines;50.4729178;2.6598973 +0310032S;Lycée General Et Technologique De Bagatelle;114 Avenue Francois Mitterrand;31806;St Gaudens;43.1122133;0.7456576 +0290098Z;Lycée General Et Technologique De Cornouaille;8 Avenue Des Oiseaux;29191;Quimper;47.9922221;-4.1232942 +0290076A;Lycée General Et Technologique De Kerneuzec;Kerneuzec;29391;Quimperle;47.88185;-3.552623 +0801900F;Lycée General Et Technologique De L Authie;20 Rue De Routequeue;80600;Doullens;50.149672;2.3555172 +0573281G;Lycée General Et Technologique De La Communication;3 Boulevard Arago;57070;Metz;49.1006107;6.2227012 +0762953X;Lycée General Et Technologique De La Cote D'albatre;24 Bis Rue Du Noroit;76460;St Valery En Caux;49.8670293;0.6986804 +0011326L;Lycée General Et Technologique De La Cotiere;270 Chemin Du Grand Casset;01120;La Boisse;45.8460955;5.0355377 +0011194T;Lycée General Et Technologique De La Plaine De L'ain;Rue Leon Blum;01500;Amberieu En Bugey;45.9629402;5.3435213 +0741532N;Lycée General Et Technologique De L'albanais;Rue Du Lycée;74152;Rumilly;0.0;0.0 +0595809U;Lycée General Et Technologique De L'escaut;1 Avenue De Saint Amand;59305;Valenciennes;50.3654932;3.5145784 +0590072H;Lycée General Et Technologique De L'europe;809 Rue Du Banc Vert;59640;Dunkerque;51.0203846;2.354849 +0781949V;Lycée General Et Technologique De Villaroy;2 Rue E Viollet Le Duc;78041;Guyancourt;48.7647055;2.0683153 +9741230U;Lycée General Et Technologique De Vincendo;10 Route De La Marine;97480;St Joseph;-21.3766787;55.6686797 +9870026P;Lycée General Et Technologique De Wallis;Mata Utu;98600;Uvea;-13.2825091;-176.1764475 +0110023R;Lycée General Et Technologique Denis Diderot;2 Rue Jean Moulin;11100;Narbonne;43.1899207;3.01902 +0310044E;Lycée General Et Technologique Deodat De Severac;26 Boulevard Deodat De Severac;31076;Toulouse;43.5869625;1.4260334 +0610018N;Lycée General Et Technologique Des Andaines;3 Place Du General De Gaulle;61600;La Ferte Mace;48.5869648;-0.3589215 +0312267W;Lycée General Et Technologique Des Arenes;Place Emile Male;31024;Toulouse;43.5912216;1.4186141 +9710882G;Lycée General Et Technologique Des Droits De L'homme;Pointe A Bacchus;97170;Petit Bourg;16.2002975;-61.5883791 +0590101P;Lycée General Et Technologique Des Flandres;2 Avenue Des Flandres;59522;Hazebrouck;50.7282931;2.5320417 +0781512V;Lycée General Et Technologique Descartes;6 Boulevard Descartes;78180;Montigny Le Bretonneux;48.766296;2.0387934 +0920130S;Lycée General Et Technologique Descartes;1 Avenue Lavoisier;92761;Antony;48.7484691;2.3136729 +0300026R;Lycée General Et Technologique Dhuoda;17 Rue Dhuoda;30913;Nimes;43.8268494;4.356595 +0520021R;Lycée General Et Technologique Diderot;21 Av Du Gal De Gaulle;52206;Langres;47.847086;5.3305042 +0620070P;Lycée General Et Technologique Diderot;Avenue Montaigne;62220;Carvin;50.4909624;2.9457485 +0670071N;Lycée General Et Technologique Docteur Koeberle;Bld Charlemagne;67604;Selestat;48.2633909;7.4554325 +0110022P;Lycée General Et Technologique Docteur Lacroix;Rue Gay Lussac;11100;Narbonne;43.1783759;3.0009809 +0050006E;Lycée General Et Technologique Dominique Villars;Place De Verdun;05010;Gap;-27.4251257;152.9935547 +0530011Z;Lycée General Et Technologique Douanier Rousseau;7 Rue Des Archives;53013;Laval;48.0717168;-0.7606616 +0260019E;Lycée General Et Technologique Dr. Gustave Jaume;Avenue Henri Becquerel;26702;Pierrelatte;44.380434;4.698 +0861228T;Lycée General Et Technologique Du Bois D'amour;9 Rue De La Garenne;86034;Poitiers;46.5506752;0.3075189 +0010010F;Lycée General Et Technologique Du Bugey;113 Rue Du 5eme Rtm;01306;Belley;45.7606158;5.68952 +0090015T;Lycée General Et Technologique Du Castella;Place Du Mercadal;09104;Pamiers;43.114347;1.6083829 +0831407D;Lycée General Et Technologique Du Coudon;Avenue Toulouse-lautrec;83957;La Garde;43.1328032;6.0180676 +9830557N;Lycée General Et Technologique Du Grand Noumea;Avenue Victor Hugo;98830;Dumbea;-22.2131693;166.467175 +0731392S;Lycée General Et Technologique Du Granier;185 Avenue Joseph Fontanet;73492;La Ravoire;45.5509773;5.9675821 +0292047T;Lycée General Et Technologique Du Leon;25 Bd De La Republique;29406;Landivisiau;48.5083658;-4.0595902 +0595616J;Lycée General Et Technologique Du Noordover;26 Avenue De Suwalki;59792;Grande Synthe;51.0111625;2.3014666 +0011276G;Lycée General Et Technologique Du Val De Saone;220 Chemin D'arras;01606;Trevoux;45.9455462;4.7619654 +0801864S;Lycée General Et Technologique Du Vimeu;3 Rue Denis Papin;80534;Friville Escarbotin;50.0832324;1.5461642 +0450062Y;Lycée General Et Technologique Duhamel Du Monceau;16 Avenue De France;45300;Pithiviers;48.1809395;2.2549277 +0830053G;Lycée General Et Technologique Dumont D Urville;212 Avenue Amiral Jaujard;83056;Toulon;43.1179515;5.9386824 +0590112B;Lycée General Et Technologique Dupleix;10 Boulevard Des Resistants;59550;Landrecies;50.1229644;3.6895214 +0560025Y;Lycée General Et Technologique Dupuy De Lome;Rue Le Coutaller;56100;Lorient;47.7478486;-3.3697288 +0450042B;Lycée General Et Technologique Durzy;23 Rue Leonard De Vinci;45702;Villemandeur;48.0003639;2.7159501 +0380029A;Lycée General Et Technologique Eaux Claires;7 Rue De Dunkerque;38030;Grenoble;45.1790269;5.7127172 +0010014K;Lycée General Et Technologique Edgar Quinet;5 Avenue Jean Marie Verne;01011;Bourg En Bresse;46.1953623;5.225314 +0520844K;Lycée General Et Technologique Edme Bouchardon;16 Rue Youri Gagarine;52903;Chaumont;48.104773;5.1478457 +0590065A;Lycée General Et Technologique Edmond Labbe;817 Rue Charles Bourseul;59508;Douai;50.3764002;3.0685187 +0911961M;Lycée General Et Technologique Edmond Michelet;2 Bd Abel Cornaton;91290;Arpajon;48.5870056;2.2468739 +0190032G;Lycée General Et Technologique Edmond Perrier;6 Avenue Henri De Bournazel;19012;Tulle;45.2680719;1.768389 +0311334G;Lycée General Et Technologique Edmond Rostand;2 Boulevard Charles De Gaulle;31110;Bagneres De Luchon;42.7929599;0.5975252 +0620052V;Lycée General Et Technologique Edouard Branly;2 Rue De La Porte Gayole;62321;Boulogne Sur Mer;50.7198852;1.6152414 +0101016A;Lycée General Et Technologique Edouard Herriot;Rue De La Maladiere;10302;Ste Savine;48.2935455;4.0271421 +0690027E;Lycée General Et Technologique Edouard Herriot;6 Place Edgar Quinet;69455;Lyon 06;45.7657284;4.8446272 +0180035R;Lycée General Et Technologique Edouard Vaillant;41 Bis Rue Charles Hurvoy;18100;Vierzon;47.2279543;2.0673471 +0332744D;Lycée General Et Technologique Elie Faure;Rue Jules Ferry;33305;Lormont;44.8701779;-0.519413 +0160010Z;Lycée General Et Technologique Elie Vinet;7 Avenue Pierre Mendes France;16300;Barbezieux St Hilaire;45.4741718;-0.1501908 +0750692V;Lycée General Et Technologique Emile Dubois;14 Rue Emile Dubois;75014;Paris 14;48.8317647;2.3369673 +0130001F;Lycée General Et Technologique Emile Zola;Av Arc De Meyran;13181;Aix En Provence;43.5118833;5.4584873 +0280015P;Lycée General Et Technologique Emile Zola;26 Rue De Civry;28200;Chateaudun;48.0722541;1.3347452 +0350024L;Lycée General Et Technologique Emile Zola;2 Avenue Janvier;35044;Rennes;48.1073742;-1.6736042 +0772294A;Lycée General Et Technologique Emily Bronte;10 Bis Mail Le Corbusier;77185;Lognes;48.8392621;2.6392575 +0380032D;Lycée General Et Technologique Emmanuel Mounier;6 Avenue Marcellin Berthelot;38029;Grenoble;45.1788938;5.7314999 +0492089E;Lycée General Et Technologique Emmanuel Mounier;1 Boulevard Robert Schuman;49017;Angers;47.4895805;-0.5269842 +0920135X;Lycée General Et Technologique Emmanuel Mounier;35 Rue Des Pres Hauts;92290;Chatenay Malabry;48.768766;2.2759475 +0130160D;Lycée General Et Technologique Emperi (l');21 Montee Du Puech;13657;Salon De Provence;43.6383013;5.0983648 +0450040Z;Lycée General Et Technologique En Foret;Route De Paucourt;45207;Montargis;48.0084469;2.750206 +0570081D;Lycée General Et Technologique Erckmann Chatrian;13 Rue De L'arsenal;57372;Phalsbourg;48.7667374;7.260468 +0540034U;Lycée General Et Technologique Ernest Bichat;4 Avenue Docteur Paul Kahn;54300;Luneville;48.5974249;6.5061312 +0590192N;Lycée General Et Technologique Ernest Couteaux;37 Avenue Du College;59734;St Amand Les Eaux;50.4477034;3.4431281 +0790029C;Lycée General Et Technologique Ernest Perochon;40 Rue Taillepied;79204;Parthenay;46.6423084;-0.2526283 +0220057T;Lycée General Et Technologique Ernest Renan;2 Et 4 Bd Herault;22021;St Brieuc;48.517094;-2.75443 +0720021G;Lycée General Et Technologique Estournelles De Constant;Domaine Bouchevereau;72205;La Fleche;47.69589;-0.074646 +0420046X;Lycée General Et Technologique Etienne Mimard;32 Rue Etienne Mimard;42021;St Etienne;45.438102;4.3960568 +0510007F;Lycée General Et Technologique Etienne Oehmichen;8 Avenue Du Mont Hery;51037;Chalons En Champagne;48.9761202;4.3588161 +0940116R;Lycée General Et Technologique Eugene Delacroix;5 Rue Pierre Curie;94704;Maisons Alfort;48.8107477;2.426031 +0220060W;Lycée General Et Technologique Eugene Freyssinet;32 Rue Mansart;22023;St Brieuc;48.5195608;-2.777683 +0230002C;Lycée General Et Technologique Eugene Jamot;1 Rue Williams Dumazet;23200;Aubusson;45.9530125;2.1677732 +0620140R;Lycée General Et Technologique Eugene Woillez;1 Rue Porte Becquerelle;62170;Montreuil;50.4644173;1.7663501 +0595867G;Lycée General Et Technologique Europeen Montebello;196 Boulevard Montebello;59006;Lille;50.6206484;3.0469456 +9740597F;Lycée General Et Technologique Evariste De Parny;Plateau Caillou 85 Rue A.vinson;97867;St Paul;0.0;0.0 +0782924E;Lycée General Et Technologique Evariste Galois;87 Avenue De Tobrouk;78500;Sartrouville;48.9285342;2.1613742 +0100015M;Lycée General Et Technologique F. Et I. Joliot Curie;1 Rue Guy Moquet;10100;Romilly Sur Seine;48.5155735;3.716231 +0570054Z;Lycée General Et Technologique Fabert;12 Rue Saint Vincent;57045;Metz;49.1226525;6.1717052 +0590119J;Lycée General Et Technologique Faidherbe;9 Rue Armand Carrel;59034;Lille;50.6165715;3.0739224 +9710774P;Lycée General Et Technologique Faustin Fleret;Section Esperance;97111;Morne A L Eau;0.0;0.0 +9730001N;Lycée General Et Technologique Felix Eboue;Rocade Sud;97306;Cayenne;4.9227;-52.3269 +0040010P;Lycée General Et Technologique Felix Esclangon;Boulevard Martin Bret;04103;Manosque;43.836809;5.781223 +0600001A;Lycée General Et Technologique Felix Faure;31 Boulevard De L Assaut;60000;Beauvais;49.4347804;2.0895545 +0220023F;Lycée General Et Technologique Felix Le Dantec;Rue Des Cordiers;22303;Lannion;48.7310544;-3.446948 +0572022N;Lycée General Et Technologique Felix Mayer;2 Square Georges Bastide;57150;Creutzwald;49.204987;6.6962469 +0590035T;Lycée General Et Technologique Fenelon;Place Fenelon;59407;Cambrai;50.1764669;3.2299983 +0331760J;Lycée General Et Technologique Fernand Daguin;15 Rue Gustave Flaubert;33698;Merignac;44.8366276;-0.6500899 +0620093P;Lycée General Et Technologique Fernand Darchicourt;211 Rue Rene Cassin;62251;Henin Beaumont;50.410645;2.960295 +0490054T;Lycée General Et Technologique Fernand Renaudeau;Rue La Tuilerie;49321;Cholet;47.0709727;-0.8903426 +6200001G;Lycée General Et Technologique Fesch;5 Cours Grandval;20176;Ajaccio;41.9176744;8.7311055 +0931565W;Lycée General Et Technologique Flora Tristan;27 Rue Des Hauts Roseaux;93166;Noisy Le Grand;48.8478226;2.5765564 +0951147F;Lycée General Et Technologique Fragonard;Allee Verte;95290;L Isle Adam;49.1193445;2.2241912 +0510062R;Lycée General Et Technologique Francois 1er;Fg De Vitry Le Brule;51308;Vitry Le Francois;48.729036;4.59224 +0660010C;Lycée General Et Technologique Francois Arago;22 Avenue Paul Doumer;66028;Perpignan;42.698684;2.8958719 +0770926N;Lycée General Et Technologique Francois Couperin;Route Hurtault;77305;Fontainebleau;48.3979083;2.6875719 +0330027A;Lycée General Et Technologique Francois Mauriac;1 Rue Henri Dunant;33015;Bordeaux;44.8394554;-0.5560782 +0421976V;Lycée General Et Technologique Francois Mauriac-forez;32 Rue Des Bullieux;42166;Andrezieux Boutheon;45.5374319;4.2959439 +0410002E;Lycée General Et Technologique Francois Philibert Dessaigne;;41007;Blois;47.588402;1.335616 +0220056S;Lycée General Et Technologique Francois Rabelais;8 Rue Rabelais;22022;St Brieuc;48.504309;-2.7467497 +0601823F;Lycée General Et Technologique Francois Truffaut;4 Rue De Pontoise;60007;Beauvais;49.4236814;2.0846913 +0851346B;Lycée General Et Technologique Francois Truffaut;8 Rue De La Cailletiere;85300;Challans;46.8538105;-1.8685351 +0451484U;Lycée General Et Technologique Francois Villon;Avenue Pierre De Felice;45190;Beaugency;47.7856371;1.6185055 +0750690T;Lycée General Et Technologique Francois Villon;10 16 Avenue Marc Sangnier;75014;Paris 14;48.826174;2.306449 +0780422K;Lycée General Et Technologique Francois Villon;Rue Salvador Allende;78133;Les Mureaux;48.97137;1.922145 +0911021R;Lycée General Et Technologique Francois-joseph Talma;1 Rue Des Cerfs;91805;Brunoy;48.6928052;2.5025791 +0510034K;Lycée General Et Technologique Franklin Roosevelt;10 Rue Roosevelt;51096;Reims;49.2620465;4.0259878 +9720350Y;Lycée General Et Technologique Frantz Fanon;Cite Scolaire Frantz Fanon;97220;La Trinite;0.0;0.0 +0540040A;Lycée General Et Technologique Frederic Chopin;39 Rue Du Sergent Blandan;54042;Nancy;48.6804667;6.1664682 +0690103M;Lycée General Et Technologique Frederic Fays;46 Rue Frederic Fays;69615;Villeurbanne;45.7625759;4.9047165 +0131549N;Lycée General Et Technologique Frederic Joliot-curie;4 Avenue Des Goums;13400;Aubagne;43.2916597;5.5612548 +0680051L;Lycée General Et Technologique Frederic Kirschleger;8 Rue Du Docteur Heid;68140;Munster;48.0385537;7.1326565 +0281047L;Lycée General Et Technologique Fulbert;Rue Saint Cheron;28011;Chartres;48.4464583;1.5061027 +0220027K;Lycée General Et Technologique Fulgence Bienvenue;Rue Eon De L'etoile;22606;Loudeac;48.1851796;-2.7600028 +0910687C;Lycée General Et Technologique Fustel De Coulanges;11 Rue Des Migneaux;91300;Massy;48.7330082;2.2656033 +0090002D;Lycée General Et Technologique Gabriel Faure;5 Rue Lt Paul Delpech;09008;Foix;42.964127;1.605232 +0740005D;Lycée General Et Technologique Gabriel Faure;2 Avenue Du Rhone;74008;Annecy;45.8989205;6.1181581 +0720033V;Lycée General Et Technologique Gabriel Touchard;8 Place Washington;72002;Le Mans;47.9956706;0.2030578 +0710071S;Lycée General Et Technologique Gabriel Voisin;Rue Saint Jean;71700;Tournus;46.574065;4.907238 +0380083J;Lycée General Et Technologique Galilee;124 Avenue General Leclerc;38209;Vienne;45.5052095;4.8559693 +0772127U;Lycée General Et Technologique Galilee;Avenue Andre Malraux;77385;Combs La Ville;48.6574407;2.5584154 +0951637N;Lycée General Et Technologique Galilee;11 Av Du Jour;95801;Cergy;49.0459761;2.0299018 +0620007W;Lycée General Et Technologique Gambetta;25 Boulevard Carnot;62022;Arras;50.2874799;2.7750733 +0440030U;Lycée General Et Technologique Gaspard Monge;2 Rue De La Fantaisie;44322;Nantes;47.2535027;-1.5719339 +0100003Z;Lycée General Et Technologique Gaston Bachelard;5 Bis Rue Gaston Bachelard;10200;Bar Sur Aube;48.2298972;4.7045231 +0770922J;Lycée General Et Technologique Gaston Bachelard;32 Avenue De L'europe;77505;Chelles;48.9146526;2.5976487 +0590258K;Lycée General Et Technologique Gaston Berger;Avenue Gaston Berger Prolongee;59016;Lille;50.6143623;3.0807063 +0640052J;Lycée General Et Technologique Gaston Febus;20 Av Georges Moutet;64301;Orthez;43.4914882;-0.7661496 +9730108E;Lycée General Et Technologique Gaston Monnerville;Rue Du Lycée;97387;Kourou;0.0;0.0 +0020014E;Lycée General Et Technologique Gay Lussac;Boulevard Gambetta;02302;Chauny;49.613832;3.2214 +0670057Y;Lycée General Et Technologique General Leclerc;8 Rue Poincare;67703;Saverne;48.7409019;7.360907 +0771663P;Lycée General Et Technologique George Sand;Rue De La Mare Au Diable;77350;Le Mee Sur Seine;48.549902;2.636207 +0880152M;Lycée General Et Technologique Georges Baumont;Rue De L'orme;88102;St Die Des Vosges;48.29796;6.946644 +0300950V;Lycée General Et Technologique Georges Brassens;Avenue Vigan Braquet;30205;Bagnols Sur Ceze;44.1607643;4.6116424 +0594413B;Lycée General Et Technologique Georges Bustin;13 Rue Du 8 Mai 1945;59690;Vieux Conde;50.4642251;3.569136 +0190010H;Lycée General Et Technologique Georges Cabanis;6 Bd Henri De Jouvenel;19311;Brive La Gaillarde;45.1600815;1.5232644 +0340039H;Lycée General Et Technologique Georges Clemenceau;31 Avenue Georges Clemenceau;34060;Montpellier;43.6031139;3.8721038 +0850006V;Lycée General Et Technologique Georges Clemenceau;Place De La Republique;85111;Chantonnay;46.6849967;-1.0493349 +0930127H;Lycée General Et Technologique Georges Clemenceau;130 Rue De Neuilly;93250;Villemomble;48.8772594;2.5239123 +0250030X;Lycée General Et Technologique Georges Cuvier;1 Place Jean Monnet;25207;Montbeliard;47.510356;6.798466 +0540041B;Lycée General Et Technologique Georges De La Tour;5 Rue De La Croix St Claude;54052;Nancy;48.6928494;6.1381574 +0572757M;Lycée General Et Technologique Georges De La Tour;Place Maud'huy;57045;Metz;49.2036097;6.1574195 +0133525L;Lycée General Et Technologique Georges Duby;200 Rue Georges Duby;13080;Aix En Provence;43.4805284;5.4133099 +0270044B;Lycée General Et Technologique Georges Dumezil;Route D'ivry;27207;Vernon;49.0818358;1.4769566 +0921594H;Lycée General Et Technologique Georges Pompidou;1 Avenue Georges Pompidou;92390;Villeneuve La Garenne;48.9419734;2.3246588 +0020059D;Lycée General Et Technologique Gerard De Nerval;14 Rue Paul Deviolaine;02209;Soissons;49.3860844;3.3238462 +0950647M;Lycée General Et Technologique Gerard De Nerval;Place De L Europe;95270;Luzarches;49.113738;2.441136 +9710002A;Lycée General Et Technologique Gerville Reache;37 Rue Amedee Fengarol;97100;Basse Terre;16.2431386;-61.5372836 +7200009X;Lycée General Et Technologique Giocante De Casabianca;Av.jean Zuccarelli;20293;Bastia;42.7033101;9.4445024 +0510068X;Lycée General Et Technologique Godart Roger;8 Rue Godart Roger;51331;Epernay;49.0398693;3.9654465 +0330003Z;Lycée General Et Technologique Grand Air;Avenue Dr Lorentz Monod;33311;Arcachon;44.6531506;-1.1636262 +0440012Z;Lycée General Et Technologique Grand Air;77 Avenue Du Bois D Amour;44503;La Baule Escoublac;47.2848421;-2.3741177 +0370038R;Lycée General Et Technologique Grandmont;Avenue De Sevigne;37204;Tours;47.3603558;0.7008859 +0160002R;Lycée General Et Technologique Guez De Balzac;Place Beaulieu;16016;Angouleme;45.65111;0.146709 +0061763J;Lycée General Et Technologique Guillaume Apollinaire;29 Bd Jean Baptiste Verany;06300;Nice;43.7155171;7.2826203 +0940123Y;Lycée General Et Technologique Guillaume Apollinaire;42 Rue Du Pave De Grignon;94320;Thiais;48.7571462;2.4006742 +0760072R;Lycée General Et Technologique Guillaume Le Conquerant;Allee De La Cote Blanche;76170;Lillebonne;49.521553;0.545717 +0900003P;Lycée General Et Technologique Gustave Courbet;Av Gambiez;90020;Belfort;47.652328;6.856124 +0931272C;Lycée General Et Technologique Gustave Eiffel;16 Chemin De La Renardiere;93220;Gagny;48.8887212;2.5413326 +0760096S;Lycée General Et Technologique Gustave Flaubert;1 Rue Albert Dupuis;76044;Rouen;49.4508628;1.1226207 +0950644J;Lycée General Et Technologique Gustave Monod;71 Avenue De Ceinture;95880;Enghien Les Bains;48.97192;2.2940159 +0860021F;Lycée General Et Technologique Guy Chauvet;Rue De L Eperon;86206;Loudun;47.0060213;0.0850569 +0760035A;Lycée General Et Technologique Guy De Maupassant;1575 Boulevard Nelson Mandela;76401;Fecamp;49.7442498;0.3892768 +0920137Z;Lycée General Et Technologique Guy De Maupassant;52 Rue Robert Schuman;92701;Colombes;48.9273923;2.240501 +0440005S;Lycée General Et Technologique Guy Moquet;1 Rue De L Europe;44146;Chateaubriant;47.729359;-1.3637892 +0940124Z;Lycée General Et Technologique Hector Berlioz;106 Avenue De Paris;94307;Vincennes;48.8456885;2.4269693 +0491966W;Lycée General Et Technologique Henri Bergson;85 Rue De La Barre;49036;Angers;47.4721055;-0.5929403 +0750711R;Lycée General Et Technologique Henri Bergson;27 Rue Edouard Pailleron;75019;Paris 19;48.8810293;2.377875 +0500082T;Lycée General Et Technologique Henri Cornat;36 Rue Henri Cornat;50700;Valognes;49.5071261;-1.4752266 +0620113L;Lycée General Et Technologique Henri Darras;Chemin Des Manufactures;62803;Lievin;50.412424;2.766446 +0340009A;Lycée General Et Technologique Henri Iv;1 Rue Ignace Brunel;34543;Beziers;43.3436459;3.2107736 +0540042C;Lycée General Et Technologique Henri Loritz;29 Rue Des Jardiniers;54042;Nancy;48.6903847;6.1913797 +0061884R;Lycée General Et Technologique Henri Matisse;101 Avenue Foch;06140;Vence;43.72337;7.1054579 +0312290W;Lycée General Et Technologique Henri Matisse;Avenue Du Comminges;31270;Cugnaux;43.5335144;1.3592883 +0670041F;Lycée General Et Technologique Henri Meck;10 Rue Henri Meck;67125;Molsheim;48.5361513;7.4912458 +0770930T;Lycée General Et Technologique Henri Moissan;20 Cours De Verdun;77100;Meaux;48.9584066;2.8748634 +0710054Y;Lycée General Et Technologique Henri Parriat;49 Rue De Gourdon;71307;Montceau Les Mines;46.6749138;4.3728594 +0540038Y;Lycée General Et Technologique Henri Poincare;2 Rue De La Visitation;54042;Nancy;48.6918437;6.179348 +0590221V;Lycée General Et Technologique Henri Wallon;16 Place De La Republique;59322;Valenciennes;50.3630096;3.5208558 +0930116W;Lycée General Et Technologique Henri Wallon;146 Rue Des Cites;93300;Aubervilliers;48.9123093;2.3885117 +0130175V;Lycée General Et Technologique Honore Daumier;46 Avenue Clot Bey;13008;Marseille 08;43.2585306;5.3838879 +0360024F;Lycée General Et Technologique Honore De Balzac;8 Rue De La Limoise;36105;Issoudun;46.9567081;2.0000772 +0750705J;Lycée General Et Technologique Honore De Balzac;118 Boulevard Bessieres;75849;Paris 17;48.8954075;2.316289 +0060033D;Lycée General Et Technologique Honore D'estienne D'orves;13 Avenue D Estienne D Orves;06050;Nice;43.7036523;7.2446985 +0420042T;Lycée General Et Technologique Honore D'urfe;1 Impasse Le Chatelier;42014;St Etienne;45.4143637;4.3895743 +0601826J;Lycée General Et Technologique Hugues Capet;Avenue De Reims;60309;Senlis;49.1915037;2.5690088 +0510035L;Lycée General Et Technologique Hugues Libergier;55 Rue Libergier;51095;Reims;49.2514521;4.0278114 +0161095D;Lycée General Et Technologique Image Et Son;303 Avenue De Navarre;16022;Angouleme;45.6332536;0.1538142 +0570108H;Lycée General Et Technologique Industriel;15 Route De La Briquerie;57100;Thionville;49.363583;6.1609848 +0010072Y;Lycée General Et Technologique International;Avenue Des Sports;01216;Ferney Voltaire;46.2620007;6.1160646 +0061642C;Lycée General Et Technologique International;190 Rue F. Mistral;06902;Valbonne;43.6211121;7.0416901 +0040543U;Lycée General Et Technologique International Iter;116 Bd Regis Ryckebush;04100;Manosque;43.831469;5.7990491 +0312093G;Lycée General Et Technologique International Victor Hugo;Boulevard Victor Hugo;31773;Colomiers;43.6169776;1.3132042 +0340076Y;Lycée General Et Technologique Irene Et Frederic Joliot Curi;19 Boulevard Joliot Curie;34200;Sete;43.3972261;3.6659039 +0840017M;Lycée General Et Technologique Ismael Dauphin;Rue Pierre Fabre;84300;Cavaillon;43.8245562;5.0387837 +0851401L;Lycée General Et Technologique J.de Lattre De Tassigny;165 Rue Hubert Cailler;85021;La Roche Sur Yon;46.6754534;-1.4044832 +0420014M;Lycée General Et Technologique Jacob Holtzer;5 Rue Michelet;42704;Firminy;45.3882874;4.2914303 +0750713T;Lycée General Et Technologique Jacquard;2 Et 2 Bis Rue Bouret;75019;Paris 19;48.8805143;2.3765501 +0770933W;Lycée General Et Technologique Jacques Amyot;6 Bis Rue Michelet;77000;Melun;48.5454598;2.6590212 +0060001U;Lycée General Et Technologique Jacques Audiberti;Boulevard Wilson;06631;Antibes;43.578323;7.11987 +0692717D;Lycée General Et Technologique Jacques Brel;7 Avenue D'oschatz;69200;Venissieux;45.6952773;4.8802134 +0931430Z;Lycée General Et Technologique Jacques Brel;4-6 Rue Dulcie September;93120;La Courneuve;48.9284905;2.4004255 +0540070H;Lycée General Et Technologique Jacques Callot;12 Rue Jacques Callot;54500;Vandoeuvre Les Nancy;48.6663588;6.1630763 +0180007K;Lycée General Et Technologique Jacques Coeur;108 Rue Jean Baffier;18026;Bourges;47.0738989;2.409201 +0371418R;Lycée General Et Technologique Jacques De Vaucanson;1 Rue Vedrines;37081;Tours;47.4229477;0.7057627 +0930120A;Lycée General Et Technologique Jacques Feyder;10 Rue Henri Wallon;93806;Epinay Sur Seine;48.9551431;2.3208206 +0540058V;Lycée General Et Technologique Jacques Marquette;Place Foch;54701;Pont A Mousson;48.90329;6.054868 +0451462V;Lycée General Et Technologique Jacques Monod;7 Rue Leon Blum;45803;St Jean De Braye;47.9106684;1.9688004 +0641839B;Lycée General Et Technologique Jacques Monod;10 Rue Du Parvis;64234;Lescar;43.332829;-0.4354594 +0921555R;Lycée General Et Technologique Jacques Monod;46 Rue Du Fort;92140;Clamart;48.7956785;2.2724849 +0271431J;Lycée General Et Technologique Jacques Prevert;30 Route De Saint-paul;27500;Pont Audemer;49.3509874;0.5239679 +0440077V;Lycée General Et Technologique Jacques Prevert;17 Rue Joseph Malegue;44260;Savenay;47.364721;-1.9397342 +0911577V;Lycée General Et Technologique Jacques Prevert;23 Rue Jules Ferry;91162;Longjumeau;48.6865888;2.2969624 +0920134W;Lycée General Et Technologique Jacques Prevert;163 Rue De Billancourt;92100;Boulogne Billancourt;48.8333966;2.2376962 +0950651S;Lycée General Et Technologique Jacques Prevert;23 Chemin Vert De Boissy;95150;Taverny;49.0137009;2.2123305 +0622276M;Lycée General Et Technologique Jan Lavezzari;Avenue Du Phare;62602;Berck;50.3988519;1.5605537 +9710923B;Lycée General Et Technologique Jardin D'essai;Route Des Abymes;97139;Les Abymes;16.2417208;-61.4735645 +0330020T;Lycée General Et Technologique Jaufre Rudel;2 Rue Urbain Chasseloup;33394;Blaye;45.1239711;-0.660485 +0241137F;Lycée General Et Technologique Jay De Beaufort;9 Rue Turenne;24007;Perigueux;45.1825357;0.7117974 +0830025B;Lycée General Et Technologique Jean Aicard;Avenue Gallieni;83412;Hyeres;43.1188945;6.1262759 +0910627M;Lycée General Et Technologique Jean Baptiste Corot;Pl Davout Le Chateau;91605;Savigny Sur Orge;48.6851259;2.349282 +0870050G;Lycée General Et Technologique Jean Baptiste Darnet;28 Avenue De Perigueux;87500;St Yrieix La Perche;45.5048579;1.2020927 +0801700N;Lycée General Et Technologique Jean Baptiste Delambre;Rue Montaigne;80084;Amiens;49.9121084;2.3220718 +0590071G;Lycée General Et Technologique Jean Bart;1 Rue Du Nouvel Arsenal;59383;Dunkerque;51.0363738;2.3838308 +0492148U;Lycée General Et Technologique Jean Bodin;Av De L Europe;49137;Les Ponts De Ce;47.4409485;-0.5286577 +0352318E;Lycée General Et Technologique Jean Brito;Avenue Du Bois Greffier;35470;Bain De Bretagne;47.8470053;-1.6939432 +0400067F;Lycée General Et Technologique Jean Cassaigne;Avenue Kennedy;40280;St Pierre Du Mont;43.869494;-0.489719 +0133195C;Lycée General Et Technologique Jean Cocteau;Avenue Jean Cocteau;13141;Miramas;43.5872654;5.0088295 +0332747G;Lycée General Et Technologique Jean Condorcet;89 Rue Condorcet;33030;Bordeaux;44.8590368;-0.5776445 +0170028N;Lycée General Et Technologique Jean Dautet;18 Rue Delayant;17022;La Rochelle;46.1645196;-1.1540598 +0570098X;Lycée General Et Technologique Jean De Pange;16 Rue Du Lycée;57216;Sarreguemines;0.0;0.0 +0820004J;Lycée General Et Technologique Jean De Prades;Route De Toulouse;82102;Castelsarrasin;44.0334192;1.1211267 +0500016W;Lycée General Et Technologique Jean Francois Millet;1 Rue De Bougainville;50130;Cherbourg Octeville;49.6407942;-1.6467298 +0870003F;Lycée General Et Technologique Jean Giraudoux;Avenue Charles De Gaulle;87300;Bellac;46.1255359;1.061553 +0350011X;Lycée General Et Technologique Jean Guehenno;11 Rue Du Champ Rossignol;35305;Fougeres;48.3484995;-1.2000706 +0610021S;Lycée General Et Technologique Jean Guehenno;16 Rue Pierre Huet;61105;Flers;48.744858;-0.552585 +0541270M;Lycée General Et Technologique Jean Hanzelet;Place De Trey;54701;Pont A Mousson;48.9095605;6.0576284 +0510032H;Lycée General Et Technologique Jean Jaures;17 Rue Ruinart De Brimont;51062;Reims;49.258353;4.0443359 +0810012Y;Lycée General Et Technologique Jean Jaures;Route De Blaye;81400;Carmaux;44.0511877;2.1480562 +9741182S;Lycée General Et Technologique Jean Joly;2 Chemin La Ouete;97899;St Louis;-21.2709804;55.4310343 +0132210G;Lycée General Et Technologique Jean Lurcat;Chemin De St Macaire;13693;Martigues;43.4217515;5.0344096 +0880004B;Lycée General Et Technologique Jean Lurcat;22 Av De Lattre De Tassigny;88600;Bruyeres;48.205641;6.7148648 +0350026N;Lycée General Et Technologique Jean Mace;10 Rue Jean Mace;35704;Rennes;48.1173362;-1.6712538 +0790023W;Lycée General Et Technologique Jean Mace;18-20 Rue Gustave Eiffel;79004;Niort;46.3310176;-0.4685901 +0390019J;Lycée General Et Technologique Jean Michel;400 Rue Du Dr Michel;39015;Lons Le Saunier;46.6791715;5.5512839 +0150006A;Lycée General Et Technologique Jean Monnet;10 Rue Du Docteur Chibret;15005;Aurillac;44.9354809;2.4517539 +0341736C;Lycée General Et Technologique Jean Monnet;Rue De Malbosc;34088;Montpellier;43.6319486;3.8311411 +0420043U;Lycée General Et Technologique Jean Monnet;16 Rue Portail Rouge;42014;St Etienne;45.4168613;4.4105286 +0601832R;Lycée General Et Technologique Jean Monnet;10 Rue Des Cedres;60803;Crepy En Valois;49.2239895;2.8929225 +0670078W;Lycée General Et Technologique Jean Monnet;2 Place A Schweitzer;67028;Strasbourg;48.6419332;7.7584307 +0741476C;Lycée General Et Technologique Jean Monnet;1 Place De Lattre De Tassigny;74106;Annemasse;46.1828008;6.2460882 +0080040A;Lycée General Et Technologique Jean Moulin;996 Avenue De La Cite Scolaire;08500;Revin;49.9269895;4.6510517 +0180024D;Lycée General Et Technologique Jean Moulin;45 Rue Jean Moulin;18206;St Amand Montrond;46.7345343;2.5142771 +0290023T;Lycée General Et Technologique Jean Moulin;10 Rue Du Lycée;29150;Chateaulin;0.0;0.0 +0340011C;Lycée General Et Technologique Jean Moulin;Av Des Martyrs De La Resistance;34521;Beziers;43.3429502;3.233546 +0340059E;Lycée General Et Technologique Jean Moulin;1 Avenue Vidal De La Blache;34120;Pezenas;43.4590752;3.4162673 +0570029X;Lycée General Et Technologique Jean Moulin;7 Rue Maurice Barres;57608;Forbach;49.183268;6.9061491 +0590185F;Lycée General Et Technologique Jean Moulin;49 Bd Du General De Gaulle;59051;Roubaix;50.68501;3.17217 +0730005J;Lycée General Et Technologique Jean Moulin;12 Rue Felix Chautemps;73202;Albertville;45.6734316;6.3903457 +0772120L;Lycée General Et Technologique Jean Moulin;6-8 Av Jean Moulin - Torcy;77206;Torcy;48.8349124;2.6594968 +0830015R;Lycée General Et Technologique Jean Moulin;Place De La Paix;83300;Draguignan;43.535376;6.469779 +0860028N;Lycée General Et Technologique Jean Moulin;Avenue Jean Moulin;86501;Montmorillon;46.428122;0.876037 +0440062D;Lycée General Et Technologique Jean Perrin;20 Rue Du Chateau De Reze;44400;Reze;47.1806714;-1.5600159 +0590110Z;Lycée General Et Technologique Jean Perrin;2 Avenue Sakharov;59832;Lambersart;50.6476975;3.038367 +0690082P;Lycée General Et Technologique Jean Perrin;48 Rue Pierre Baizet;69338;Lyon 09;45.7936129;4.8146401 +0760076V;Lycée General Et Technologique Jean Prevost;Avenue Jean Prevost;76290;Montivilliers;49.5450214;0.177044 +0595786U;Lycée General Et Technologique Jean Prouve;2 Rue De Lompret;59463;Lille;50.6533164;2.9946618 +0930118Y;Lycée General Et Technologique Jean Renoir;11 Rue Fremin;93141;Bondy;48.9027563;2.473295 +0140017T;Lycée General Et Technologique Jean Rostand;98 Route D'ifs;14054;Caen;49.1629104;-0.3522713 +0590184E;Lycée General Et Technologique Jean Rostand;361 Grande Rue;59057;Roubaix;50.6954992;3.1964449 +0600009J;Lycée General Et Technologique Jean Rostand;Place Georges Paquier;60501;Chantilly;49.181337;2.462574 +0120012Y;Lycée General Et Technologique Jean Vigo;Le Puits De Cales;12100;Millau;44.090247;3.059129 +0301722J;Lycée General Et Technologique Jean Vilar;616 Av Du Dr Paul Gache;30409;Villeneuve Les Avignon;43.9814603;4.788257 +0772229E;Lycée General Et Technologique Jean Vilar;83 Avenue Salvador Allende;77100;Meaux;48.9547859;2.893935 +0780582J;Lycée General Et Technologique Jean Vilar;1 Rue Jacques Prevert;78375;Plaisir;48.8240075;1.9601951 +0540076P;Lycée General Et Technologique Jean Zay;2 Rue De La Tuilerie;54801;Jarny;49.1581264;5.8767019 +0630069H;Lycée General Et Technologique Jean Zay;21 Rue Jean Zay;63300;Thiers;45.864148;3.5305398 +0572027U;Lycée General Et Technologique Jean-baptiste Colbert;7 Impasse Colbert;57129;Thionville;49.366287;6.1478214 +0590064Z;Lycée General Et Technologique Jean-baptiste Corot;133 Rue Saint Vaast;59508;Douai;50.3751343;3.0784266 +0601906W;Lycée General Et Technologique Jean-baptiste Corot;Rue Henri Lebesgue;60000;Beauvais;49.42964;2.081875 +0300002P;Lycée General Et Technologique Jean-baptiste Dumas;Place De La Belgique;30104;Ales;44.128435;4.0760861 +0750700D;Lycée General Et Technologique Jean-baptiste Say;11 Bis Rue D'auteuil;75016;Paris 16;48.8474879;2.2677956 +0950648N;Lycée General Et Technologique Jean-jacques Rousseau;20 Rue De Jaigny;95160;Montmorency;48.9902224;2.3179386 +0211909L;Lycée General Et Technologique Jean-marc Boivin;4 Bis Route De Dijon;21800;Chevigny St Sauveur;47.3024948;5.127503 +0290034E;Lycée General Et Technologique Jean-marie Le Bris;2 Place Du Lycée;29172;Douarnenez;0.0;0.0 +0782132U;Lycée General Et Technologique Jeanne D Albret;6 Rue Giraud Teulon;78101;St Germain En Laye;48.8936305;2.0989384 +0540039Z;Lycée General Et Technologique Jeanne D Arc;16 Rue Pierre Fourier;54000;Nancy;48.6932069;6.1859085 +0760091L;Lycée General Et Technologique Jeanne D'arc;Rue Sainte-genevieve Du Mont;76044;Rouen;49.4441008;1.1081523 +0601824G;Lycée General Et Technologique Jeanne Hachette;31 Boulevard Amyot D Inville;60009;Beauvais;49.4354984;2.081916 +0693044J;Lycée General Et Technologique Jean-paul Sartre;93 Avenue Francois Mitterrand;69675;Bron;45.7372573;4.9196386 +0920802X;Lycée General Et Technologique Jean-pierre Vernant;21 Rue Du Dr Ledermann;92310;Sevres;48.8255916;2.211662 +0570085H;Lycée General Et Technologique Jean-victor Poncelet;Rue Des Anglais;57501;St Avold;49.1018211;6.7061996 +0760023M;Lycée General Et Technologique Jehan Ango;25 Rue Roger Lecoffre;76203;Dieppe;49.9217708;1.0652099 +0490002L;Lycée General Et Technologique Joachim Du Bellay;1 Avenue Marie Talet;49105;Angers;47.4762607;-0.5475231 +0340038G;Lycée General Et Technologique Joffre;150 Allee De La Citadelle;34060;Montpellier;43.6135477;3.8827869 +0311902Z;Lycée General Et Technologique Jolimont;44 Chemin Cassaing;31079;Toulouse;43.6168618;1.4763001 +0350029S;Lycée General Et Technologique Joliot-curie;144 Bd De Vitre;35703;Rennes;48.1220757;-1.6510693 +0920141D;Lycée General Et Technologique Joliot-curie;92 Avenue Joliot Curie;92014;Nanterre;48.890764;2.2068759 +0790019S;Lycée General Et Technologique Joseph Desfontaines;2 Rue Guillotiere;79500;Melle;46.2238133;-0.144721 +9720004X;Lycée General Et Technologique Joseph Gaillard;Rue Marie Therese Gertrude;97261;Fort De France;14.6040646;-61.084565 +0560038M;Lycée General Et Technologique Joseph Loth;4 Rue J M De Lamennais;56306;Pontivy;48.0662246;-2.9630909 +0220065B;Lycée General Et Technologique Joseph Savina;5 Place De La Republique;22220;Treguier;48.7859048;-3.2335656 +0010016M;Lycée General Et Technologique Joseph-marie Carriat;1 Rue De Crouy;01011;Bourg En Bresse;46.1981597;5.2225406 +0440001M;Lycée General Et Technologique Joubert;160 Rue Du Pressoir Rouge;44154;Ancenis;47.3752742;-1.1822546 +0260036Y;Lycée General Et Technologique Jules Algoud;Rue Barthelemy De Laffemas;26901;Valence;44.917059;4.915496 +0060014H;Lycée General Et Technologique Jules Ferry;82 Boulevard De La Republique;06402;Cannes;43.5586684;7.0219303 +0770924L;Lycée General Et Technologique Jules Ferry;4 Rue Henri Dunant;77527;Coulommiers;48.8240411;3.0905384 +0781845G;Lycée General Et Technologique Jules Ferry;7 Rue Bouyssel;78700;Conflans Ste Honorine;48.9989596;2.0960496 +0880055G;Lycée General Et Technologique Jules Ferry;48 Rue Saint Charles;88109;St Die Des Vosges;48.2877577;6.9551559 +0290013G;Lycée General Et Technologique Jules Lesven;34 Rue Jules Lesven;29801;Brest;48.4056622;-4.4787824 +0590282L;Lycée General Et Technologique Jules Mousseron;Boulevard Du 8 Mai 1945;59723;Denain;50.32402;3.396566 +0580031U;Lycée General Et Technologique Jules Renard;11 Boulevard Saint Exupery;58002;Nevers;47.0034899;3.1609303 +0600021X;Lycée General Et Technologique Jules Uhry;10 Rue Aristide Briand;60107;Creil;49.2580804;2.4755361 +0021939X;Lycée General Et Technologique Jules Verne;;02401;Chateau Thierry;49.064633;3.479051 +0911983L;Lycée General Et Technologique Jules Verne;49 Rue D'arpajon;91470;Limours;48.6420954;2.0892316 +0570146Z;Lycée General Et Technologique Julie Daubie;Rue Joffre;57120;Rombas;49.2534261;6.0999961 +0690032K;Lycée General Et Technologique Juliette Recamier;57 Rue De La Charite;69287;Lyon 02;45.7563614;4.8333688 +0500036T;Lycée General Et Technologique Julliot De La Morandiere;Rue De La Crete;50406;Granville;48.8264841;-1.5782999 +0920144G;Lycée General Et Technologique L Agora;120 Rue De Verdun;92816;Puteaux;48.8780243;2.2345512 +0782563M;Lycée General Et Technologique La Bruyere;31 Avenue De Paris;78000;Versailles;48.8005385;2.1377792 +0440086E;Lycée General Et Technologique La Coliniere;129 Rue Du Landreau;44319;Nantes;47.2345916;-1.5099978 +0801882L;Lycée General Et Technologique La Hotoie;Rue Du Batonnier Mahiu;80016;Amiens;49.8964029;2.2741636 +0690038S;Lycée General Et Technologique La Martiniere Duchere;300 Avenue Andrei Sakharov;69338;Lyon 09;45.7867171;4.7986751 +0690037R;Lycée General Et Technologique La Martiniere Diderot;18 Place Gabriel Rambaud;69283;Lyon 01;45.767838;4.8294264 +0692866R;Lycée General Et Technologique La Martiniere Monplaisir;41 Rue Antoine Lumiere;69372;Lyon 08;45.7399085;4.8673503 +9830002K;Lycée General Et Technologique La Perouse;5 Rue G. Baudoux;98849;Noumea;-22.2811991;166.4425546 +0710023P;Lycée General Et Technologique La Prat's;Rue Du 19 Mars 1962;71250;Cluny;46.4311567;4.6548977 +0650040R;Lycée General Et Technologique La Serre De Sarsan;Rue St Exupery;65107;Lourdes;43.1082589;-0.0241297 +0021522U;Lycée General Et Technologique La Thierache;Le Pont De Pierre;02140;Fontaine Les Vervins;49.832387;3.886854 +0740046Y;Lycée General Et Technologique La Versoie;12 Avenue Du Forchat;74203;Thonon Les Bains;46.3652365;6.4698132 +0690029G;Lycée General Et Technologique Lacassagne;93 Rue Antoine Charial;69425;Lyon 03;45.7577924;4.8703604 +6200002H;Lycée General Et Technologique Laetitia Bonaparte;3, Ave Napoleon Iii;20192;Ajaccio;41.9266076;8.7346838 +0920145H;Lycée General Et Technologique Lakanal;3 Av Pdt Franklin Roosevelt;92331;Sceaux;48.7772605;2.3077341 +0800007Y;Lycée General Et Technologique Lamarck;Avenue Robert Solente;80301;Albert;49.9999141;2.6573268 +0710045N;Lycée General Et Technologique Lamartine;Avenue Des Gaises;71018;Macon;46.317061;4.82604 +0240025X;Lycée General Et Technologique Laure Gatet;25 Av G Pompidou;24003;Perigueux;45.189843;0.7240838 +0530016E;Lycée General Et Technologique Lavoisier;281 Rue Du Pommier;53103;Mayenne;48.3058312;-0.628314 +0782546U;Lycée General Et Technologique Le Corbusier;88 Rue De Villiers;78300;Poissy;48.9233505;2.0271586 +0930117X;Lycée General Et Technologique Le Corbusier;44 Rue Leopold Rechossiere;93533;Aubervilliers;48.9145674;2.3910044 +0250033A;Lycée General Et Technologique Le Grand Chenois;Rue Pierre Donzelot;25204;Montbeliard;47.496347;6.808773 +9741185V;Lycée General Et Technologique Le Verger;1 Avenue Des Corossols;97438;Ste Marie;-20.9285865;55.4894457 +9740001H;Lycée General Et Technologique Leconte De Lisle;Cite Scolaire Du Butor;97491;St Denis;48.936181;2.357443 +0370001A;Lycée General Et Technologique Leonard De Vinci;Rue Du Clos Des Gardes;37402;Amboise;47.3993791;0.9837666 +0430947W;Lycée General Et Technologique Leonard De Vinci;Le Mazel;43120;Monistrol Sur Loire;45.292432;4.172183 +0951753P;Lycée General Et Technologique Leonard De Vinci;2 Rue Robquin;95470;St Witz;49.0876254;2.55935 +0170029P;Lycée General Et Technologique Leonce Vieljeux;118 Rue Des Gonthieres;17028;La Rochelle;46.1774956;-1.1513475 +0271579V;Lycée General Et Technologique Leopold Sedar Senghor;Rue Du Canada;27035;Evreux;49.0100928;1.1380869 +0760109F;Lycée General Et Technologique Les Bruyeres;67 Avenue Des Canadiens;76300;Sotteville Les Rouen;49.4127725;1.0733177 +0701052N;Lycée General Et Technologique Les Haberges;1 Rue Du Dr Jean Georges Girard;70014;Vesoul;47.630099;6.140727 +0721329D;Lycée General Et Technologique Les Horizons;Rue Victor Duruy;72650;St Saturnin;48.056355;0.165149 +0100025Y;Lycée General Et Technologique Les Lombards;12 Avenue Des Lombards;10000;Troyes;48.2747672;4.0725464 +0780515L;Lycée General Et Technologique Les Sept Mares;13 Rue De La Beauce;78310;Maurepas;48.766684;1.9452384 +0261277X;Lycée General Et Technologique Les Trois Sources;110 Chemin Du Valentin;26501;Bourg Les Valence;44.9493203;4.8967491 +0440029T;Lycée General Et Technologique Livet;16 Rue Dufour;44042;Nantes;47.2252268;-1.5446675 +0830032J;Lycée General Et Technologique Lorgues;1 Rue Emile Heraud;83510;Lorgues;43.4925907;6.3552396 +0693330V;Lycée General Et Technologique Louis Aragon;12 Che De La Cote A Cailloux;69700;Givors;45.6012034;4.7609173 +0680034T;Lycée General Et Technologique Louis Armand;3 Boulevard Des Nations;68058;Mulhouse;47.7321514;7.305391 +0731248K;Lycée General Et Technologique Louis Armand;321 Rue Du Grand Champ;73020;Chambery;45.5947932;5.9203139 +0860037Y;Lycée General Et Technologique Louis Armand;63 Rue De La Bugellerie;86022;Poitiers;46.5974421;0.3312595 +0541286E;Lycée General Et Technologique Louis Bertrand;27 Av Albert De Briey;54154;Briey;49.2572689;5.9349594 +0620040G;Lycée General Et Technologique Louis Blaringhem;Boulevard Victor Hugo;62408;Bethune;50.5308387;2.6424351 +0781861Z;Lycée General Et Technologique Louis De Broglie;1 Avenue Jean Beranger;78160;Marly Le Roi;48.8668593;2.1005876 +0640011P;Lycée General Et Technologique Louis De Foix;4 Av Jean Rostand;64103;Bayonne;43.4849353;-1.4754015 +0880019T;Lycée General Et Technologique Louis Lapicque;5 Rue Nicolas Bellot;88021;Epinal;48.1749063;6.4408913 +0540066D;Lycée General Et Technologique Louis Majorelle;16 Rue Porte De Metz;54200;Toul;48.6783902;5.8933465 +0250008Y;Lycée General Et Technologique Louis Pasteur;4 Rue Du Lycée;25043;Besancon;0.0;0.0 +0590117G;Lycée General Et Technologique Louis Pasteur;1 Rue Des Urbanistes;59000;Lille;50.6417246;3.0706587 +0590207E;Lycée General Et Technologique Louis Pasteur;151 Boulevard Louise Michel;59490;Somain;50.3493932;3.2826358 +0620095S;Lycée General Et Technologique Louis Pasteur;800 Rue Leon Blum;62251;Henin Beaumont;50.4306013;2.9485013 +0670082A;Lycée General Et Technologique Louis Pasteur;24 Rue Humann;67085;Strasbourg;48.5756269;7.7381955 +9741050Y;Lycée General Et Technologique Louis Payen;329 Rue Saint Louis;97863;St Paul;-21.000214;55.2816619 +0250010A;Lycée General Et Technologique Louis Pergaud;91 93 Boulevard Blum;25022;Besancon;47.263734;6.0453694 +0570058D;Lycée General Et Technologique Louis Vincent;Rue De Verdun;57000;Metz;49.1064034;6.1678408 +0270026G;Lycée General Et Technologique Louise Michel;47 Route De Dieppe;27140;Gisors;49.2850688;1.7823312 +0931613Y;Lycée General Et Technologique Louise Michel;70 Avenue Jean Jaures;93000;Bobigny;48.9025957;2.440509 +0841093G;Lycée General Et Technologique Lucie Aubrac;224 Rue Ernest Laffont;84500;Bollene;44.2939051;4.7496556 +0021946E;Lycée General Et Technologique Lycée Europeen;Avenue De Noue;02600;Villers Cotterets;49.2513352;3.0801771 +9741324W;Lycée General Et Technologique Mahatma Gandhi;Zac Porte Des Salazes;97440;St Andre;0.0;0.0 +0240005A;Lycée General Et Technologique Maine De Biran;108 Rue Valette;24108;Bergerac;44.8571395;0.4744008 +0140013N;Lycée General Et Technologique Malherbe;14 Avenue Albert Sorel;14052;Caen;49.1758952;-0.3712852 +0783140P;Lycée General Et Technologique Mansart;26 Rue Victorien Sardou;78210;St Cyr L Ecole;48.8044358;2.0638552 +0271582Y;Lycée General Et Technologique Marc Bloch;1 Voie Bacheliere;27106;Val De Reuil;49.2680332;1.2021861 +0672604S;Lycée General Et Technologique Marc Bloch;Allee Blaise Pascal;67803;Bischheim;48.6165342;7.7652418 +0511926S;Lycée General Et Technologique Marc Chagall;60 Chaussee Saint Martin;51726;Reims;49.2416308;4.0282581 +0130037V;Lycée General Et Technologique Marcel Pagnol;128 Boulevard De Saint Loup;13395;Marseille 10;43.2831428;5.4344926 +0910623H;Lycée General Et Technologique Marcel Pagnol;Avenue De La Terrasse;91205;Athis Mons;48.6946547;2.3768362 +0690104N;Lycée General Et Technologique Marcel Sembat;20 Boulevard Marcel Sembat;69694;Venissieux;45.7159355;4.8819736 +0760110G;Lycée General Et Technologique Marcel Sembat;128 Rue Leon Salva;76300;Sotteville Les Rouen;49.4152133;1.0855593 +0310039Z;Lycée General Et Technologique Marcelin Berthelot;59 Rue Achille Viadieu;31078;Toulouse;43.5883613;1.4444026 +0860003L;Lycée General Et Technologique Marcelin Berthelot;1 Avenue Du President Wilson;86106;Chatellerault;46.8148527;0.5406476 +0930124E;Lycée General Et Technologique Marcelin Berthelot;110 Avenue Jean Jaures;93500;Pantin;48.9063319;2.3950721 +0940120V;Lycée General Et Technologique Marcelin Berthelot;6 Boulevard Maurice Berteaux;94100;St Maur Des Fosses;48.8100943;2.470758 +0810033W;Lycée General Et Technologique Marechal Soult;Rue Du Lycée;81207;Mazamet;0.0;0.0 +0590086Y;Lycée General Et Technologique Marguerite De Flandre;15 Rue Pasteur;59147;Gondecourt;50.5443046;2.9799955 +0180006J;Lycée General Et Technologique Marguerite De Navarre;50 Rue De Vauvert;18016;Bourges;47.0813154;2.3815361 +0610002W;Lycée General Et Technologique Marguerite De Navarre;Avenue Du General Leclerc;61014;Alencon;48.4166835;0.0894658 +0160003S;Lycée General Et Technologique Marguerite De Valois;Rue Louise Leriget;16017;Angouleme;45.6532678;0.1797211 +0470018P;Lycée General Et Technologique Marguerite Filhol;Avenue Charles De Gaulle;47501;Fumel;44.498344;0.958251 +0721493G;Lycée General Et Technologique Marguerite Yourcenar;2 Rue Du Miroir;72007;Le Mans;47.9895703;0.2007066 +0141555P;Lycée General Et Technologique Marie Curie;16 Rue Octave Greard;14504;Vire;48.8401949;-0.8975237 +0382920T;Lycée General Et Technologique Marie Curie;Avenue Du 8 Mai 1945;38435;Echirolles;45.1701009;5.7563365 +0650026A;Lycée General Et Technologique Marie Curie;Rue Georges Ledormeur;65930;Tarbes;43.223345;0.07366 +0782567S;Lycée General Et Technologique Marie Curie;70 Av De Paris;78002;Versailles;48.7987327;2.1414585 +0100023W;Lycée General Et Technologique Marie De Champagne;2 Avenue Marie De Champagne;10026;Troyes;48.3040593;4.0617662 +0133244F;Lycée General Et Technologique Marie Madeleine Fourcade;Avenue Du Groupe Manouchian;13120;Gardanne;43.455669;5.470649 +0383263R;Lycée General Et Technologique Marie Reynoard;20 Rue Louis Neel;38190;Villard Bonnot;45.2511485;5.8974898 +0622949U;Lycée General Et Technologique Mariette;69 Rue De Beaurepaire;62321;Boulogne Sur Mer;50.7321268;1.6119389 +0130038W;Lycée General Et Technologique Marseilleveyre;83 Traverse Parangon;13285;Marseille 08;43.2463268;5.3798313 +0772292Y;Lycée General Et Technologique Martin Luther King;21 Av Du General De Gaulle;77600;Bussy St Georges;48.8390621;2.7187791 +0350042F;Lycée General Et Technologique Maupertuis;Rue Pierre De Coubertin;35407;St Malo;48.6400752;-2.0058975 +0911927A;Lycée General Et Technologique Maurice Eliot;Rue De Provence;91860;Epinay Sous Senart;48.6992445;2.5232739 +0132410Z;Lycée General Et Technologique Maurice Genevoix;Avenue General De Gaulle;13700;Marignane;43.4128456;5.2048951 +0790007D;Lycée General Et Technologique Maurice Genevoix;36 Rue De Malabry;79301;Bressuire;46.8407623;-0.4847921 +0921399W;Lycée General Et Technologique Maurice Genevoix;29 Avenue Du Fort;92120;Montrouge;48.810644;2.323172 +0170022G;Lycée General Et Technologique Maurice Merleau-ponty;3 Rue Raymonde Maous;17304;Rochefort;45.9505449;-0.9713481 +0750715V;Lycée General Et Technologique Maurice Ravel;89 Cours De Vincennes;75020;Paris 20;48.8476082;2.4082145 +0330088S;Lycée General Et Technologique Max Linder;43 All Robert Boulin;33505;Libourne;44.9171154;-0.2403445 +0940580V;Lycée General Et Technologique Maximilien Sorre;61 Avenue Du President Wilson;94230;Cachan;48.789111;2.325304 +0680032R;Lycée General Et Technologique Michel De Montaigne;5 Rue De Metz;68090;Mulhouse;47.7489658;7.3421381 +0920149M;Lycée General Et Technologique Michelet;5 Rue Jullien;92174;Vanves;48.8250185;2.287109 +0600015R;Lycée General Et Technologique Mireille Grenet;13 Avenue De Huy;60321;Compiegne;49.4025055;2.8188505 +0740037N;Lycée General Et Technologique Mme De Stael;16 Route De Thairy;74163;St Julien En Genevois;46.1409969;6.0704696 +0270017X;Lycée General Et Technologique Modeste Leroy;32 Rue Pierre Brossolette;27016;Evreux;49.0216265;1.1618627 +0080027L;Lycée General Et Technologique Monge;2 Avenue De St Julien;08000;Charleville Mezieres;49.7613023;4.7131743 +0210017E;Lycée General Et Technologique Montchapet;36 Boulevard Francois Pompon;21000;Dijon;47.3343589;5.0174446 +0630068G;Lycée General Et Technologique Montdory;Cite Pontel 7 Ter Av J.jaures;63304;Thiers;45.857907;3.545177 +0951723G;Lycée General Et Technologique Montesquieu;Rue Emile Zola;95220;Herblay;48.9963505;2.152554 +9720727H;Lycée General Et Technologique Montgerald;Quartier Montgerald;97290;Le Marin;0.0;0.0 +0130042A;Lycée General Et Technologique Montgrand;13 Rue Montgrand;13291;Marseille 06;43.2915831;5.3783833 +0630034V;Lycée General Et Technologique Murat;27 Boulevard Pasteur;63500;Issoire;45.5506873;3.2484695 +0610026X;Lycée General Et Technologique Napoleon;15 Rue Des Sports;61306;L Aigle;48.7582148;0.6363548 +9710940V;Lycée General Et Technologique Nord Basse Terre;La Ramee;97115;Ste Rose;45.9474919;4.838131 +0332081H;Lycée General Et Technologique Odilon Redon;R Maquis Des Vignes Oudides;33250;Pauillac;45.1907714;-0.7495194 +0930123D;Lycée General Et Technologique Olympe De Gouges;205 Rue De Brement;93130;Noisy Le Sec;48.8872318;2.4667557 +0310047H;Lycée General Et Technologique Ozenne;9 Rue Merly;31070;Toulouse;43.610058;1.4428833 +0762169V;Lycée General Et Technologique Pablo Neruda;Chemin Des Bruyeres;76204;Dieppe;49.900744;1.06796 +0620027T;Lycée General Et Technologique Pablo Picasso;2 Boulevard Anatole France;62210;Avion;50.4005382;2.8354531 +0660014G;Lycée General Et Technologique Pablo Picasso;120 Avenue General Jean Gilles;66028;Perpignan;42.6975235;2.9260966 +0941347D;Lycée General Et Technologique Pablo Picasso;2 Avenue Pablo Picasso;94120;Fontenay Sous Bois;48.855411;2.4862776 +0332722E;Lycée General Et Technologique Pape Clement;1 Rue Leo Lagrange;33605;Pessac;44.8086751;-0.6500731 +0690074F;Lycée General Et Technologique Parc Chabrieres;9 Chemin Des Chassagnes;69600;Oullins;45.7224869;4.8100176 +0911251R;Lycée General Et Technologique Parc Des Loges;Boulevard Des Champs Elysees;91012;Evry;48.6410335;2.4334713 +0060029Z;Lycée General Et Technologique Parc Imperial;2 Avenue Paul Arene;06050;Nice;43.7038318;7.2505348 +0320002D;Lycée General Et Technologique Pardailhan;Chemin De Baron;32008;Auch;43.6546824;0.5749589 +7200021K;Lycée General Et Technologique Pascal Paoli;Av President Pierucci;20250;Corte;42.3041836;9.1512357 +0130002G;Lycée General Et Technologique Paul Cezanne;Av J Et M Fontenaille;13100;Aix En Provence;43.5346979;5.467873 +0020032Z;Lycée General Et Technologique Paul Claudel;Place Foch;02001;Laon;49.5655582;3.6097485 +0141275K;Lycée General Et Technologique Paul Cornu;9 Rue Paul Cornu;14107;Lisieux;49.15711;0.2224269 +0590034S;Lycée General Et Technologique Paul Duez;1 Boulevard Paul Bezin;59407;Cambrai;50.174142;3.2420519 +0790024X;Lycée General Et Technologique Paul Guerin;19 Rue Des Fiefs;79004;Niort;46.3145227;-0.443402 +0590010R;Lycée General Et Technologique Paul Hazard;1 Rue Paul Hazard;59426;Armentieres;50.6883173;2.8763804 +0730037U;Lycée General Et Technologique Paul Heroult;307 Avenue Du Mont Cenis;73302;St Jean De Maurienne;45.2725564;6.3502605 +0600002B;Lycée General Et Technologique Paul Langevin;3 Avenue Montaigne;60009;Beauvais;49.414897;2.1063143 +0920147K;Lycée General Et Technologique Paul Langevin;2 Rue Payret Dortail;92150;Suresnes;48.877042;2.2221624 +0920138A;Lycée General Et Technologique Paul Lapie;5 Bd Aristide Briand;92401;Courbevoie;48.899279;2.257718 +0110004V;Lycée General Et Technologique Paul Sabatier;36 Rue Alfred De Musset;11802;Carcassonne;43.2222389;2.3451227 +0720055U;Lycée General Et Technologique Paul Scarron;La Brunetiere;72140;Sille Le Guillaume;48.1924459;-0.118455 +0080039Z;Lycée General Et Technologique Paul Verlaine;Rue Du Docteur Gobinet;08305;Rethel;49.5167514;4.3704596 +0370039S;Lycée General Et Technologique Paul-louis Courier;2 Pl Gregoire De Tours;37012;Tours;47.3959502;0.6953666 +0441992B;Lycée General Et Technologique Pays De Retz;Place Joseph Girard;44211;Pornic;47.1175499;-2.096457 +0130036U;Lycée General Et Technologique Perier;270 Rue Paradis;13295;Marseille 08;43.2942989;5.3763908 +0720027N;Lycée General Et Technologique Perseigne;Rue Jean Jaures;72600;Mamers;48.349964;0.363104 +0332835C;Lycée General Et Technologique Philippe Cousteau;13 Rue Arnaudin;33240;St Andre De Cubzac;44.9914275;-0.4497814 +0840005Z;Lycée General Et Technologique Philippe De Girard;138 Av De Tarascon;84082;Avignon;43.9250989;4.8091279 +0301552Z;Lycée General Et Technologique Philippe Lamour;Rue De L Occitanie;30001;Nimes;43.8322264;4.3863175 +0080045F;Lycée General Et Technologique Pierre Bayle;Rue Rogissart;08200;Sedan;49.6930223;4.9478574 +0383119J;Lycée General Et Technologique Pierre Beghin;Rue De La Roche Brune;38430;Moirans;45.3211861;5.5864031 +0230020X;Lycée General Et Technologique Pierre Bourdan;Place Moliere;23011;Gueret;46.171715;1.871472 +0690132U;Lycée General Et Technologique Pierre Brossolette;161 Cours Emile Zola;69628;Villeurbanne;45.7698199;4.8784389 +0782822U;Lycée General Et Technologique Pierre Corneille;Avenue Corneille;78170;La Celle St Cloud;48.8450625;2.1372043 +0600014P;Lycée General Et Technologique Pierre D Ailly;136 Bd Des Etats Unis;60321;Compiegne;49.406211;2.8338602 +0310024H;Lycée General Et Technologique Pierre D'aragon;14 Avenue Henri Peyrusse;31605;Muret;43.4478905;1.3276737 +0620062F;Lycée General Et Technologique Pierre De Coubertin;320 Boulevard Du 8 Mai;62225;Calais;50.958627;1.837843 +0770931U;Lycée General Et Technologique Pierre De Coubertin;Chaussee De Paris;77100;Meaux;48.9559126;2.869815 +0020049T;Lycée General Et Technologique Pierre De La Ramee;1 Rue Jules Siegfried;02100;St Quentin;49.8511637;3.3005423 +0382270L;Lycée General Et Technologique Pierre Du Terrail;Avenue Du Lycée;38530;Pontcharra;0.0;0.0 +0060026W;Lycée General Et Technologique Pierre Et Marie Curie;Avenue Du Doyen Jean Lepine;06500;Menton;43.777619;7.485669 +0360009P;Lycée General Et Technologique Pierre Et Marie Curie;31 Rue P Et M Curie;36018;Chateauroux;46.809443;1.7081627 +0501219D;Lycée General Et Technologique Pierre Et Marie Curie;377 Rue De L'exode;50010;St Lo;49.107496;-1.0893717 +0880040R;Lycée General Et Technologique Pierre Et Marie Curie;Rue Victor Martin;88307;Neufchateau;48.358763;5.6969839 +0891168L;Lycée General Et Technologique Pierre Larousse;6 Rue Des Montagnes;89130;Toucy;47.739374;3.2968476 +0800046R;Lycée General Et Technologique Pierre Mendes France;Route De Saint Denis;80201;Peronne;49.9361685;2.9435387 +0772188K;Lycée General Et Technologique Pierre Mendes-france;11 Avenue De L'europe;77176;Savigny Le Temple;48.5970024;2.576709 +0850025R;Lycée General Et Technologique Pierre Mendes-france;Boulevard Arago;85021;La Roche Sur Yon;46.6704474;-1.4451322 +9740952S;Lycée General Et Technologique Pierre Poivre;Rue Hippolyte Foucque;97480;St Joseph;-21.3746802;55.6251873 +0040490L;Lycée General Et Technologique Pierre-gilles De Gennes;Quartier St Christophe;04000;Digne Les Bains;44.077491;6.187476 +0861223M;Lycée General Et Technologique Pilote Innovant International;Teleport 5;86130;Jaunay Clan;46.6634435;0.3613592 +0781297L;Lycée General Et Technologique Plaine De Neauphle;Avenue Salvador Allende;78190;Trappes;48.783188;1.98746 +0270042Z;Lycée General Et Technologique Porte De Normandie;830 Chemin Des Poissonniers;27130;Verneuil Sur Avre;48.7405634;0.9156189 +0380089R;Lycée General Et Technologique Portes De L'oisans;Avenue Aristide Briand;38220;Vizille;45.067413;5.769686 +0450049J;Lycée General Et Technologique Pothier;2 Bis Rue Marcel Proust;45044;Orleans;47.908273;1.9068319 +0240035H;Lycée General Et Technologique Pre De Cordy;5 Avenue Josephine Baker;24200;Sarlat La Caneda;44.8703019;1.2059934 +0390786T;Lycée General Et Technologique Pre Saint Sauveur;Cite Scolaire Pre St Sauveur;39201;St Claude;46.387405;5.8677659 +0030051P;Lycée General Et Technologique Presles;Boulevard Du 8 Mai 1945;03306;Cusset;46.131694;3.442323 +9710921Z;Lycée General Et Technologique Providence;Morne L'epingle Providence;97139;Les Abymes;16.27395;-61.502615 +0920798T;Lycée General Et Technologique Rabelais;6 Rue Georges Langrognet;92190;Meudon;48.8019343;2.2402667 +0750688R;Lycée General Et Technologique Rabelais-paramedical Et Socia;9 Rue Francis De Croisset;75018;Paris 18;48.8994731;2.3455576 +0720012X;Lycée General Et Technologique Racan;9 Avenue Du Mans;72500;Chateau Du Loir;47.6987575;0.4132499 +0750664P;Lycée General Et Technologique Racine;20 Rue Du Rocher;75008;Paris 08;48.8765125;2.3224703 +0870118F;Lycée General Et Technologique Raoul Dautry;14 Rue Du Puy Imbert;87036;Limoges;45.8387379;1.2785581 +0580032V;Lycée General Et Technologique Raoul Follereau;9 Boulevard Saint Exupery;58000;Nevers;47.0040888;3.1563606 +0900004R;Lycée General Et Technologique Raoul Follereau;3 Rue Louis Marchal;90016;Belfort;47.6383708;6.8462164 +0550002D;Lycée General Et Technologique Raymond Poincare;1 Place Paul Lemagny;55012;Bar Le Duc;48.7772859;5.1572672 +0594424N;Lycée General Et Technologique Raymond Queneau;Place Leon Blum;59650;Villeneuve D Ascq;50.6501468;3.1057879 +0120031U;Lycée General Et Technologique Raymond Savignac;Rue Agnes Savignac;12203;Villefranche De Rouergue;44.3563661;2.0381734 +0530012A;Lycée General Et Technologique Reaumur;39 Avenue Chanzy;53013;Laval;48.0669908;-0.7596169 +0280036M;Lycée General Et Technologique Remi Belleau;33 Rue Bretonnerie;28400;Nogent Le Rotrou;48.3189144;0.8109948 +0352235P;Lycée General Et Technologique Rene Cassin;Route De St Meen Le Grand;35162;Montfort Sur Meu;48.138879;-1.957605 +0690085T;Lycée General Et Technologique Rene Cassin;75 Route De Saint Clement;69173;Tarare;45.8973407;4.4437914 +0911632E;Lycée General Et Technologique Rene Cassin;17 Rue Jean Moulin;91294;Arpajon;48.580006;2.2446388 +0950646L;Lycée General Et Technologique Rene Cassin;7 Avenue Francois Mitterrand;95500;Gonesse;48.985717;2.4325823 +0840935K;Lycée General Et Technologique Rene Char;2 Rue Pierre A Renoir;84033;Avignon;43.9460637;4.8531827 +0351907H;Lycée General Et Technologique Rene Descartes;Chemin De Ronde;35207;Rennes;48.0893228;-1.6435067 +0631861F;Lycée General Et Technologique Rene Descartes;Avenue Jules Ferry;63801;Cournon D Auvergne;45.7436203;3.2103095 +0693654X;Lycée General Et Technologique Rene Descartes;Avenue De Gadagne;69230;St Genis Laval;45.6945428;4.7859462 +0340023R;Lycée General Et Technologique Rene Gosse;2 Rue Victor Hugo;34800;Clermont L Herault;43.6264775;3.4318207 +0170027M;Lycée General Et Technologique Rene Josue Valin;Rue Henri Barbusse;17023;La Rochelle;46.155842;-1.137402 +0290062K;Lycée General Et Technologique Rene Laennec;61 Rue Du Lycée;29120;Pont L Abbe;0.0;0.0 +0730043A;Lycée General Et Technologique Rene Perrin;41 Rue Rene Perrin;73400;Ugine;45.7514631;6.4161324 +0680060W;Lycée General Et Technologique Ribeaupierre;12 Rue Du Chateau;68150;Ribeauville;48.1969038;7.3163765 +0920799U;Lycée General Et Technologique Richelieu;64 Rue George Sand;92501;Rueil Malmaison;48.8669727;2.1806262 +0311323V;Lycée General Et Technologique Rive Gauche;Avenue Jean Baylet;31081;Toulouse;43.5846573;1.3942102 +9710884J;Lycée General Et Technologique Riviere Des Peres;Riviere Des Peres;97100;Basse Terre;16.008758;-61.7407286 +0801841S;Lycée General Et Technologique Robert De Luzarches;4 Rue Le Mongnier;80097;Amiens;49.8856067;2.3204605 +0693619J;Lycée General Et Technologique Robert Doisneau;5 Rue Du Lycée;69511;Vaulx En Velin;0.0;0.0 +0570057C;Lycée General Et Technologique Robert Schuman;4 Rue Monseigneur Pelt;57074;Metz;49.1122787;6.2034203 +0670020H;Lycée General Et Technologique Robert Schuman;2 Quai Des Pecheurs;67504;Haguenau;48.8145686;7.7953074 +0760058A;Lycée General Et Technologique Robert Schuman;51 Avenue Du 8 Mai;76610;Le Havre;49.5161566;0.1664377 +0620006V;Lycée General Et Technologique Robespierre;Avenue Des Fusilles;62022;Arras;50.2867806;2.7650924 +0750696Z;Lycée General Et Technologique Roger Verlomme;24 Rue Fondary;75015;Paris 15;48.8480367;2.2932272 +0950640E;Lycée General Et Technologique Romain Rolland;1 Place Romain Rolland;95100;Argenteuil;48.9516193;2.2306041 +0280019U;Lycée General Et Technologique Rotrou;Les Marchebeaux;28104;Dreux;48.734267;1.362417 +0260017C;Lycée General Et Technologique Roumanille;Draye De Meyne;26111;Nyons;44.361204;5.139882 +0490055U;Lycée General Et Technologique Sadi Carnot;25 Rue Marceau;49401;Saumur;47.250839;-0.0746699 +0130039X;Lycée General Et Technologique Saint Charles;5 Rue Guy Fabre;13232;Marseille 01;43.3058152;5.3920359 +0130048G;Lycée General Et Technologique Saint Exupery;529 Che De La Madrague Ville;13326;Marseille 15;43.3463387;5.3547981 +0690028F;Lycée General Et Technologique Saint Just;21 Rue Des Farges;69321;Lyon 05;45.7575431;4.8217746 +0171418Z;Lycée General Et Technologique Saint-exupery;Allee De Lattre De Tassigny;17028;La Rochelle;46.1569483;-1.1815663 +0641732K;Lycée General Et Technologique Saint-john Perse;2 Rue J Ferry;64012;Pau;43.319586;-0.3639694 +0141796B;Lycée General Et Technologique Salvador Allende;15 Rue Guyon De Guercheville;14200;Herouville St Clair;49.2033372;-0.3448826 +0941470M;Lycée General Et Technologique Samuel De Champlain;61 Rue Des Bordes;94430;Chennevieres Sur Marne;48.7987853;2.5577377 +9740043D;Lycée General Et Technologique Sarda Garriga;755 Rue De La Communaute;97440;St Andre;-20.9643338;55.6558272 +0850032Y;Lycée General Et Technologique Savary De Mauleon;78 Avenue De Bretagne;85108;Les Sables D Olonne;46.5047869;-1.7776341 +0680073K;Lycée General Et Technologique Scheurer Kestner;1 Rue Moschenross;68802;Thann;47.8011401;7.1139147 +0670002N;Lycée General Et Technologique Schure;2 Rue Du Lycée;67142;Barr;0.0;0.0 +0080007P;Lycée General Et Technologique Sevigne;14 Rue Mme De Sevigne;08013;Charleville Mezieres;49.7710181;4.7172012 +0352304P;Lycée General Et Technologique Sevigne;2 Rue De La Chalotais;35513;Cesson Sevigne;48.1647545;-1.5796421 +0590215N;Lycée General Et Technologique Sevigne;151 Rue De La Malcense;59208;Tourcoing;50.730799;3.1650448 +0951766D;Lycée General Et Technologique Simone De Beauvoir;171 Av De Stalingrad;95141;Garges Les Gonesse;48.9677071;2.4101572 +0420044V;Lycée General Et Technologique Simone Weil;63 Avenue Albert Raimond;42272;St Priest En Jarez;45.4752;4.3691484 +0430021P;Lycée General Et Technologique Simone Weil;22 Boulevard Marechal Joffre;43003;Le Puy En Velay;45.048158;3.8937244 +0750651A;Lycée General Et Technologique Simone Weil;7 Rue De Poitou;75003;Paris 03;48.8613887;2.3639619 +0501839C;Lycée General Et Technologique Sivard De Beaulieu;7 Bis Rue Sivard De Beaulieu;50500;Carentan;49.304491;-1.2397239 +0620063G;Lycée General Et Technologique Sophie Berthelot;224 Boulevard Gambetta;62104;Calais;50.9466002;1.8418599 +0750653C;Lycée General Et Technologique Sophie Germain;9 Rue De Jouy;75004;Paris 04;48.8548803;2.3581925 +0520027X;Lycée General Et Technologique St Exupery;82 Rue Anatole France;52105;St Dizier;48.6430006;4.9626225 +0782539L;Lycée General Et Technologique St Exupery;8 Rue Marcel Fouque;78201;Mantes La Jolie;48.9959726;1.6947006 +9710922A;Lycée General Et Technologique Ste Anne;Poirier De Gissac;97180;Ste Anne;0.0;0.0 +0210012Z;Lycée General Et Technologique Stephen Liegeard;1 Rue Stephen Liegeard;21220;Brochon;47.2390412;4.9716086 +0573326F;Lycée General Et Technologique Teyssier;23 Rue Du Schellenthal;57230;Bitche;49.0565652;7.4161082 +0840004Y;Lycée General Et Technologique Theodore Aubanel;14 Rue De La Palapharnerie;84025;Avignon;43.952567;4.8119249 +0061691F;Lycée General Et Technologique Thierry Maulnier;2 Avenue Claude Debussy;06200;Nice;43.6854715;7.1997893 +0130040Y;Lycée General Et Technologique Thiers;5 Place Du Lycée;13232;Marseille 01;0.0;0.0 +0760005T;Lycée General Et Technologique Thomas Corneille;Avenue Andre Maurois;76360;Barentin;49.5566128;0.9530339 +0311586F;Lycée General Et Technologique Toulouse-lautrec;64 Bd Pierre Curie;31020;Toulouse;43.6251328;1.4415296 +0290051Y;Lycée General Et Technologique Tristan Corbiere;16 Rue De Kerveguen;29671;Morlaix;48.5705251;-3.8221176 +0750647W;Lycée General Et Technologique Turgot;69 Rue De Turbigo;75003;Paris 03;48.8660831;2.3583935 +0870056N;Lycée General Et Technologique Turgot;6 Rue Paul Derignac;87031;Limoges;45.8284168;1.2486231 +0770918E;Lycée General Et Technologique Uruguay France;1 Avenue Des Marronniers;77211;Avon;48.4103009;2.7355078 +0841117H;Lycée General Et Technologique Vaison La Romaine;Vaison La Romaine;84110;Vaison La Romaine;44.2414859;5.0777653 +0761742F;Lycée General Et Technologique Val De Seine;Avenue Georges Braque;76124;Le Grand Quevilly;49.402714;1.04095 +0062015H;Lycée General Et Technologique Valbonne;1265 Route De Biot;06560;Valbonne;43.6435755;7.0381165 +0590122M;Lycée General Et Technologique Valentine Labbe;41 Rue Paul Doumer;59110;La Madeleine;50.6473736;3.0721697 +0911913K;Lycée General Et Technologique Vallee De Chevreuse;8 Rue De Madrid;91192;Gif Sur Yvette;48.7000948;2.0991226 +0762879S;Lycée General Et Technologique Vallee Du Cailly;Rue Du Petit Aulnay;76250;Deville Les Rouen;49.462879;1.045217 +0771512A;Lycée General Et Technologique Van Dongen;45 Rue Jean Mermoz;77400;Lagny Sur Marne;48.8677351;2.7191655 +0950645K;Lycée General Et Technologique Van Gogh;Rue Du General Decaen;95123;Ermont;48.9828272;2.2525097 +0080018B;Lycée General Et Technologique Vauban;15 Rue Andre Bouzy;08600;Givet;50.1333015;4.8290573 +0380033E;Lycée General Et Technologique Vaucanson;27 Rue Anatole France;38030;Grenoble;45.1722442;5.7083809 +0130003H;Lycée General Et Technologique Vauvenargues;60 Boulevard Carnot;13625;Aix En Provence;43.5284055;5.4549853 +0791062A;Lycée General Et Technologique Venise Verte;71 Rue Laurent Bonnevay;79012;Niort;46.3171043;-0.4858123 +0440031V;Lycée General Et Technologique Vial;12 Rue Du 14 Juillet;44042;Nantes;47.2182601;-1.5678263 +0400017B;Lycée General Et Technologique Victor Duruy;Rue Noneres;40002;Mont De Marsan;43.898948;-0.499903 +0352009U;Lycée General Et Technologique Victor Et Helene Basch;15 Avenue Charles Tillon;35083;Rennes;48.1268772;-1.6876718 +0501828R;Lycée General Et Technologique Victor Grignard;12 R Guillaume Fouace;50100;Cherbourg Octeville;49.6385355;-1.6291646 +0130043B;Lycée General Et Technologique Victor Hugo;3 Boulevard Gustave Desplaces;13003;Marseille 03;43.3051852;5.3805831 +0250007X;Lycée General Et Technologique Victor Hugo;1 Rue Rembrandt;25052;Besancon;47.2183047;5.9777021 +0530004S;Lycée General Et Technologique Victor Hugo;4 Rue Du Gal Lemonnier;53200;Chateau Gontier;47.828315;-0.6982177 +0561607T;Lycée General Et Technologique Victor Hugo;26 Avenue Francois Mitterrand;56704;Hennebont;47.8091467;-3.2627756 +0840016L;Lycée General Et Technologique Victor Hugo;139 Av Victor Hugo;84208;Carpentras;44.0515235;5.0474084 +0860034V;Lycée General Et Technologique Victor Hugo;10 Rue Victor Hugo;86034;Poitiers;46.5805351;0.337763 +9720002V;Lycée General Et Technologique Victor Schoelcher;Boulevard Robert Attuly;97262;Fort De France;14.6029213;-61.0763884 +0250032Z;Lycée General Et Technologique Viette;1b Rue P Donzelot;25206;Montbeliard;47.4951765;6.8092234 +0930834B;Lycée General Et Technologique Voillaume;136 Rue De Mitry;93604;Aulnay Sous Bois;48.9457777;2.5099926 +0450782F;Lycée General Et Technologique Voltaire;3 Avenue Voltaire;45072;Orleans;47.8350087;1.937364 +0623915U;Lycée General Et Technologique Voltaire;Place Bertin Ledoux;62410;Wingles;50.494578;2.852404 +0750675B;Lycée General Et Technologique Voltaire;101 Avenue De La Republique;75011;Paris 11;48.8634943;2.3846316 +0590222W;Lycée General Et Technologique Watteau;20 Boulevard Pater;59307;Valenciennes;50.3582668;3.5317714 +0932034F;Lycée General Et Technologique Wolfgang Amadeus Mozart;10 Ave Charles De Gaulle;93152;Le Blanc Mesnil;48.9438125;2.4679399 +0640017W;Lycée General Experimental A.malraux;Rue Du 8 Mai 1945;64203;Biarritz;43.485562;-1.545229 +0590116F;Lycée General Fenelon;27 Rue Alexandre Leleux;59013;Lille;50.6315402;3.0572537 +0750660K;Lycée General Fenelon;2 Rue De L'eperon;75006;Paris 06;48.8533839;2.3409528 +0340005W;Lycée General Ferdinand Fabre;Boulevard Jean Moulin;34600;Bedarieux;43.613182;3.155169 +0120022J;Lycée General Ferdinand Foch;1 Rue Vieussens;12000;Rodez;44.350556;2.5620421 +0312754A;Lycée General Fonsorbes;Avenue Lucie Aubrac Fonsorbes;31470;Fonsorbes;43.535456;1.230645 +0783548H;Lycée General Franco Allemand;5 Rue Collin Mamet;78530;Buc;48.7712546;2.1214749 +0760052U;Lycée General Francois 1er;2 Rue Jean-paul Sartre;76066;Le Havre;49.4954384;0.1137507 +0770927P;Lycée General Francois 1er;11 Rue Victor Hugo;77300;Fontainebleau;48.4091563;2.6957656 +0260006R;Lycée General Francois Jean Armorin;35 Avenue H.grand;26402;Crest;44.731422;5.0164927 +0330026Z;Lycée General Francois Magendie;10 Rue Des Treuils;33023;Bordeaux;44.8270751;-0.5854467 +0820016X;Lycée General Francois Mitterrand;Boulevard Du Quercy;82201;Moissac;44.1012226;1.0931804 +0350710G;Lycée General Francois Rene De Chateaubriand;136 Bd De Vitre;35703;Rennes;48.1220757;-1.6510693 +0840003X;Lycée General Frederic Mistral;37 Rue D Annanelle;84023;Avignon;43.9467812;4.7999217 +0670049P;Lycée General Freppel;25 Rue Du Gen Gouraud;67212;Obernai;48.4616378;7.4873303 +0670079X;Lycée General Fustel De Coulanges;1 Place Du Chateau;67061;Strasbourg;48.5816654;7.7520269 +0750684L;Lycée General Gabriel Faure;81 Avenue De Choisy;75013;Paris 13;48.823831;2.3615927 +0440024M;Lycée General Gabriel Guisthau;3 Rue Marie Anne Du Boccage;44042;Nantes;47.2163672;-1.5683753 +0442112G;Lycée General Galilee;16 Avenue Gustave Flaubert;44353;Guerande;47.3241762;-2.4171927 +0870015U;Lycée General Gay Lussac;12 Boulevard Georges Perin;87031;Limoges;45.8308335;1.2627435 +0470028A;Lycée General George Sand;Bd Pierre De Coubertin;47600;Nerac;44.137773;0.334861 +0754684J;Lycée General Georges Brassens;40 Rue Manin;75019;Paris 19;48.8840069;2.3904607 +0510031G;Lycée General Georges Clemenceau;46 Avenue Georges Clemenceau;51096;Reims;49.2516684;4.0457383 +0300008W;Lycée General Gerard Philipe;17 Avenue Leon Blum;30205;Bagnols Sur Ceze;44.1625881;4.616978 +0240013J;Lycée General Giraut De Borneil;10 Boulevard A Dupuy;24160;Excideuil;45.3397691;1.0460386 +0570107G;Lycée General Helene Boucher;55 Boulevard Foch;57100;Thionville;49.358428;6.1612623 +0750714U;Lycée General Helene Boucher;75 Cours De Vincennes;75020;Paris 20;48.8477131;2.4070344 +0750654D;Lycée General Henri Iv;23 Rue Clovis;75005;Paris 05;48.8463061;2.347663 +0020048S;Lycée General Henri Martin;1 Rue Gabriel Girodon;02100;St Quentin;49.8694377;3.2486887 +0782562L;Lycée General Hoche;73 Avenue De St Cloud;78000;Versailles;48.806093;2.136819 +0783549J;Lycée General International;Rue Du Fer A Cheval;78104;St Germain En Laye;48.8978284;2.0627911 +0383242T;Lycée General International Europole;4 Place De Sfax;38012;Grenoble;45.1946007;5.7111866 +0890003V;Lycée General Jacques Amyot;3 Rue De L'etang Saint Vigile;89015;Auxerre;47.799159;3.571808 +0750668U;Lycée General Jacques Decour;12 Avenue Trudaine;75009;Paris 09;48.881468;2.3434282 +0750699C;Lycée General Janson De Sailly;106 Rue De La Pompe;75016;Paris 16;48.8655035;2.2794818 +0750702F;Lycée General Jean De La Fontaine;1 Place De La Porte Molitor;75016;Paris 16;48.8451877;2.2565988 +0360008N;Lycée General Jean Giraudoux;31 Avenue Marcel Lemoine;36000;Chateauroux;46.8162727;1.696676 +0460026D;Lycée General Jean Lurcat;Quai Jules Ferry;46400;St Cere;44.8580683;1.8929695 +0332745E;Lycée General Jean Monnet;70 Av Gl De Gaulle;33294;Blanquefort;44.910084;-0.635244 +0371417P;Lycée General Jean Monnet;45 Rue De La Gitonniere;37306;Joue Les Tours;47.3382097;0.6558827 +0331636Z;Lycée General Jean Moulin;Bd Francois Mauriac;33210;Langon;44.5478647;-0.2565149 +0690030H;Lycée General Jean Moulin;1 Place Des Minimes;69321;Lyon 05;45.757748;4.8209387 +0420031F;Lycée General Jean Puy;Rue Jean Puy;42328;Roanne;46.0381422;4.0698154 +0630019D;Lycée General Jeanne D'arc;40 Avenue De Grande Bretagne;63037;Clermont Ferrand;45.777262;3.0955677 +0320036R;Lycée General Joseph Saverne;5 Avenue Claude Auge;32600;L Isle Jourdain;43.6128214;1.0853875 +0750669V;Lycée General Jules Ferry;77 Boulevard De Clichy;75009;Paris 09;48.883866;2.3288165 +0640047D;Lycée General Jules Supervielle;Boulevard F.mitterrand;64404;Oloron Ste Marie;43.184366;-0.621821 +0440022K;Lycée General Jules Verne;1 Rue General Meusnier;44042;Nantes;47.2173915;-1.5617182 +0670080Y;Lycée General Kleber;25 Place De Bordeaux;67082;Strasbourg;48.5950246;7.7579489 +0920143F;Lycée General La Folie Saint James;41 Rue De Longchamp;92200;Neuilly Sur Seine;48.8825303;2.2557562 +0510053F;Lycée General La Fontaine Du Ve;Avenue De La Fontaine Du Ve;51122;Sezanne;48.726156;3.7170255 +0880030E;Lycée General La Haie Griselle;Boite Postale 109;88407;Gerardmer;48.070081;6.877292 +0010013J;Lycée General Lalande;16 Rue Du Lycée;01011;Bourg En Bresse;0.0;0.0 +0750670W;Lycée General Lamartine;121 Rue Du Fg Poissonniere;75009;Paris 09;48.8784359;2.3490775 +0681761V;Lycée General Lambert;73 Rue Josue Heilmann;68069;Mulhouse;47.7546303;7.3302166 +0810006S;Lycée General Laperouse;2 Lices Georges Pompidou;81030;Albi;43.9302181;2.1472285 +0810030T;Lycée General Las Cases;Place De La Resistance;81500;Lavaur;43.698135;1.8209846 +0620017G;Lycée General Lavoisier;99 Rue Jean Jaures;62260;Auchel;50.5075071;2.4754481 +0750656F;Lycée General Lavoisier;17 Rue Henri Barbusse;75005;Paris 05;48.8423289;2.3392448 +0500065Z;Lycée General Le Verrier;7 Rue Le Verrier;50002;St Lo;49.114593;-1.085313 +0460013P;Lycée General Leo Ferre;75 Avenue Cavaignac;46300;Gourdon;44.7341503;1.3771929 +0510015P;Lycée General Leon Bourgeois;29 Avenue De Champagne;51331;Epernay;49.043149;3.9594547 +0870016V;Lycée General Leonard Limosin;13 Rue Des Clairettes;87036;Limoges;45.8296501;1.2542492 +0640055M;Lycée General Louis Barthou;2 Rue Louis Barthou;64015;Pau;43.2946775;-0.3652763 +0750655E;Lycée General Louis Le Grand;123 Rue Saint Jacques;75005;Paris 05;48.8479761;2.3441552 +0920142E;Lycée General Louis Pasteur;17 A 21 Bd D Inkermann;92200;Neuilly Sur Seine;48.8858222;2.2741732 +0800009A;Lycée General Louis Thuillier;70 Boulevard De Saint Quentin;80098;Amiens;49.8791607;2.2965051 +9840001D;Lycée General Lycée D'uturoa;Uturoa;98735;Uturoa;-16.7331706;-151.4403326 +9840002E;Lycée General Lycée Paul Gauguin;Papeete;98714;Papeete;-17.535022;-149.569594 +0030025L;Lycée General Madame De Stael;1 Rue Madame De Stael;03100;Montlucon;46.3388313;2.6097306 +0800010B;Lycée General Madeleine Michelis;43 Rue Des Otages;80037;Amiens;49.8890443;2.3029357 +0280007F;Lycée General Marceau;2 Rue Pierre Mendes France;28000;Chartres;48.4414461;1.4951576 +0140061R;Lycée General Marcel Gambier;3 Rue General Leclerc;14107;Lisieux;49.1496609;0.2243572 +0670083B;Lycée General Marie Curie;7 Rue De Leicester;67084;Strasbourg;48.5777746;7.7720751 +0920146J;Lycée General Marie Curie;1 Rue Constant Pilate;92331;Sceaux;48.7778987;2.2870922 +0060030A;Lycée General Massena;2 Avenue Felix Faure;06050;Nice;43.6991436;7.2762535 +0640065Y;Lycée General Maurice Ravel;Av Du Pr Maranon;64502;St Jean De Luz;43.388051;-1.663055 +0590181B;Lycée General Maxence Van Der Meersch;1 Av Maxence Van Der Meersch;59052;Roubaix;50.7014845;3.238885 +0330021U;Lycée General Michel Montaigne;118 Cours Victor Hugo;33075;Bordeaux;44.834861;-0.5718881 +0130045D;Lycée General Michelet;21 Avenue Marechal Foch;13248;Marseille 04;43.3009814;5.3993878 +0650012K;Lycée General Michelet;148 Rue Michelet;65303;Lannemezan;43.1267858;0.3861872 +0820020B;Lycée General Michelet;22 Faubourg Lacapelle;82004;Montauban;44.0133311;1.3611955 +0750703G;Lycée General Moliere;71 Rue Du Ranelagh;75016;Paris 16;48.8550931;2.272105 +0750657G;Lycée General Montaigne;17 Rue Auguste Comte;75006;Paris 06;48.8442327;2.3349031 +0330022V;Lycée General Montesquieu;4 Et 5 Place De Longchamps;33081;Bordeaux;48.7535502;0.0030397 +0720029R;Lycée General Montesquieu;1 Rue Montesquieu;72008;Le Mans;48.0099285;0.2002142 +0130010R;Lycée General Montmajour;Che Des Moines Qua Du Trebon;13200;Arles;43.6017739;4.6243751 +0332724G;Lycée General Nord Bassin;128 Avenue De Bordeaux;33510;Andernos Les Bains;44.7518058;-1.0797311 +0750689S;Lycée General Paul Bert;7 Rue Huyghens;75014;Paris 14;48.8408655;2.328794 +0640046C;Lycée General Paul Rey;6 Avenue Jean Seigneres;64800;Nay;43.1810505;-0.2540108 +0340075X;Lycée General Paul Valery;55 Rue Paul Valery;34200;Sete;43.4013903;3.6930027 +0750679F;Lycée General Paul Valery;38 Boulevard Soult;75012;Paris 12;48.8391381;2.4089398 +0442334Y;Lycée General Pierre Abelard;Chemin Du Rouaud;44330;Vallet;47.1685954;-1.2643431 +0510006E;Lycée General Pierre Bayen;22 Rue Du Lycée;51037;Chalons En Champagne;0.0;0.0 +0312696M;Lycée General Pierre Bourdieu;Avenue De Villaudric;31620;Fronton;43.834408;1.401836 +0760090K;Lycée General Pierre Corneille;4 Rue Du Maulevrier;76044;Rouen;49.4453249;1.1001636 +0310036W;Lycée General Pierre De Fermat;Parvis Des Jacobins;31068;Toulouse;43.604652;1.444209 +0650038N;Lycée General Pierre Mendes France;Rue Du College;65501;Vic En Bigorre;43.388411;0.056435 +0710011B;Lycée General Pontus De Tyard;13 Rue Des Gaillardons;71321;Chalon Sur Saone;46.7792939;4.8365859 +0596122J;Lycée General Privé Active Bilingue Jeannine Manue;418 Bis Rue Albert Bailly;59700;Marcq En Baroeul;50.6905276;3.1198309 +0940880W;Lycée General Privé Albert De Mun;12-14 Av Des Marronniers;94736;Nogent Sur Marne;48.8371754;2.470904 +0331491S;Lycée General Privé Albert Le Grand;189 Rue De Saint Genes;33000;Bordeaux;44.8239861;-0.5819519 +0931813R;Lycée General Privé Alliance;35 Allee Robert Estienne;93320;Les Pavillons Sous Bois;48.9066801;2.5114479 +0133555U;Lycée General Privé Ami;47 Rue St Suffren;13006;Marseille 06;43.2871912;5.3813577 +0311132M;Lycée General Privé Annonciation;7 Chemin De Percin;31840;Seilh;43.678854;1.3673649 +0350778F;Lycée General Privé Assomption;18 Boulevard Painleve;35702;Rennes;48.1217772;-1.6560228 +0772648K;Lycée General Privé Assomption;2 Rue De Salins;77130;Forges;48.418206;2.960877 +0810071M;Lycée General Privé Barral;113 Rue Marcel Briguiboul;81100;Castres;43.593354;2.2327115 +0941909P;Lycée General Privé Bernard Palissy;2 Rue Merciere;94470;Boissy St Leger;48.7508613;2.5140211 +0754325U;Lycée General Privé Beth Hanna;49 Rue Petit;75019;Paris 19;48.8855041;2.3853435 +0910838S;Lycée General Privé Beth Rivkah;43 49 Rue Raymond Poincare;91330;Yerres;48.711413;2.5006397 +0753384W;Lycée General Privé Beth Yacov;50 Bis Rue Des Prairies;75020;Paris 20;48.8634435;2.401922 +0610700E;Lycée General Privé Bignon;3 Rue De La Comedie;61400;Mortagne Au Perche;48.5222373;0.5449647 +0440154D;Lycée General Privé Blanche De Castille;43-45 Boulevard Jules Verne;44319;Nantes;47.237147;-1.5333106 +0930974D;Lycée General Privé Blanche De Castille;1 Place Charles De Gaulle;93250;Villemomble;48.9012637;2.4777022 +0753915Y;Lycée General Privé Blomet;5 Rue Blomet;75015;Paris 15;48.8440336;2.3089779 +0753887T;Lycée General Privé Bossuet-notre-dame;17 Rue Yves Toudic;75010;Paris 10;48.8704201;2.3632866 +0622107D;Lycée General Privé Catho Haffreingue Chanlaire;67 Avenue Charles De Gaulle;62200;Boulogne Sur Mer;50.7302448;1.6189027 +0260064D;Lycée General Privé Chabrillan;Route De Dieulefit;26207;Montelimar;44.552049;4.6923919 +0511131C;Lycée General Privé Charles Peguy;2 Bis Allees Paul Doumer;51037;Chalons En Champagne;48.9525137;4.3710585 +0930962R;Lycée General Privé Charles Peguy;216 Avenue Henri Barbusse;93000;Bobigny;48.9116357;2.434482 +0690574Z;Lycée General Privé Chevreul;21 Rue Sala;69287;Lyon 02;45.7560734;4.8289023 +0131335F;Lycée General Privé Chevreul Blancarde;1 Rue Saint Francois De Sales;13248;Marseille 04;43.3017559;5.4016838 +0932110N;Lycée General Privé Chne Or;150 Rue Andre Karman;93300;Aubervilliers;48.9112912;2.3852031 +0680141J;Lycée General Privé College Episcopal;5 Rue Du Seminaire;68720;Zillisheim;47.7002658;7.3057379 +0671609K;Lycée General Privé College Episcopal St Etienne;2 Rue De La Pierre Large;67084;Strasbourg;48.583169;7.75531 +0131344R;Lycée General Privé Cours Bastide;50 Rue De Lodi;13006;Marseille 06;43.2897281;5.386952 +0141165R;Lycée General Privé Cours Notre-dame;Rue De L'arbalete;14440;Douvres La Delivrande;49.2954884;-0.3728552 +0440158H;Lycée General Privé D'amboise-chavagnes;11 Rue Mondesir;44015;Nantes;47.2177089;-1.5694414 +0160065J;Lycée General Privé De Beaulieu;23 Place Beaulieu;16105;Cognac;45.6944011;-0.3306255 +0753943D;Lycée General Privé De La Tour;86 Rue De La Tour;75016;Paris 16;48.8615462;2.2788627 +0690529A;Lycée General Privé Deborde;72 Rue Ney;69006;Lyon 06;45.7666327;4.8556717 +0271051W;Lycée General Privé Des Roches;Avenue Edmond Demolins;27137;Verneuil Sur Avre;48.7386465;0.911939 +0753848A;Lycée General Privé D'hulst;21 Rue De Varenne;75007;Paris 07;48.8536447;2.3250815 +0292137R;Lycée General Privé Diwan;Kerampuil;29270;Carhaix Plouguer;48.273696;-3.552728 +0530052U;Lycée General Privé Don Bosco;18 Bd Anatole France;53102;Mayenne;48.295993;-0.6158636 +0680134B;Lycée General Privé Don Bosco;1 Rue Don Bosco;68440;Landser;47.6849042;7.3871869 +0331498Z;Lycée General Privé Du Mirail;36 Rue Du Mirail;33000;Bordeaux;44.8333866;-0.5710599 +0070071P;Lycée General Privé Du Sacre Coeur;7 Avenue De La Gare;07301;Tournon Sur Rhone;45.0637796;4.834587 +0752937K;Lycée General Privé Ecole Active Bilingue Etoile;24 Bis Rue De Berri;75008;Paris 08;48.8732062;2.3056124 +0753874D;Lycée General Privé Ecole Active Bilingue J.manuel;70 Rue Du Theatre;75015;Paris 15;48.847462;2.291572 +0753647G;Lycée General Privé Ecole Alsacienne;109 R Notre Dame Des Champs;75006;Paris 06;48.8409608;2.3336956 +0671615S;Lycée General Privé Ecole Aquiba;4 Rue Baldung Grien;67000;Strasbourg;48.5899787;7.7529847 +0753883N;Lycée General Privé Edgar Poe;2 R Du Faubourg Poissonniere;75010;Paris 10;48.8709741;2.3480187 +0930961P;Lycée General Privé Esperance;35 Rue Anatole France;93600;Aulnay Sous Bois;48.935783;2.4938629 +0290336H;Lycée General Privé Estran Charles De Foucauld;32 Rue De Quimper;29287;Brest;48.4021619;-4.4602772 +0690533E;Lycée General Privé Externat De La Trinite;31 Rue De Seze;69006;Lyon 06;45.7682665;4.8465112 +0440160K;Lycée General Privé Externat Enfants Nantais;31 Avenue Camus;44042;Nantes;47.2189478;-1.5726768 +0381803D;Lycée General Privé Externat Notre Dame;43 Avenue Marcellin Berthelot;38100;Grenoble;45.1737452;5.731904 +0421021G;Lycée General Privé Externat Saint Michel;4 Rue Jules Valles;42030;St Etienne;45.4315189;4.3977734 +0631074A;Lycée General Privé Fenelon;1 Cours Raymond Poincare;63037;Clermont Ferrand;45.7709705;3.0900322 +0830099G;Lycée General Privé Fenelon;251 Rue Pourquoi Pas;83000;Toulon;43.1097419;5.9519764 +0931799A;Lycée General Privé Fenelon;1 Rue De Montauban;93410;Vaujours;48.9301569;2.5695529 +0753873C;Lycée General Privé Fenelon Sainte-marie;24 Rue Du General Foy;75008;Paris 08;48.8784763;2.3179146 +0941693E;Lycée General Privé Foyer Des Ptt;36 Avenue Du President Wilson;94234;Cachan;48.7922449;2.3291571 +0753935V;Lycée General Privé Georges Leven;30 Boulevard Carnot;75012;Paris 12;48.8424354;2.4129184 +0753941B;Lycée General Privé Gerson;31 Rue De La Pompe;75116;Paris 16;48.8608035;2.2751876 +0771720B;Lycée General Privé Guy Gasnier-sainte Bathilde;28 Rue Du Tir;77500;Chelles;48.8893418;2.5961092 +0755025E;Lycée General Privé Heikhal Menahem Sinai;110 Boulevard De Menilmontant;75020;Paris 20;48.8655642;2.384824 +0930965U;Lycée General Privé Henri Matisse;88 Bis Rue Jules Guesde;93100;Montreuil;48.8627932;2.4792419 +0120052S;Lycée General Privé Immaculee Conception;Rue Auzuech;12500;Espalion;44.525222;2.769631 +0690554C;Lycée General Privé Immaculee Conception;74 Place Grandclement;69613;Villeurbanne;45.7591491;4.8897359 +0840074Z;Lycée General Privé Immaculee Conception;18 Rue Des Marins;84208;Carpentras;44.0539842;5.0490871 +0596335R;Lycée General Privé Institut D Anchin;Abbaye D'anchin;59146;Pecquencourt;50.375524;3.212056 +0690564N;Lycée General Privé Institution Des Chartreux;58 Rue Pierre Dupont;69283;Lyon 01;45.7718191;4.8226139 +0141169V;Lycée General Privé Institution Fremont;12 Rue Paul Banaston;14100;Lisieux;49.1476683;0.2237108 +0141157G;Lycée General Privé Institution Jeanne D'arc;10 Rue D'eterville;14403;Bayeux;49.2809816;-0.7065406 +6200650M;Lycée General Privé Institution Saint-paul;Avenue Marechal Lyautey;20186;Ajaccio;41.9379483;8.7447941 +0671552Y;Lycée General Privé Jan Amos Comenius;8 Place Des Etudiants;67060;Strasbourg;48.5842395;7.7489578 +0511130B;Lycée General Privé Jean Xxiii;18 Rue Andrieux;51064;Reims;49.2592782;4.0360628 +0572341K;Lycée General Privé Jean Xxiii;10 R Monseigneur Heintz;57958;Montigny Les Metz;49.0988857;6.1588903 +0110047S;Lycée General Privé Jeanne D Arc;47 Rue De La Baffe;11400;Castelnaudary;43.3159746;1.957673 +0640127R;Lycée General Privé Jeanne D Arc;47 Rue Moncade;64300;Orthez;43.4903576;-0.7717527 +0141161L;Lycée General Privé Jeanne D'arc;27 Rue Claude Chappe;14000;Caen;49.1815244;-0.395588 +0381670J;Lycée General Privé Jeanne D'arc;2 Rue Poincare;38550;Le Peage De Roussillon;45.3678002;4.7966512 +0490822C;Lycée General Privé Jeanne D'arc;3 Rue Joubert;49103;Angers;47.4725034;-0.5449917 +0650062P;Lycée General Privé Jeanne D'arc;17 Rue Massey;65000;Tarbes;43.2347802;0.0738099 +0730760E;Lycée General Privé Jeanne D'arc;3 Place De L'eglise;73203;Albertville;45.6780169;6.3908431 +0771232W;Lycée General Privé Jeanne D'arc Saint Aspais;18 Boulevard Andre Maginot;77300;Fontainebleau;48.4057991;2.6893858 +0260062B;Lycée General Privé Jeunes Filles;77 Rue Geoffroy De Moirans;26330;Chateauneuf De Galaure;45.2345255;4.9553014 +0693769X;Lycée General Privé Juif De Lyon;40 Rue Alexandre Boutin;69100;Villeurbanne;45.768352;4.868835 +0592935V;Lycée General Privé La Croix Blanche;2 Rue De L'abbe Six;59588;Bondues;50.7198225;3.1094008 +0622116N;Lycée General Privé La Malassise;Pension Saint Joseph;62968;Longuenesse;50.736744;2.243632 +0541986R;Lycée General Privé La Malgrange;Avenue De La Malgrange;54140;Jarville La Malgrange;48.669525;6.199726 +0442226F;Lycée General Privé La Mennais;24 Rue Jean Baptiste Legeay;44350;Guerande;47.3216018;-2.4317213 +0570201J;Lycée General Privé La Misericorde;11 Rue Des Recollets;57000;Metz;49.1206035;6.1806771 +0440172Y;Lycée General Privé La Perverie Sacre Coeur;63 Rue De La Perverie;44322;Nantes;47.2408522;-1.5621761 +0753851D;Lycée General Privé La Rochefoucauld;90 Bis Rue Saint Dominique;75007;Paris 07;48.8594019;2.3057007 +0331504F;Lycée General Privé La Sauque;Le Bourg;33650;La Brede;44.6707635;-0.4790839 +0693587Z;Lycée General Privé La Xaviere;252 Route De Vienne;69008;Lyon 08;45.7282439;4.8537692 +0131324U;Lycée General Privé Lacordaire;7 Boulevard Lacordaire;13013;Marseille 13;43.3197549;5.4057658 +0753849B;Lycée General Privé L'alma;12 Avenue Bosquet;75007;Paris 07;48.8602799;2.3023934 +0753948J;Lycée General Privé L'assomption;6 Rue De Lubeck;75116;Paris 16;48.8666108;2.2940415 +0311126F;Lycée General Privé Le Ferradou;Route De Grenade;31700;Blagnac;43.6401295;1.3886897 +0850118S;Lycée General Privé L'esperance;Bourdevaire;85110;Ste Cecile;46.711412;-1.113992 +0771228S;Lycée General Privé Libre De Juilly;7 Rue Barre;77230;Juilly;49.0111848;2.7045603 +0131345S;Lycée General Privé L'olivier - Robert Coffy;29 Avenue Des Caillols;13012;Marseille 12;43.3039572;5.4277833 +0753827C;Lycée General Privé Louise De Marillac;32 R Geoffroy Saint Hilaire;75005;Paris 05;48.8408725;2.3561278 +0080081V;Lycée General Privé Mabillon;23 Av Martyrs De La Resistance;08201;Sedan;49.7000092;4.930206 +0610702G;Lycée General Privé Marie Immaculee;6 Rue Charles Forget;61500;Sees;48.6067391;0.1716333 +0141172Y;Lycée General Privé Marie-joseph;Avenue De La Marniere;14360;Trouville Sur Mer;49.3710312;0.1146815 +0631847R;Lycée General Privé Massillon;5 Rue Bansac;63037;Clermont Ferrand;45.7786092;3.091566 +0753824Z;Lycée General Privé Massillon;2 Bis Quai Des Celestins;75004;Paris 04;48.851529;2.3607945 +0131328Y;Lycée General Privé Melizan;55 Route Des Camoins;13011;Marseille 11;43.2971335;5.4945421 +0932303Y;Lycée General Privé Merkaz Hatorah Filles;67 Boulevard Du Midi;93340;Le Raincy;48.8945151;2.5251787 +0931464L;Lycée General Privé Merkaz Hatorah Garcons;92-94 Chemin Des Bourdons;93220;Gagny;48.8909288;2.5225507 +0631076C;Lycée General Privé Monanges;7 Rue De Metz;63000;Clermont Ferrand;45.7807747;3.0980538 +0060668U;Lycée General Privé Mont Saint Jean;Avenue Du Chataignier;06600;Antibes;43.5809119;7.1129044 +0940881X;Lycée General Privé Montalembert;28 Boulevard Gambetta;94736;Nogent Sur Marne;48.838178;2.4713696 +0753823Y;Lycée General Privé Moria - Diane Benvenuti;9 11 Rue Lekain;75116;Paris 16;48.8571388;2.277618 +0131319N;Lycée General Privé Nativite (la);8 Rue Jean Andreani La Beauvale;13097;Aix En Provence;43.529742;5.447427 +0622118R;Lycée General Privé Nazareth;84 Rue De Maquetra;62280;St Martin Boulogne;50.7287576;1.6255495 +0440171X;Lycée General Privé Nd De L'abbaye;38 Bis Rue De L Abbaye;44100;Nantes;47.1998104;-1.5962954 +0440166S;Lycée General Privé Nd De Toutes Aides;33 Boulevard Louis Millet;44319;Nantes;47.22695;-1.519285 +0421660B;Lycée General Privé Nd De Valbenoite Le Rond Point;10 Place De L'abbaye;42030;St Etienne;45.4216919;4.3986281 +0440178E;Lycée General Privé Nd D'esperance;15 Rue Du Bois Savary;44615;St Nazaire;47.2747567;-2.2080565 +0490834R;Lycée General Privé Nd D'orveau;;49500;Nyoiseau;47.716305;-0.914938 +0211074D;Lycée General Privé Notre Dame;97 Rue De Talant;21000;Dijon;47.3310729;5.0153828 +0260069J;Lycée General Privé Notre Dame;91 Rue Montplaisir;26000;Valence;44.930458;4.9086288 +0331499A;Lycée General Privé Notre Dame;45 Rue Du Palais Gallien;33000;Bordeaux;44.843183;-0.5815713 +0421025L;Lycée General Privé Notre Dame;9 Rue Cacherat;42190;Charlieu;46.1614459;4.1732205 +0490837U;Lycée General Privé Notre Dame;;49310;La Salle De Vihiers;47.15538;-0.63641 +0720833P;Lycée General Privé Notre Dame;46 Rue De La Magdeleine;72205;La Fleche;47.7006579;-0.0802937 +0690540M;Lycée General Privé Notre Dame De Bellegarde;22 Avenue Gambetta;69250;Neuville Sur Saone;45.870459;4.8419155 +0640122K;Lycée General Privé Notre Dame De Betharram;1 Place Saint Michel;64800;Lestelle Betharram;43.125087;-0.212631 +0131333D;Lycée General Privé Notre Dame De France;132 Rue Breteuil;13253;Marseille 06;43.2846524;5.378104 +0290174G;Lycée General Privé Notre Dame De Kerbertrand;154 Rue De Pont Aven;29391;Quimperle;47.8710014;-3.5663631 +0940877T;Lycée General Privé Notre Dame De La Providence;7 Avenue Gabriel Peri;94307;Vincennes;48.8472148;2.4409415 +0690552A;Lycée General Privé Notre Dame De Mongre;276 Avenue Saint Exupery;69652;Villefranche Sur Saone;45.9909387;4.7116054 +0131341M;Lycée General Privé Notre Dame De Sion;231 Rue Paradis;13006;Marseille 06;43.2823556;5.3815876 +0690519P;Lycée General Privé Notre Dame Des Minimes;65 Rue Des Aqueducs;69322;Lyon 05;45.7594493;4.789366 +0941877E;Lycée General Privé Notre Dame Des Missions;4 Rue Du President Kennedy;94220;Charenton Le Pont;48.8223455;2.404544 +0541318P;Lycée General Privé Notre Dame Saint Sigisbert;19 21 Cours Leopold;54042;Nancy;48.695498;6.1750984 +0090036R;Lycée General Privé Notre-dame;3 Place Des Cordeliers;09100;Pamiers;43.1175009;1.6124603 +0593141U;Lycée General Privé Notre-dame;15 Rue Des Capucins;59308;Valenciennes;50.358955;3.5292575 +0753902J;Lycée General Privé Notre-dame De France;63 Rue De La Sante;75013;Paris 13;48.833057;2.3413569 +0592938Y;Lycée General Privé Notre-dame De Grace;31 Boulevard De La Liberte;59402;Cambrai;50.1705607;3.2302062 +0593109J;Lycée General Privé Notre-dame De La Paix;14 Place Du Concert;59009;Lille;50.6427151;3.0616162 +0622120T;Lycée General Privé Notre-dame De Sion;52 Rue Courteville;62501;St Omer;50.7540117;2.2558843 +0592913W;Lycée General Privé Notre-dame Des Anges;4 Rue Du Bruille;59733;St Amand Les Eaux;50.4487482;3.4257749 +0593102B;Lycée General Privé Notre-dame Des Dunes;60 Rue Du Sud;59140;Dunkerque;51.0341311;2.3836906 +0753946G;Lycée General Privé Notre-dame Des Oiseaux;12 Rue Michel Ange;75016;Paris 16;48.8468059;2.2630177 +0511135G;Lycée General Privé Notre-dame St Victor;13 Rue Jean Chandon Moet;51200;Epernay;49.0412476;3.9571882 +0931970L;Lycée General Privé Ohr Sarah;6 Rue Saint Louis;93500;Pantin;48.8914438;2.418068 +0312356T;Lycée General Privé Ozar Hatorah;33 Rue Jules Dalou;31500;Toulouse;43.6217711;1.4620342 +0754479L;Lycée General Privé Ozar Hatorah;34 Rue Du Moulin Joly;75011;Paris 11;48.8701778;2.3779337 +0941711Z;Lycée General Privé Ozar Hatorah Filles;65 Rue Saint Simon;94000;Creteil;48.7947133;2.4451292 +0941943B;Lycée General Privé Ozar Hatorah Garcons;2 Voie Felix Eboue;94000;Creteil;48.798567;2.4507301 +0753919C;Lycée General Privé Pascal;33 Boulevard Lannes;75116;Paris 16;48.8670712;2.2721096 +0753809H;Lycée General Privé Paul Claudel;118 120 Rue De Grenelle;75007;Paris 07;48.8570302;2.319517 +7200073S;Lycée General Privé Pensionnat Jeanne D Arc;15 Boulevard Benoite Danesi;20297;Bastia;42.6969548;9.4436937 +0940891H;Lycée General Privé Petit Val;12 Avenue Albert Pleuvry;94370;Sucy En Brie;48.7688364;2.5165907 +0650058K;Lycée General Privé Peyramale Saint-joseph;13 Avenue Du Marechal Joffre;65100;Lourdes;43.0948515;-0.0445345 +0690538K;Lycée General Privé Pierre Termier;23 Rue Des Alouettes;69008;Lyon 08;45.7414256;4.8733393 +0740097D;Lycée General Privé Presentation De Marie;10 Rue Monseigneur Paget;74163;St Julien En Genevois;46.1432999;6.0801747 +0596281G;Lycée General Privé Privé De Genech;Rue De La Liberation;59242;Genech;50.5300906;3.2117313 +0261395A;Lycée General Privé Protestant Du Cedre;La Cloitre;26750;Montmiral;45.1702732;5.1414735 +0131323T;Lycée General Privé Provence (de);42 Boulevard Emile Sicard;13272;Marseille 08;43.2702033;5.3844699 +0753885R;Lycée General Privé Rocroy Saint-leon;106 R Du Faubourg Poissonniere;75010;Paris 10;48.8786454;2.3492599 +0911844K;Lycée General Privé Rudolf Steiner;62 Rue De Paris;91370;Verrieres Le Buisson;48.7391608;2.2441374 +0593139S;Lycée General Privé Sacre Coeur;111 Rue De Lille;59334;Tourcoing;50.7229;3.149171 +0511142P;Lycée General Privé Sacre-coeur;86 Rue De Courlancy;51096;Reims;49.2407341;4.0270779 +0592944E;Lycée General Privé Saint Adrien;15 Rue St Jb De La Salle;59653;Villeneuve D Ascq;50.6288481;3.1487012 +0940885B;Lycée General Privé Saint Andre;12 Avenue Leon Gourdault;94600;Choisy Le Roi;48.7634434;2.4057917 +0570211V;Lycée General Privé Saint Antoine;Chemin Des Dames;57370;Phalsbourg;48.7699551;7.2698303 +0570194B;Lycée General Privé Saint Augustin;56 Rue Jean Jacques Kieffer;57231;Bitche;49.044565;7.4272673 +0260063C;Lycée General Privé Saint Bonnet;Saint Bonnet;26330;Chateauneuf De Galaure;45.218168;4.940543 +0021870X;Lycée General Privé Saint Charles;1 Rue Du Brouage;02300;Chauny;49.6147218;3.2121038 +0541319R;Lycée General Privé Saint Dominique;11 Rue Du Manege;54004;Nancy;48.690925;6.1880469 +0602070Z;Lycée General Privé Saint Dominique;5,rue Gerard De Nerval;60128;Mortefontaine;49.1109074;2.6017471 +0301499S;Lycée General Privé Saint Felix;90 Chemin Des Marguilliers;30300;Beaucaire;43.818271;4.636691 +0740104L;Lycée General Privé Saint Francois;19 Rue Fernand David;74100;Ville La Grand;46.2049328;6.2495591 +0592940A;Lycée General Privé Saint Jean;246 Rue Saint Jean;59506;Douai;50.3694281;3.0865161 +0690550Y;Lycée General Privé Saint Joseph;7 Rue Lieutenant Audras;69160;Tassin La Demi Lune;45.7606452;4.7579506 +0740098E;Lycée General Privé Saint Joseph;St Martin Sur Arve;74703;Sallanches;45.912929;6.689997 +0740099F;Lycée General Privé Saint Joseph;Rue Bienheureux Pierre Favre;74230;Thones;45.8834922;6.3272261 +0541313J;Lycée General Privé Saint Leon Ix;20 Rue St Leon;54000;Nancy;48.6891381;6.172546 +0421020F;Lycée General Privé Saint Louis;22 Rue Desire Claude;42030;St Etienne;45.4290047;4.3889574 +0931812P;Lycée General Privé Saint Louis-sainte Clotilde;37 Allee De La Fontaine;93340;Le Raincy;48.8928994;2.5177604 +0690571W;Lycée General Privé Saint Marc;10 Rue Sainte Helene;69287;Lyon 02;45.7550807;4.8288541 +0260066F;Lycée General Privé Saint Maurice;Rue Eugene Blain;26106;Romans Sur Isere;45.046612;5.042497 +0370728R;Lycée General Privé Saint Medard Inst N-d La Riche;30 Rue Delperier;37058;Tours;47.3925342;0.6769264 +0593138R;Lycée General Privé Saint Michel;13 Rue Emile Zola;59730;Solesmes;50.1838289;3.5030972 +0940882Y;Lycée General Privé Saint Michel De Picpus;10 Ter Rue Jeanne D'arc;94165;St Mande;48.8403691;2.418619 +0421023J;Lycée General Privé Saint Paul;15 Rue Bourgneuf;42308;Roanne;46.038829;4.0705744 +0421035X;Lycée General Privé Saint Paul;11 Rue De La Paix;42016;St Etienne;45.4392601;4.385133 +0593114P;Lycée General Privé Saint Paul;25 Bis Rue Colbert;59000;Lille;50.6278376;3.0477018 +0450108Y;Lycée General Privé Saint Paul-bourdon Blanc;4 Rue Neuve St Aignan;45057;Orleans;47.8984756;1.9160429 +0593117T;Lycée General Privé Saint Pierre;18 Rue Saint J-b De La Salle;59000;Lille;50.6284889;3.0392123 +0631032E;Lycée General Privé Saint Pierre;;63120;Courpiere;45.754178;3.538632 +0570216A;Lycée General Privé Saint Pierre Chanel;33 Rue Du Chardon;57109;Thionville;49.3635917;6.1516523 +0541312H;Lycée General Privé Saint Pierre Fourier;14 Rue Des Benedictins;54300;Luneville;48.5874967;6.4975357 +0300083C;Lycée General Privé Saint Stanislas;16 Rue Des Chassaintes;30900;Nimes;43.8371085;4.3525051 +0622106C;Lycée General Privé Saint Vaast-saint Dominique;92 Rue De L'universite;62401;Bethune;50.5243179;2.6481843 +0260071L;Lycée General Privé Saint Victor;3 Rue De La Cecile;26000;Valence;44.9228247;4.8883094 +0381660Y;Lycée General Privé Sainte Cecile;Avenue Marechal Foch;38260;La Cote St Andre;45.3900283;5.2511309 +0772602K;Lycée General Privé Sainte Celine;29 Rue Pierre Marx;77262;La Ferte Sous Jouarre;48.9494327;3.1265198 +0570214Y;Lycée General Privé Sainte Chretienne;20 Rue Ste Croix;57204;Sarreguemines;49.111329;7.0675702 +0593113N;Lycée General Privé Sainte Claire;8 Rue Des Augustins;59800;Lille;50.6343712;3.0691758 +0772290W;Lycée General Privé Sainte Croix;1 Rue Des Jacobins;77160;Provins;48.5629412;3.2939093 +0741443S;Lycée General Privé Sainte Croix Des Neiges;Chef Lieu;74360;Abondance;46.298615;6.789512 +0410676M;Lycée General Privé Sainte Marie;33 Rue Du Bourgneuf;41013;Blois;47.5902427;1.3309065 +0592932S;Lycée General Privé Sainte Marie;31 Rue De L'eglise;59134;Beaucamps Ligny;50.6028831;2.9202882 +0631034G;Lycée General Privé Sainte Marie;3 Place Marinette Menut;63203;Riom;45.8932067;3.1086693 +0771247M;Lycée General Privé Sainte Marie;41 Rue De Chaage;77109;Meaux;48.9658178;2.8806747 +0830095C;Lycée General Privé Sainte Marie;1 Place Germain Loro;83501;La Seyne Sur Mer;43.1015838;5.8770157 +0421022H;Lycée General Privé Sainte Marie La Grand'grange;15 Route Du Coin;42401;St Chamond;45.4714856;4.5209864 +0593106F;Lycée General Privé Sainte Odile;244 Avenue De Dunkerque;59130;Lambersart;50.6368821;3.0253351 +0753916Z;Lycée General Privé Sainte-elisabeth;112 Rue De Lourmel;75015;Paris 15;48.8428294;2.2860109 +0753947H;Lycée General Privé Saint-jean De Passy;72 Rue Raynouard;75016;Paris 16;48.8544406;2.2785455 +0320054K;Lycée General Privé Saint-joseph;1 Rue De L'abbe Tournier;32700;Lectoure;43.933604;0.623621 +0810075S;Lycée General Privé Saint-joseph De L'apparition;10 Boulevard Gambetta;81600;Gaillac;43.8992922;1.8986894 +0753880K;Lycée General Privé Saint-louis;50 Rue De Clichy;75009;Paris 09;48.8802553;2.3290393 +0753933T;Lycée General Privé Saint-louis De Gonzague;12 Rue Benjamin Franklin;75116;Paris 16;48.8595496;2.2854729 +0660768B;Lycée General Privé Saint-louis-de-gonzague;71 Av Du Docteur Schweitzer;66043;Perpignan;42.7182794;2.8790947 +0753897D;Lycée General Privé Saint-michel De Picpus;53 Rue De La Gare De Reuilly;75012;Paris 12;48.8422799;2.3964787 +0753959W;Lycée General Privé Saint-michel Des Batignolles;35 Avenue De Saint Ouen;75017;Paris 17;48.8897216;2.3263756 +0753898E;Lycée General Privé Saint-pierre Fourier;13 Rue De Prague;75012;Paris 12;48.84972;2.3757534 +0753838P;Lycée General Privé Saint-sulpice;68 Rue D'assas;75006;Paris 06;48.8455323;2.3316045 +0820046E;Lycée General Privé Saint-theodard;12 Quai De Verdun;82008;Montauban;44.0217919;1.3480475 +0753850C;Lycée General Privé Saint-thomas D'aquin;44 Rue De Grenelle;75007;Paris 07;48.8541469;2.3267057 +0671618V;Lycée General Privé Seminaire De Jeunes;60 Grand'rue;67360;Walbourg;48.8873256;7.7852027 +0131348V;Lycée General Privé Sevigne;1 Avenue Saint Jerome;13388;Marseille 13;43.3289588;5.4188772 +0500119H;Lycée General Privé Sevigne;15 Bd Girard Desprairies;50400;Granville;48.840647;-1.5908235 +0753598D;Lycée General Privé Sevigne;28 Rue Pierre Nicole;75005;Paris 05;48.8408072;2.3394515 +0753231E;Lycée General Privé Sinai;2-6 Rue Tristan Tzara;75018;Paris 18;48.8946459;2.3657946 +0100047X;Lycée General Privé St Bernard;8 Rue Du Palais De Justice;10041;Troyes;48.296588;4.0706044 +0220110A;Lycée General Privé St Charles;2 Rue Cordiere;22021;St Brieuc;48.5101209;-2.7676223 +0131342N;Lycée General Privé St Charles Camas;21 Rue Du Camas;13392;Marseille 05;43.2984501;5.3926775 +0440176C;Lycée General Privé St Dominique;103 Avenue Cheverny;44807;St Herblain;47.2439329;-1.6042189 +0331488N;Lycée General Privé St Elme;50 Boulevard Deganne;33120;Arcachon;44.6554765;-1.1525843 +0290198H;Lycée General Privé St Esprit;3 Rue Emile Souvestre;29403;Landivisiau;48.5121112;-4.0797023 +0440167T;Lycée General Privé St Felix;27 Rue Du Ballet;44001;Nantes;47.2316606;-1.560045 +0640124M;Lycée General Privé St Francois;1 Rue De Belzunce;64130;Mauleon Licharre;43.2233966;-0.8850869 +0100046W;Lycée General Privé St Francois De Sales;11 Rue General Saussier;10000;Troyes;48.2955822;4.0742386 +0290160S;Lycée General Privé St Francois-notre Dame;1 Rue Des Recollets;29260;Lesneven;48.5708202;-4.3245065 +0560114V;Lycée General Privé St Francois-xavier;3 Rue Thiers;56000;Vannes;47.6538433;-2.7595614 +0050035L;Lycée General Privé St Joseph;2 Rue Des Pins;05000;Gap;44.5629426;6.0769717 +0352341E;Lycée General Privé St Joseph;3 Av. Alphonse Legault;35171;Bruz;48.0262162;-1.7463371 +0490835S;Lycée General Privé St Joseph;50 Rue De La Loire;49620;La Pommeraye;47.3601893;-0.8600084 +0511140M;Lycée General Privé St Joseph;177 Rue Des Capucins;51095;Reims;49.2475072;4.0338348 +0640125N;Lycée General Privé St Joseph;Avenue Des Abbes Dupont;64800;Nay;43.1720298;-0.2670033 +0640126P;Lycée General Privé St Joseph;1 Rue Palou;64400;Oloron Ste Marie;43.188782;-0.6093897 +0840078D;Lycée General Privé St Joseph;105 Rue Duplessis;84205;Carpentras;44.0524315;5.0457201 +0850083D;Lycée General Privé St Joseph;40 Rue Victor Hugo;85035;La Roche Sur Yon;46.6732361;-1.426608 +0440161L;Lycée General Privé St Joseph Du Loquidy;73 Boulevard Michelet;44322;Nantes;47.2388267;-1.5566766 +0440177D;Lycée General Privé St Louis;10 Boulevard Albert 1er;44616;St Nazaire;47.2681941;-2.217917 +0840075A;Lycée General Privé St Louis;Colline St Eutrope;84100;Orange;44.132831;4.808196 +0640115C;Lycée General Privé St Louis Villa Pia;Avenue Marechal Soult;64100;Bayonne;43.4856105;-1.4920018 +0352471W;Lycée General Privé St Magloire;2 Rue Du Chanoine Boursier;35120;Dol De Bretagne;48.5518844;-1.7542833 +0490824E;Lycée General Privé St Martin;5 Cloitre Saint Martin;49100;Angers;47.491878;-0.5478767 +0720842Z;Lycée General Privé St Michel Des Perrais;;72330;Parigne Le Polin;47.8496499;0.10935 +0721549T;Lycée General Privé St Paul-notre Dame;54 Av De La Republique;72406;La Ferte Bernard;48.1880106;0.646915 +0910812N;Lycée General Privé St Pierre;70 Rue De Montgeron;91800;Brunoy;48.6945366;2.4899797 +0080082W;Lycée General Privé St Remi;6 Place Winston Churchill;08000;Charleville Mezieres;49.7718959;4.7217776 +0440163N;Lycée General Privé St Stanislas;2 Rue Saint Stanislas;44009;Nantes;47.2222759;-1.5552563 +0131343P;Lycée General Privé St Thomas D Aquin;23 Rue Dieude;13006;Marseille 06;43.2923588;5.3816291 +0350776D;Lycée General Privé St Vincent-providence;57 Rue De Paris;35064;Rennes;48.1126265;-1.665047 +0060729K;Lycée General Privé Stanislas;25 Av Bieckert;06008;Nice;43.7082584;7.2717928 +0753840S;Lycée General Privé Stanislas;22 R Notre Dame Des Champs;75006;Paris 06;48.8457593;2.3277363 +0831551K;Lycée General Privé Stanislas;2431 Bd Pierre Delli Zotti;83700;St Raphael;43.4356915;6.7948685 +0290338K;Lycée General Privé Ste Anne;20 Rue Lamotte-picquet;29220;Brest;48.390394;-4.486076 +0470065R;Lycée General Privé Ste Catherine;2 Chemin De Velours;47300;Villeneuve Sur Lot;44.4033113;0.7143613 +0131320P;Lycée General Privé Ste Catherine De Sienne;20 Rue Mignet;13100;Aix En Provence;43.5304783;5.4508604 +0450757D;Lycée General Privé Ste Croix St Euverte;28 Rue De L Etelon;45043;Orleans;47.9025554;1.9171035 +0290194D;Lycée General Privé Ste Elisabeth;16 Rue Lamennais;29171;Douarnenez;48.0904856;-4.3274811 +0490831M;Lycée General Privé Ste Marie;43 Rue St Bonaventure;49307;Cholet;47.0594586;-0.8778263 +0331503E;Lycée General Privé Ste Marie Grand Lebrun;164 Av Charles De Gaulle;33021;Bordeaux;44.8502305;-0.6056323 +0131347U;Lycée General Privé Ste Trinite;55 Av Lattre Tassigny Mazargue;13009;Marseille 09;43.2465459;5.4082558 +0754081D;Lycée General Privé Ste Ursule-louise Bettignies;25 Rue Daubigny;75017;Paris 17;48.8858511;2.3075599 +0730759D;Lycée General Privé Talmudique;50 Montee De La Reine Victoria;73105;Aix Les Bains;45.6922535;5.8982884 +0754860A;Lycée General Privé Therese Chappuis;52 Rue Vaneau;75007;Paris 07;48.8510132;2.3191724 +0731040J;Lycée General Privé Tomer Debora;9 Chemin De Saint Pol;73100;Aix Les Bains;45.6854748;5.9200229 +0131327X;Lycée General Privé Tour Sainte (de);Av De Tour Sainte Ste Marthe;13014;Marseille 14;43.3450548;5.3873213 +0490823D;Lycée General Privé Urbain Mongazon;1 Rue Du Colombier;49036;Angers;47.4592971;-0.5294038 +0131360H;Lycée General Privé Viala Lacoste;16 Avenue Gaston Cabrier;13300;Salon De Provence;43.6407537;5.1006531 +0753834K;Lycée General Privé Yabne;29 Avenue Leon Bollee;75013;Paris 13;48.8179276;2.3624561 +0132472S;Lycée General Privé Yavne;44 Bd Barry St Just;13013;Marseille 13;43.3178816;5.4096639 +0330115W;Lycée General Reclus;7 Avenue De Verdun;33220;Ste Foy La Grande;44.8382054;0.2152357 +0640010N;Lycée General Rene Cassin;2 Rue Lasseguette;64100;Bayonne;43.4809394;-1.4805897 +0750682J;Lycée General Rodin;19 Rue Corvisart;75013;Paris 13;48.8325019;2.3470622 +0360002G;Lycée General Rollinat;Rue Du Lycée;36200;Argenton Sur Creuse;0.0;0.0 +0940115P;Lycée General Romain Rolland;17 Rue Lucien Nadaire;94200;Ivry Sur Seine;48.8015275;2.3935972 +0694026B;Lycée General Rosa Parks;13 Rue Pollet;69582;Neuville Sur Saone;45.8763307;4.8483431 +0731507S;Lycée General Saint Exupery;107 Rue Du Pre De Foire;73704;Bourg St Maurice;45.6188058;6.7651855 +0400046H;Lycée General Saint-exupery;Av Du Lycée Cite Scolaire;40160;Parentis En Born;0.0;0.0 +0750658H;Lycée General Saint-louis;44 Boulevard Saint Michel;75006;Paris 06;48.848959;2.341577 +0310041B;Lycée General Saint-sernin;3 Place St Sernin;31070;Toulouse;43.608488;1.440625 +0670081Z;Lycée General Sections Internationales;1 Rue Des Pontonniers;67081;Strasbourg;48.5840973;7.7564613 +0380028Z;Lycée General Stendhal;1 Bis Place Jean Achard;38816;Grenoble;45.189156;5.729899 +0470009E;Lycée General Stendhal;Allees Charles De Gaulle;47190;Aiguillon;44.300538;0.3372336 +0400933X;Lycée General Sud Des Landes;11 Voie Romaine;40231;St Vincent De Tyrosse;43.6667923;-1.2957191 +0332831Y;Lycée General Sud Medoc La Boetie;2 Rue Du Lycée;33320;Le Taillan Medoc;0.0;0.0 +0220099N;Lycée General Technologi Privé St Joseph;Rue Mouexigne;22404;Lamballe;48.4631626;-2.5132448 +0030036Y;Lycée General Theodore De Banville;12 Cours Vincent D'indy;03017;Moulins;46.5683421;3.3269656 +0650025Z;Lycée General Theophile Gautier;15 Rue Abbe Torne;65016;Tarbes;43.2337724;0.0718967 +0080053P;Lycée General Thomas Masaryk;35 Rue Bournizet;08400;Vouziers;49.3974951;4.6979019 +0730013T;Lycée General Vaugelas;;73006;Chambery;46.874236;1.586684 +0750662M;Lycée General Victor Duruy;33 Boulevard Des Invalides;75007;Paris 07;48.852408;2.3147672 +0142059M;Lycée General Victor Hugo;16 Rue Defense Passive;14070;Caen;49.1989716;-0.3668609 +0750648X;Lycée General Victor Hugo;27 Rue De Sevigne;75003;Paris 03;48.8572227;2.3629231 +0590143K;Lycée General Yves Kernanec;91 Avenue Du Docteur Calmette;59700;Marcq En Baroeul;50.6619541;3.0871451 +0580661D;Lycée Horticole Et Rural Privé Du Haut Nivernais;Chemin Des Plantes Froides;58210;Varzy;47.358735;3.388036 +0692681P;Lycée Horticole Privé De Lyon Pressin;81 Chemin De Beaunant;69230;St Genis Laval;45.7087787;4.7789349 +0593040J;Lycée Hotelier Notre-dame De La Providence;Rue Des Glycines;59358;Orchies;50.4806719;3.2420076 +0783441S;Lycée International Montessori;5 Rue De Chaponval;78870;Bailly;48.8359161;2.0710087 +0780015T;Lycée Militaire Militaire;2 Avenue Jean Jaures;78211;St Cyr L Ecole;48.7993005;2.0427068 +0711064W;Lycée Militaire National;3 Rue Gaston Joliet;71404;Autun;46.9497566;4.3057409 +0720896H;Lycée Militaire National Prytanee National Militaire;22 Rue Du College;72208;La Fleche;47.7003277;-0.0743896 +0930863H;Lycée Ministere Justice Maison De La Legion D'honneur;5 Rue De La Legion D'honneur;93206;St Denis;48.9344018;2.3581156 +0290063L;Lycée Naval;Avenue De L'ecole Navale;29240;Brest;48.3741391;-4.5258197 +0150747F;Lycée Polyvalent;Avenue Raymond Cortat;15200;Mauriac;45.2120206;2.3397724 +0772685A;Lycée Polyvalent;19 Rue Du Lion;77260;La Ferte Sous Jouarre;48.9433531;3.1318635 +0810023K;Lycée Polyvalent;41 Rue Victor Hugo;81604;Gaillac;43.9032896;1.9009418 +9720695Y;Lycée Polyvalent Acajou 2;Quartier Acajou;97232;Le Lamentin;0.0;0.0 +0941294W;Lycée Polyvalent Adolphe Cherioux;195 Rue Julian Grimau;94408;Vitry Sur Seine;48.7804644;2.3734255 +0781983G;Lycée Polyvalent Adrienne Bolland;62 64 Bd Devaux;78300;Poissy;48.9327605;2.0499354 +0693734J;Lycée Polyvalent Aiguerande;Chemin Des Sablons;69823;Belleville;46.0990944;4.7468894 +0320023B;Lycée Polyvalent Alain-fournier;Avenue Laplagne;32300;Mirande;43.517913;0.404015 +0831440P;Lycée Polyvalent Albert Camus;560 Rue Henri Giraud;83600;Frejus;43.443681;6.7538626 +0140056K;Lycée Polyvalent Albert Sorel;Avenue Du Labrador;14600;Honfleur;49.398803;0.236287 +0240021T;Lycée Polyvalent Alcide Dusolier;Avenue Jules Ferry;24300;Nontron;45.534132;0.666243 +0040027H;Lycée Polyvalent Alexandra David Neel;17 Avenue General Leclerc;04000;Digne Les Bains;43.0535129;2.2158732 +0330135T;Lycée Polyvalent Alfred Kastler;2 Avenue De L Universite;33402;Talence;44.802672;-0.599039 +0550072E;Lycée Polyvalent Alfred Kastler;1 Rue De Munnerstadt;55700;Stenay;49.487591;5.201371 +0911985N;Lycée Polyvalent Alfred Kastler;Chemin Du Champ De Course;91410;Dourdan;48.5346237;1.9966259 +0932026X;Lycée Polyvalent Alfred Nobel;130 Allee De Gagny;93390;Clichy Sous Bois;48.9014549;2.5470145 +0840021S;Lycée Polyvalent Alphonse Benoit;Cours Victor Hugo;84803;L Isle Sur La Sorgue;43.92001;5.0478131 +0681888H;Lycée Polyvalent Amelie Zurcher;30 Rue Jean Mermoz;68310;Wittelsheim;47.7978887;7.2389802 +0300052U;Lycée Polyvalent Andre Chamson;Boulevard Pasteur;30123;Le Vigan;43.9901973;3.6016316 +0040003G;Lycée Polyvalent Andre Honnorat;1 Rue Honnorat;04400;Barcelonnette;44.3881411;6.6525371 +0595884A;Lycée Polyvalent Andre Lurcat;113 Rue D'haumont;59602;Maubeuge;50.267331;3.944058 +0932123C;Lycée Polyvalent Andre Sabatier;140 Rue De La Republique;93000;Bobigny;48.9072448;2.4295277 +0740051D;Lycée Polyvalent Anna De Noailles;2 Avenue Anna De Noailles;74500;Evian Les Bains;46.3992246;6.5798893 +0210047M;Lycée Polyvalent Anna Judic;3 Rue Du Champ De Foire;21140;Semur En Auxois;47.4953913;4.3360813 +0830042V;Lycée Polyvalent Antoine De Saint Exupery;270 Avenue De Valescure;83700;St Raphael;43.4500551;6.8063128 +0940114N;Lycée Polyvalent Antoine De Saint Exupery;2-4 Rue Henri Matisse;94000;Creteil;48.7825757;2.460862 +0241125T;Lycée Polyvalent Antoine De St Exupery;Les Plantes;24122;Terrasson Lavilledieu;45.131328;1.281654 +0781948U;Lycée Polyvalent Antoine Lavoisier;40 A 60 Bd De La Republique;78440;Porcheville;48.9729155;1.7749472 +0660809W;Lycée Polyvalent Aristide Maillol;73 Avenue Pau Casals;66044;Perpignan;42.7173923;2.884448 +0951787B;Lycée Polyvalent Arthur Rimbaud;99 Av De La Div Leclerc;95140;Garges Les Gonesse;48.9697855;2.3963489 +0930933J;Lycée Polyvalent Assomption;12 Avenue De Verdun;93140;Bondy;48.9059128;2.4892334 +0070004S;Lycée Polyvalent Astier;Quartier Roqua;07205;Aubenas;44.620909;4.389863 +0850016F;Lycée Polyvalent Atlantique;5 Rue Jean Jaures;85402;Lucon;46.4541495;-1.1606375 +0930126G;Lycée Polyvalent Auguste Blanqui;54 Rue Charles Schmidt;93404;St Ouen;48.903085;2.3338269 +0131747D;Lycée Polyvalent Auguste Et Louis Lumiere;Avenue Jules Ferry;13600;La Ciotat;43.1835597;5.6019241 +0340002T;Lycée Polyvalent Auguste Loubatieres;Boulevard Des Hellenes;34304;Agde;43.299603;3.4826301 +0762975W;Lycée Polyvalent Auguste Perret;Place Robert Schuman;76600;Le Havre;49.5138762;0.1616656 +0700009E;Lycée Polyvalent Augustin Cournot;73 Grande Rue;70104;Gray;47.4462233;5.5896275 +9711046K;Lycée Polyvalent Baimbridge 2 (ex Caraibes);Boulevard Des Heros;97001;Les Abymes;16.2498118;-61.5230947 +9741270M;Lycée Polyvalent Bel Air;2 Rue Du Lycée;97441;Ste Suzanne;0.0;0.0 +0310038Y;Lycée Polyvalent Bellevue;135 Route De Narbonne;31031;Toulouse;43.567539;1.4578558 +0450051L;Lycée Polyvalent Benjamin Franklin;21 Bis Rue Eugene Vignat;45010;Orleans;47.9107742;1.9118459 +9730235T;Lycée Polyvalent Bertene Juminer;Route De Saint Maurice;97320;St Laurent Du Maroni;0.0;0.0 +0932048W;Lycée Polyvalent Blaise Cendrars;12 Avenue Leon Jouhaux;93270;Sevran;48.9395961;2.538669 +0360043B;Lycée Polyvalent Blaise Pascal;27 Bd Blaise Pascal;36019;Chateauroux;46.7980558;1.7115349 +0630001J;Lycée Polyvalent Blaise Pascal;23 Rue Blaise Pascal;63600;Ambert;45.5458577;3.7456095 +0680010S;Lycée Polyvalent Blaise Pascal;74 Rue Du Logelbach;68025;Colmar;48.0828085;7.345401 +0760095R;Lycée Polyvalent Blaise Pascal;5 Rue Des Emmurees;76174;Rouen;49.4329729;1.08562 +0772230F;Lycée Polyvalent Blaise Pascal;15 Allee Du Commandant Guesnet;77253;Brie Comte Robert;48.6991093;2.5959103 +0932221J;Lycée Polyvalent Blaise Pascal;18 Rue Marc Vieville;93250;Villemomble;48.8819491;2.5110143 +0771200L;Lycée Polyvalent Blanche De Castille;42 Bis Rue Du Chateau;77300;Fontainebleau;48.4065006;2.703165 +9741087N;Lycée Polyvalent Boisjoly Potier;Rue Ignaz Pleyel;97839;Le Tampon;-21.249148;55.525948 +0070001N;Lycée Polyvalent Boissy D'anglas;50 Avenue Jean Jaures;07104;Annonay;45.2470817;4.67011 +0710001R;Lycée Polyvalent Bonaparte;Place Du Champ De Mars;71403;Autun;46.9501701;4.2987595 +9741051Z;Lycée Polyvalent Bras Panon;51 Chemin Bras Panon;97412;Bras Panon;-21.0023692;55.6829973 +0783533S;Lycée Polyvalent Camille Claudel;21 Rue De La Lyre;78711;Mantes La Ville;48.9641158;1.7162431 +0951710T;Lycée Polyvalent Camille Claudel;Boulevard De L'oise;95490;Vaureal;49.030652;2.0219014 +0590042A;Lycée Polyvalent Camille Desmoulins;47 Rue De La Republique;59360;Le Cateau Cambresis;50.1021507;3.541294 +0950649P;Lycée Polyvalent Camille Pissarro;1 Rue Henri Matisse;95300;Pontoise;49.0560649;2.0891196 +0620056Z;Lycée Polyvalent Carnot;148 Rue Alfred Leroy;62701;Bruay La Buissiere;50.4792475;2.5445456 +9720825P;Lycée Polyvalent Centre Sud;Qua Vaudrancourt;97224;Ducos;0.0;0.0 +0951727L;Lycée Polyvalent Charles Baudelaire;13 Rue Du Grand Tremblay;95470;Fosses;49.101103;2.504328 +9711032V;Lycée Polyvalent Charles Coeffin;Trioncelle;97122;Baie Mahault;0.0;0.0 +0840001V;Lycée Polyvalent Charles De Gaulle (place);104 Place Charles De Gaulle;84405;Apt;47.3408348;-1.5269078 +0380053B;Lycée Polyvalent Charles Gabriel Pravaz;257 Rue Du Pre Saint Martin;38480;Le Pont De Beauvoisin;45.5398188;5.6687632 +0570021N;Lycée Polyvalent Charles Hermite;6 Rue Du Calvaire;57260;Dieuze;48.8088865;6.7245785 +0922277A;Lycée Polyvalent Charles Petiet;65 Bd Gallieni;92391;Villeneuve La Garenne;48.9267832;2.3291268 +0660021P;Lycée Polyvalent Charles Renouvier;Route De Catllar;66500;Prades;42.6265495;2.4199729 +0142120D;Lycée Polyvalent Charles Tellier;Route De Vire;14110;Conde Sur Noireau;48.8514625;-0.5649747 +0890032B;Lycée Polyvalent Chevalier D'eon;2 Place Edmond Jacob;89700;Tonnerre;47.8571627;3.9726577 +0941918Z;Lycée Polyvalent Christophe Colomb;154 Rue De Boissy;94370;Sucy En Brie;48.760231;2.5268783 +0690097F;Lycée Polyvalent Claude Bernard;234 Rue Philippe Heron;69665;Villefranche Sur Saone;45.9892277;4.7122182 +0922427N;Lycée Polyvalent Claude Garamont;69 Rue De L'industrie;92701;Colombes;48.9143256;2.2460678 +0271581X;Lycée Polyvalent Clement Ader;Rue De Rouen;27305;Bernay;49.0936362;0.6014418 +0772342C;Lycée Polyvalent Clement Ader;74-76 Rue Georges Clemenceau;77220;Tournan En Brie;48.7426674;2.7497947 +0910676R;Lycée Polyvalent Clement Ader;37 Bis Rue G.anthonioz De Gaull;91200;Athis Mons;48.7107797;2.3924387 +0050003B;Lycée Polyvalent Climatique D Altitude;2 Rue Marius Chancel;05105;Briancon;44.8987602;6.6403997 +0381630R;Lycée Polyvalent Clinique Du Gresivaudan;10 Avenue Maquis Gresivaudan;38702;La Tronche;45.2048437;5.7474496 +0210006T;Lycée Polyvalent Clos Maire;4 Rue Des Roles;21206;Beaune;47.033372;4.8358441 +0781884Z;Lycée Polyvalent Condorcet;Allee Condorcet;78520;Limay;48.986312;1.751793 +0940122X;Lycée Polyvalent Condorcet;1 Avenue Condorcet;94214;St Maur Des Fosses;48.7954086;2.5120495 +0831563Y;Lycée Polyvalent Costebelle;150 Bd Felix Descroix;83408;Hyeres;43.0980537;6.1232362 +0762920L;Lycée Polyvalent Coubertin;130 Rue Georges Clemenceau;76210;Bolbec;49.5651014;0.4607496 +0750650Z;Lycée Polyvalent D'alembert;22 Sente Des Dorees;75019;Paris 19;48.886887;2.393574 +0932122B;Lycée Polyvalent D'alembert;7 Rue Du Commandant L'herminier;93300;Aubervilliers;48.9173889;2.3867244 +0941474S;Lycée Polyvalent Darius Milhaud;80 Rue Du Professeur Bergonie;94276;Le Kremlin Bicetre;48.8022789;2.3507331 +7200123W;Lycée Polyvalent De Balagne;Av.paul Bisgambiglia;20220;L Ile Rousse;42.63651;8.937046 +9741206T;Lycée Polyvalent De Bois D'olive;;97432;St Pierre;49.1838819;-0.360612 +0400007R;Lycée Polyvalent De Borda;7 Avenue Paul Doumer;40107;Dax;43.7031026;-1.0566084 +9741233X;Lycée Polyvalent De Bras Fusil;Zac De Bras Fusil;97470;St Benoit;0.0;0.0 +9760316P;Lycée Polyvalent De Chirongui;;97620;Chirongui;0.0;0.0 +0150030B;Lycée Polyvalent De Haute Auvergne;20 Rue Marcellin Boudet;15101;St Flour;45.0349214;3.0838309 +0951824S;Lycée Polyvalent De L Hautil;1 Rue Gabriel Faure;95280;Jouy Le Moutier;49.010143;2.025968 +0220013V;Lycée Polyvalent De La Fontaine Des Eaux;La Fontaine Des Eaux;22102;Dinan;48.4614665;-2.0384413 +0772296C;Lycée Polyvalent De La Mare Carree;Rue Du Lycée;77552;Moissy Cramayel;0.0;0.0 +0380049X;Lycée Polyvalent De La Matheysine;3 Rue Lesdiguieres;38350;La Mure;44.9032559;5.785122 +0061987C;Lycée Polyvalent De La Montagne;Quartier Du Clot;06420;Valdeblore;44.0699954;7.1679872 +0950947N;Lycée Polyvalent De La Tourelle;8 Rue Fernand Leger;95200;Sarcelles;48.9928689;2.3828852 +0492224B;Lycée Polyvalent De L'hyrome;71 Rue Nationale;49120;Chemille;47.2166775;-0.7288251 +9760127J;Lycée Polyvalent De Mamoudzou;;97600;Mamoudzou;-12.780556;45.227778 +0641844G;Lycée Polyvalent De Navarre;Rue Jai Alai;64220;St Jean Pied De Port;43.1673642;-1.2319105 +9760229V;Lycée Polyvalent De Petite-terre;;97615;Pamandzi;-12.8004511;45.2829634 +9760182U;Lycée Polyvalent De Sada;;97640;Sada;45.0526421;7.3985249 +9741231V;Lycée Polyvalent De Sainte Anne;;97437;St Benoit;43.6491386;3.8061648 +9741380G;Lycée Polyvalent De St Paul 4;363 Route De Savannah;97411;St Paul;0.0;0.0 +9741186W;Lycée Polyvalent De Trois Bassins;81 Rue Georges Brassens;97426;Les Trois Bassins;-21.1040298;55.3016406 +0130050J;Lycée Polyvalent Denis Diderot;23 Bd Laveran;13388;Marseille 13;43.3251745;5.4146234 +0660004W;Lycée Polyvalent Deodat De Severac;Avenue Des Tilleuls;66403;Ceret;42.4852289;2.7526652 +0890008A;Lycée Polyvalent Des Chaumes;16 Avenue Du Parc Des Chaumes;89206;Avallon;47.4878384;3.9149854 +0740009H;Lycée Polyvalent Des Glieres;2 A Avenue De Verdun;74107;Annemasse;46.1882191;6.2434341 +0210013A;Lycée Polyvalent Desire Nisard;19 Rue De Seine;21402;Chatillon Sur Seine;47.8624852;4.5786136 +0750712S;Lycée Polyvalent Diderot;61 Rue David D'angers;75019;Paris 19;48.8813652;2.3961279 +0750676C;Lycée Polyvalent Dorian;74 Avenue Philippe Auguste;75011;Paris 11;48.8543909;2.3924246 +0390029V;Lycée Polyvalent Du Bois;Rue De Strasbourg;39330;Mouchard;46.974334;5.797075 +7200719U;Lycée Polyvalent Du Fium'orbu;Zi Miggliacciaro;20243;Prunelli Di Fiumorbo;42.010314;9.325429 +0421788R;Lycée Polyvalent Du Forez;10 Route De Civens;42110;Feurs;45.7508946;4.2267226 +0763002A;Lycée Polyvalent Du Golf;Chemin Du Golf;76372;Dieppe;49.918011;1.059239 +0831242Z;Lycée Polyvalent Du Golfe De Saint Tropez;Quartier Saint Martin;83580;Gassin;43.248582;6.569659 +0382863F;Lycée Polyvalent Du Gresivaudan;1 Avenue Du Taillefer;38240;Meylan;45.2098436;5.7821035 +0790031E;Lycée Polyvalent Du Haut Val De Sevre;22 Rue Du Panier Fleuri;79403;St Maixent L Ecole;46.4112715;-0.2201006 +9760270P;Lycée Polyvalent Du Nord;;97630;Acoua;41.8291937;9.4039267 +0781883Y;Lycée Polyvalent Dumont D Urville;2 Avenue De Franche Comte;78310;Maurepas;48.764406;1.9522346 +0762600N;Lycée Polyvalent E. Delamare Deboutteville;5 Rue Andre Bertrand;76440;Forges Les Eaux;49.6182713;1.5372705 +0212045J;Lycée Polyvalent E.j. Marey;5 Rue Du 16e Chasseurs;21206;Beaune;47.0286521;4.8505659 +0251671F;Lycée Polyvalent Edgar Faure;2 Rue Du Dr Sauze;25503;Morteau;47.0622542;6.6074931 +0750671X;Lycée Polyvalent Edgar Quinet;63 Rue Des Martyrs;75009;Paris 09;48.8814303;2.3398192 +0700905D;Lycée Polyvalent Edouard Belin;18 Rue Edouard Belin;70006;Vesoul;47.6337311;6.1648428 +0280021W;Lycée Polyvalent Edouard Branly;29 Avenue Kennedy;28100;Dreux;48.7305183;1.3791302 +0941018W;Lycée Polyvalent Edouard Branly;33 Rue Du Petit Bois;94000;Creteil;48.7840361;2.4667703 +0380091T;Lycée Polyvalent Edouard Herriot;Avenue Edouard Herriot;38506;Voiron;45.3698096;5.5884471 +0380073Y;Lycée Polyvalent Elie Cartan;2 Rue Justin Vernet;38351;La Tour Du Pin;45.5642227;5.4436956 +0750677D;Lycée Polyvalent Elisa Lemonnier;20 Avenue Armand Rousseau;75012;Paris 12;48.8360288;2.4091258 +0711729U;Lycée Polyvalent Emiland Gauthey;20 Rue De L'ancien Collège;71321;Chalon Sur Saone;0.0;0.0 +0170020E;Lycée Polyvalent Emile Combes;Rue Des Cordeliers;17800;Pons;45.578308;-0.550741 +0500002F;Lycée Polyvalent Emile Littre;28 Rue De Verdun;50303;Avranches;48.679946;-1.3603845 +0670089H;Lycée Polyvalent Emile Mathis;1 Rue Du Dauphine;67311;Schiltigheim;48.6337017;7.7455694 +0160022M;Lycée Polyvalent Emile Roux;Avenue Du General De Gaulle;16500;Confolens;46.0082;0.6721341 +0590233H;Lycée Polyvalent Emile Zola;174 Rue De La Baillerie;59391;Wattrelos;50.7073916;3.2319465 +0781819D;Lycée Polyvalent Emilie De Breteuil;3 Rue Du Canal;78053;Montigny Le Bretonneux;48.7819147;2.0421257 +0772688D;Lycée Polyvalent Emilie Du Chatelet;110 Bd Du Champ Du Moulin;77700;Serris;48.8486962;2.7884286 +0430953C;Lycée Polyvalent Emmanuel Chabrier;Le Piny-haut;43201;Yssingeaux;45.128135;4.1201489 +0573491K;Lycée Polyvalent Ernest Cuvelette;Rue De Grenoble;57800;Freyming Merlebach;49.146598;6.787573 +0300027S;Lycée Polyvalent Ernest Hemingway;98 Avenue Jean Jaures;30910;Nimes;43.8260467;4.3533949 +0770940D;Lycée Polyvalent Etienne Bezout;31 Avenue Etienne Dailly;77796;Nemours;48.2606523;2.7152517 +0922443F;Lycée Polyvalent Etienne-jules Marey;154 Rue De Silly;92100;Boulogne Billancourt;48.8320565;2.2340158 +0930119Z;Lycée Polyvalent Eugene Delacroix;4 Rue Du Docteur Schweitzer;93700;Drancy;48.9239128;2.4582228 +0922397F;Lycée Polyvalent Eugene Ionesco;152 Avenue De Verdun;92130;Issy Les Moulineaux;48.8189753;2.2539959 +0951810B;Lycée Polyvalent Eugene Ronceray;5 7 Rue Marcel Langlois;95875;Bezons;48.9218246;2.2168644 +0590168M;Lycée Polyvalent Eugene Thomas;100 Avenue Leo Lagrange;59530;Le Quesnoy;50.2480377;3.6440963 +0932116V;Lycée Polyvalent Eugenie Cotton;58 Avenue Faidherbe;93100;Montreuil;48.866896;2.4372326 +0490018D;Lycée Polyvalent Europe Robert Schuman;39 Avenue De L Europe;49311;Cholet;47.0461537;-0.88536 +0932047V;Lycée Polyvalent Evariste Galois;32 Avenue Montaigne;93165;Noisy Le Grand;48.8444723;2.5393145 +0951748J;Lycée Polyvalent Evariste Galois;14 Bd Leon Blum;95260;Beaumont Sur Oise;49.1454738;2.2950659 +0951811C;Lycée Polyvalent F. Et N. Leger;7 Allee F Et N Leger;95104;Argenteuil;48.947411;2.2489499 +0380092U;Lycée Polyvalent Ferdinand Buisson;21 Boulevard Edgar Kolfer;38506;Voiron;45.3607983;5.5995988 +0760030V;Lycée Polyvalent Ferdinand Buisson;6 Rue Auguste Houzeau;76504;Elbeuf;49.2867649;1.0043623 +0941972H;Lycée Polyvalent Fernand Leger;15 Avenue Henri Barbusse;94200;Ivry Sur Seine;48.8092163;2.3777145 +0910621F;Lycée Polyvalent Francisque Sarcey;Chemin Du Champ De Courses;91410;Dourdan;48.5346237;1.9966259 +0941952L;Lycée Polyvalent Francois Arago;36 Avenue De L'europe;94190;Villeneuve St Georges;48.7311569;2.4586032 +0693566B;Lycée Polyvalent Francois Mansart;Rue Jacquard;69240;Thizy;46.0249019;4.3125338 +0142132S;Lycée Polyvalent Francois Rabelais;Esplanade Rabelais;14200;Herouville St Clair;49.2082446;-0.290863 +0370009J;Lycée Polyvalent Francois Rabelais;28 Quai Danton;37501;Chinon;47.16364;0.2344406 +0693504J;Lycée Polyvalent Francois Rabelais;Chemin Du Dodin;69571;Dardilly;45.8161875;4.7629028 +0850068M;Lycée Polyvalent Francois Rabelais;45 Rue Rabelais;85205;Fontenay Le Comte;46.4679784;-0.8171308 +0752701D;Lycée Polyvalent Francois Truffaut;28 Rue Debelleyme;75003;Paris 03;48.861928;2.3634247 +0911937L;Lycée Polyvalent Francois Truffaut;Rue Georges Pompidou;91072;Bondoufle;48.6186709;2.3897445 +0762976X;Lycée Polyvalent Francoise De Grace;16 Rue De La Vallee;76071;Le Havre;49.4930608;0.1501086 +0771027Y;Lycée Polyvalent Frederic Joliot Curie;168 Rue F Joliot Curie;77196;Dammarie Les Lys;48.518903;2.6489522 +0941301D;Lycée Polyvalent Frederic Mistral;7-9 Rue Frederic Mistral;94260;Fresnes;48.7609403;2.3299967 +0611182D;Lycée Polyvalent Gabriel;7 Rue Saint Exupery;61204;Argentan;48.7533879;-0.0075234 +0070029U;Lycée Polyvalent Gabriel Faure;Place Mallarme;07301;Tournon Sur Rhone;45.0672005;4.8344977 +0762911B;Lycée Polyvalent Galilee;461 Route De Belbeuf;76520;Franqueville St Pierre;49.3983018;1.1607919 +0400002K;Lycée Polyvalent Gaston Crampe;Av Droits De L Homme Et Citoyen;40801;Aire Sur L Adour;43.700062;-0.262294 +0301654K;Lycée Polyvalent Genevieve De Gaulle-anthonioz;43 Rue Du Moulin;30540;Milhaud;43.7864243;4.309575 +0031044U;Lycée Polyvalent Genevieve Vincent;15 Bd Du General De Gaulle;03600;Commentry;46.2911311;2.7336414 +0910622G;Lycée Polyvalent Geoffroy-st-hilaire;2-6 Av Geoffroy St Hilaire;91153;Etampes;48.4351205;2.1463921 +0360019A;Lycée Polyvalent George Sand;25 Avenue George Sand;36400;La Chatre;46.5824064;1.9815737 +0951788C;Lycée Polyvalent George Sand;Avenue Du Lycée;95331;Domont;0.0;0.0 +0763237F;Lycée Polyvalent Georges Baptiste;41 Rte De Duclair;76380;Canteleu;49.4431486;1.0186583 +0950666H;Lycée Polyvalent Georges Braque;21 Rue Victor Puiseux;95100;Argenteuil;48.950485;2.2505933 +0420027B;Lycée Polyvalent Georges Brassens;8 Rue Grange Burlat;42800;Rive De Gier;45.5325721;4.60488 +0762601P;Lycée Polyvalent Georges Brassens;27 Boulevard Gustave Eiffel;76270;Neufchatel En Bray;49.7379225;1.4389657 +0911828T;Lycée Polyvalent Georges Brassens;8 R Georges Brassens;91080;Courcouronnes;48.626643;2.4205622 +0932260B;Lycée Polyvalent Georges Brassens;Rue Des Bancs Publics;93420;Villepinte;48.9554743;2.5328795 +0940743X;Lycée Polyvalent Georges Brassens;12 Avenue Le Foll;94290;Villeneuve Le Roi;48.7301773;2.4276725 +9740053P;Lycée Polyvalent Georges Brassens;16 Avenue Georges Brassens;97493;St Denis;44.9366126;-0.4864077 +0772339Z;Lycée Polyvalent Georges Clemenceau;5 Rue Georges Clemenceau;77430;Champagne Sur Seine;48.3990891;2.7972818 +6200043C;Lycée Polyvalent Georges Clemenceau;Bd Jean Nicolai;20100;Sartene;41.621822;8.97472 +0700018P;Lycée Polyvalent Georges Colomb;1 Rue G Colomb;70204;Lure;47.6879696;6.4987354 +0672614C;Lycée Polyvalent Georges Imbert;2 Rue Vincent D'indy;67261;Sarre Union;48.9359751;7.0886788 +0341921D;Lycée Polyvalent Georges Pompidou;351 Av Mal De Lattre Tassigny;34172;Castelnau Le Lez;43.6469391;3.9199801 +0771940R;Lycée Polyvalent Gerard De Nerval;89 Cours Des Roches Noisiel;77441;Champs Sur Marne;48.8427362;2.6154529 +9711012Y;Lycée Polyvalent Grand Bourg;Rue De La Savane;97112;Grand Bourg;0.0;0.0 +0940742W;Lycée Polyvalent Guillaume Bude;2 Voie Georges Pompidou;94456;Limeil Brevannes;48.7441012;2.5110985 +0740013M;Lycée Polyvalent Guillaume Fichet;219 Rue De Pressy;74136;Bonneville;46.0793702;6.411561 +0754476H;Lycée Polyvalent Guillaume Tirel;237 Boulevard Raspail;75014;Paris 14;48.8387393;2.3308641 +0330028B;Lycée Polyvalent Gustave Eiffel;143 Crs Marne;33074;Bordeaux;44.837789;-0.57918 +0922398G;Lycée Polyvalent Gustave Eiffel;78 Avenue Du Pdt Pompidou;92500;Rueil Malmaison;48.8715605;2.1940934 +0940111K;Lycée Polyvalent Gustave Eiffel;61 Avenue Du President Wilson;94235;Cachan;48.789111;2.325304 +0620256S;Lycée Polyvalent Guy Mollet;57 Rue Bocquet Flochel;62022;Arras;50.2800007;2.7946232 +0380014J;Lycée Polyvalent Hector Berlioz;Place De L Europe;38260;La Cote St Andre;45.2002269;6.6710747 +0772277G;Lycée Polyvalent Henri Becquerel;1 Bd Henri Rousselle;77370;Nangis;48.5518365;3.0076186 +0261397C;Lycée Polyvalent Henri Laurens;Quartier Des Rioux;26241;St Vallier;0.0;0.0 +0912251C;Lycée Polyvalent Henri Poincaré;36 Rue Leon Bourgeois;91122;Palaiseau;48.702946;2.2370181 +0932120Z;Lycée Polyvalent Henri Sellier;73 Avenue Du Colonel Fabien;93190;Livry Gargan;48.9186434;2.5162221 +0710042K;Lycée Polyvalent Henri Vincenot;Montee Saint Claude;71500;Louhans;46.623382;5.214177 +0550008K;Lycée Polyvalent Henri Vogt;12 Rue Andre Malraux;55205;Commercy;48.7548233;5.5858673 +0711422K;Lycée Polyvalent Hilaire De Chardonnet;1 Rue Henri Dunant;71321;Chalon Sur Saone;46.8024283;4.8663853 +0210018F;Lycée Polyvalent Hippolyte Fontaine;20 Boulevard Voltaire;21033;Dijon;47.3181031;5.0511309 +0771996B;Lycée Polyvalent Honore De Balzac;Avenue Paul Langevin;77290;Mitry Mory;48.9724391;2.6505336 +0022044L;Lycée Polyvalent Hotelier;Passage Le Corbusier;02200;Soissons;49.376636;3.32342 +0622807P;Lycée Polyvalent Hotelier;Avenue Du Chateau;62520;Le Touquet Paris Plage;50.516361;1.602514 +9711066G;Lycée Polyvalent Hotelier Du Gosier;Saint-felix;97190;Le Gosier;0.0;0.0 +0881664F;Lycée Polyvalent Hotelier J-b Simeon Chardin;32 Boulevard D'alsace;88407;Gerardmer;48.0732813;6.8764418 +9720823M;Lycée Polyvalent Hotelier Nord Caraibes;Bellefontaine;97222;Bellefontaine;46.558079;6.068529 +0641823J;Lycée Polyvalent Hotelier Tourisme;2 Rue F Jammes;64204;Biarritz;43.4623738;-1.5522272 +0390033Z;Lycée Polyvalent Hyacinthe Friant;2 Rue Hyacinthe Friant;39801;Poligny;0.0;0.0 +9710981P;Lycée Polyvalent Iles Du Nord;Marigot;97052;St Martin;18.0664178;-63.0872351 +9830003L;Lycée Polyvalent J.garnier;65 Avenue James Cook;98849;Noumea;-22.2686289;166.4242812 +0350048M;Lycée Polyvalent Jacques Cartier;31 Rue De La Balue;35403;St Malo;48.6280158;-2.0040075 +0390013C;Lycée Polyvalent Jacques Duhamel;Rue Charles Laurent Thouverey;39107;Dole;47.09534;5.49081 +0750428H;Lycée Polyvalent Jacques Monod;12 Rue Victor Cousin;75005;Paris 05;48.8476683;2.3423041 +0301778V;Lycée Polyvalent Jacques Prevert;;30380;St Christol Les Ales;44.083566;4.077927 +0110019L;Lycée Polyvalent Jacques Ruffie;Esplanade Francois Mitterand;11303;Limoux;43.0506812;2.2194271 +0542293Z;Lycée Polyvalent Jacques-marie Boutet De Monvel;4 Rue Boutet De Monvel;54300;Luneville;48.5986914;6.4925295 +0022042J;Lycée Polyvalent Jean Bouin;Rue Gaston Bachelard;02100;St Quentin;49.854359;3.30866 +0750708M;Lycée Polyvalent Jean Drouant;20 Rue Mederic;75017;Paris 17;48.880936;2.3040609 +0110012D;Lycée Polyvalent Jean Durand;Avenue Docteur Laennec;11493;Castelnaudary;43.3068767;1.9402732 +0230051F;Lycée Polyvalent Jean Favard;27 Rte De Courtille;23003;Gueret;46.168088;1.8569052 +0840015K;Lycée Polyvalent Jean Henri Fabre;Av Du Mont Ventoux;84208;Carpentras;44.0569097;5.0619963 +9740979W;Lycée Polyvalent Jean Hinglo;2 Rue Des Sans Soucis;97825;Le Port;43.0154964;3.0609934 +0170135E;Lycée Polyvalent Jean Hyppolite;Place Saint Exupery;17502;Jonzac;45.4414119;-0.4322947 +0680001G;Lycée Polyvalent Jean Jacques Henner;20 Rue De Hirtzbach;68130;Altkirch;47.6153682;7.234954 +0120025M;Lycée Polyvalent Jean Jaures;Avenue Jean Jaures;12401;St Affrique;43.9510528;2.8953583 +0342066L;Lycée Polyvalent Jean Jaures;Avenue Saint Sauveur;34980;St Clement De Riviere;43.7098148;3.8323688 +0921166T;Lycée Polyvalent Jean Jaures;280 Avenue Jean Jaures;92291;Chatenay Malabry;48.7677839;2.254895 +0930121B;Lycée Polyvalent Jean Jaures;1 Rue Dombasle;93105;Montreuil;48.8636396;2.4491594 +0941974K;Lycée Polyvalent Jean Jaures;9 Avenue Jean Jaures;94220;Charenton Le Pont;48.8247353;2.4126023 +0950641F;Lycée Polyvalent Jean Jaures;25 Rue Charles Lecoq;95100;Argenteuil;48.9403527;2.225817 +0660011D;Lycée Polyvalent Jean Lurcat;25 Avenue Albert Camus;66000;Perpignan;42.6918634;2.9063835 +0753268V;Lycée Polyvalent Jean Lurcat;48 Avenue Des Gobelins;75013;Paris 13;48.8342909;2.3533682 +0561698S;Lycée Polyvalent Jean Mace;Rue Jean-paul Sartre;56601;Lanester;47.765248;-3.344219 +0340042L;Lycée Polyvalent Jean Mermoz;717 Avenue Jean Mermoz;34060;Montpellier;43.6109837;3.8901026 +0680066C;Lycée Polyvalent Jean Mermoz;53 Rue Du Docteur Hurst;68300;St Louis;47.5793744;7.558521 +0133288D;Lycée Polyvalent Jean Monnet;Bd Rhin Et Danube;13127;Vitrolles;43.424732;5.2717125 +0160020K;Lycée Polyvalent Jean Monnet;66 Boulevard De Chatenay;16100;Cognac;45.701557;-0.3180834 +0611148S;Lycée Polyvalent Jean Monnet;2 Rue Jean Monnet;61400;Mortagne Au Perche;48.5124382;0.5531263 +0781839A;Lycée Polyvalent Jean Monnet;Place De L Europe;78940;La Queue Les Yvelines;48.8022064;1.7827586 +0851400K;Lycée Polyvalent Jean Monnet;57 Rue De La Demoiselle;85500;Les Herbiers;46.8663273;-1.0202618 +0951722F;Lycée Polyvalent Jean Monnet;Rue Jean Monnet;95131;Franconville;48.9919298;2.2151614 +0271585B;Lycée Polyvalent Jean Moulin;Rue Du Marechal Foch;27700;Les Andelys;49.2492308;1.4406574 +0492123S;Lycée Polyvalent Jean Moulin;1 Place Jean Moulin;49017;Angers;47.491678;-0.551624 +0932118X;Lycée Polyvalent Jean Moulin;2 Avenue Charles De Gaulle;93150;Le Blanc Mesnil;48.9433914;2.4685539 +0130053M;Lycée Polyvalent Jean Perrin;74 Rue Verdillon;13395;Marseille 10;43.2746696;5.4264973 +0951104J;Lycée Polyvalent Jean Perrin;2 Rue Des Egalisses;95310;St Ouen L Aumone;49.0572645;2.1326835 +0910975R;Lycée Polyvalent Jean Pierre Timbaud;4 Rue Henri Douard;91220;Bretigny Sur Orge;48.6109653;2.3129067 +0330109P;Lycée Polyvalent Jean Renou;2 Rue Jean Renou;33192;La Reole;44.5815968;-0.0413168 +0670084C;Lycée Polyvalent Jean Rostand;18 Boulevard De La Victoire;67084;Strasbourg;48.5829461;7.7626845 +0782540M;Lycée Polyvalent Jean Rostand;66 Rue Fernand Bodet;78200;Mantes La Jolie;48.9979639;1.696681 +0931584S;Lycée Polyvalent Jean Rostand;8 Rue Pierre Audat;93420;Villepinte;48.959629;2.545226 +0511951U;Lycée Polyvalent Jean Talon;105 Avenue Daniel Simonnot;51037;Chalons En Champagne;48.9551578;4.3400748 +0450050K;Lycée Polyvalent Jean Zay;2 Rue Ferdinand Buisson;45044;Orleans;47.9067083;1.9127555 +0930833A;Lycée Polyvalent Jean Zay;Avenue Du Marechal Juin;93604;Aulnay Sous Bois;48.93782;2.5057694 +0550025D;Lycée Polyvalent Jean-auguste Margueritte;13 Place Commandant Galland;55107;Verdun;49.1563087;5.3904397 +0271634E;Lycée Polyvalent Jean-baptiste Decretot;7 Rue De La Gare;27400;Louviers;49.2162353;1.1756759 +0782557F;Lycée Polyvalent Jean-baptiste Poquelin;72 Rue Leon Desoyer;78101;St Germain En Laye;48.8995389;2.0856482 +0671832C;Lycée Polyvalent Jean-baptiste Schwilgue;1 Rue Du Stade;67604;Selestat;48.2504987;7.3971004 +0880036L;Lycée Polyvalent Jean-baptiste Vuillaume;Avenue Graillet;88503;Mirecourt;48.298504;6.132288 +0341794R;Lycée Polyvalent Jean-francois Champollion;Avenue De Figuieres;34973;Lattes;43.5876661;3.9306222 +0942125Z;Lycée Polyvalent Jean-jacques Rousseau;15 Rue Lebrun;94400;Vitry Sur Seine;48.7828517;2.3819482 +0950650R;Lycée Polyvalent Jean-jacques Rousseau;2 Rue J J Rousseau;95200;Sarcelles;48.9938677;2.3821641 +0280044W;Lycée Polyvalent Jehan De Beauce;20 Rue Du Commandant Chesne;28000;Chartres;48.4586432;1.4735645 +0772276F;Lycée Polyvalent Jehan De Chelles;47 Rue Des Cites;77649;Chelles;48.888756;2.6115193 +0420008F;Lycée Polyvalent Jeremie De La Rue;Route De Saint Bonnet;42190;Charlieu;46.167128;4.1718563 +0590018Z;Lycée Polyvalent Jesse De Forest;15 Avenue Du Pont Rouge;59363;Avesnes Sur Helpe;50.121543;3.9255446 +0941930M;Lycée Polyvalent Johannes Gutenberg;16-18 Rue De Saussure;94000;Creteil;48.7697246;2.4709831 +0890005X;Lycée Polyvalent Joseph Fourier;Rue R Poincare;89010;Auxerre;47.79215;3.552682 +0312759F;Lycée Polyvalent Joseph Gallieni;79 Route D'espagne;31100;Toulouse;43.573033;1.4243835 +0590044C;Lycée Polyvalent Joseph Marie Jacquard;4 Avenue Jean Moulin;59544;Caudry;50.1263865;3.4040061 +9720726G;Lycée Polyvalent Joseph Pernock;Le Lorrain;97214;Le Lorrain;0.0;0.0 +0681839E;Lycée Polyvalent Joseph Storck (hotelier);Rue Jules Ferry;68504;Guebwiller;47.901203;7.228875 +0340028W;Lycée Polyvalent Joseph Vallot;5 Boulevard Gambetta;34702;Lodeve;43.7313632;3.3171287 +9720725F;Lycée Polyvalent Joseph Zobel;Quartier Thoraille;97215;Riviere Salee;0.0;0.0 +0142131R;Lycée Polyvalent Jules Dumont D'urville;73 Rue De Lebisey;14070;Caen;49.1965539;-0.3548514 +0782565P;Lycée Polyvalent Jules Ferry;29 Rue Du Marechal Joffre;78000;Versailles;48.7942841;2.1208585 +0340040J;Lycée Polyvalent Jules Guesde;110 Avenue De Lodeve;34060;Montpellier;43.6106065;3.8486836 +0250011B;Lycée Polyvalent Jules Haag;1 Rue Labbe;25041;Besancon;47.239422;6.013522 +0951756T;Lycée Polyvalent Jules Verne;1 Rue Michel Strogoff;95800;Cergy;49.0528165;2.009558 +0710018J;Lycée Polyvalent Julien Wittmer;13 Rue De Champagny;71120;Charolles;46.4353344;4.2731882 +0911492C;Lycée Polyvalent L Essouriau;Avenue De Dordogne;91940;Les Ulis;48.6767778;2.1657625 +0710026T;Lycée Polyvalent L?on Blum;72 Rue Jean Jaures;71200;Le Creusot;46.8049544;4.4140633 +0430003V;Lycée Polyvalent La Fayette;Plateau St Laurent;43103;Brioude;45.295564;3.386482 +9720771F;Lycée Polyvalent La Jetee;Route De La Jetee;97240;Le Francois;46.21037;6.15433 +0382838D;Lycée Polyvalent La Pleiade;Rue Du Repos;38232;Pont De Cheruy;45.7529419;5.1711019 +9741173G;Lycée Polyvalent La Possession;;97419;La Possession;44.6915692;-0.8600616 +0380063M;Lycée Polyvalent La Saulaie;La Saulaie;38162;St Marcellin;45.1599563;5.3196875 +0772295B;Lycée Polyvalent La Tour Des Dames;Rue De Vilpres;77540;Rozay En Brie;48.6791913;2.9646581 +0940113M;Lycée Polyvalent Langevin-wallon;126 Avenue Roger Salengro;94507;Champigny Sur Marne;48.8171426;2.4974266 +0772340A;Lycée Polyvalent L'arche Guedon;2 Passage De L'arche Guedon;77200;Torcy;48.8499971;2.6359863 +0681768C;Lycée Polyvalent Lavoisier;42 Rue Lavoisier;68200;Mulhouse;47.7536602;7.3250939 +0620008X;Lycée Polyvalent Lazare Carnot;21 Boulevard Carnot;62022;Arras;50.2875817;2.77396 +0210019G;Lycée Polyvalent Le Castel;22 Rue Daubenton;21033;Dijon;47.3131033;5.0362136 +0071397F;Lycée Polyvalent Le Cheylard;Quartier Plaisance;07160;Le Cheylard;44.9113929;4.42675 +0762964J;Lycée Polyvalent Le Corbusier;Rue De L'universite;76800;St Etienne Du Rouvray;49.3870486;1.0650878 +0721094Y;Lycée Polyvalent Le Mans Sud;128 Rue Henri Champion;72058;Le Mans;47.9831673;0.2336784 +0831646N;Lycée Polyvalent Le Muy;Avenue De Vaugrenier;83490;Le Muy;43.4761296;6.5373255 +0932282A;Lycée Polyvalent Leo Lagrange;2 Rue Compagnon;93140;Bondy;48.9064253;2.4914471 +0941413A;Lycée Polyvalent Leon Blum;5 Rue Jean Gabin;94000;Creteil;48.779746;2.4450351 +0061478Z;Lycée Polyvalent Leonard De Vinci;214 Rue Jean Joannon;06633;Antibes;43.6049397;7.0729317 +0624141P;Lycée Polyvalent Leonard De Vinci;Rue Pasteur Martin Luther King;62228;Calais;50.9537082;1.8876944 +0754475G;Lycée Polyvalent Leonard De Vinci;20 Rue Bourseul;75015;Paris 15;48.8382608;2.3020551 +0782556E;Lycée Polyvalent Leonard De Vinci;2 Bd Hector Berlioz;78100;St Germain En Laye;48.8914922;2.0638407 +0851390Z;Lycée Polyvalent Leonard De Vinci;Rue Du Fromenteau;85603;Montaigu;46.971961;-1.303299 +0911946W;Lycée Polyvalent Leonard De Vinci;1 Place Leonard De Vinci;91240;St Michel Sur Orge;48.6177611;2.3315865 +0921230M;Lycée Polyvalent Leonard De Vinci;4 Av Georges Pompidou;92304;Levallois Perret;48.8973095;2.2778034 +0781951X;Lycée Polyvalent Leopold Sedar Senghor;Place Pierre Beregovoy;78200;Magnanville;48.96488;1.683672 +9740045F;Lycée Polyvalent Les Avirons;;97425;Les Avirons;-21.240637;55.334205 +0441552Y;Lycée Polyvalent Les Bourdonnieres;Rue De La Perriere;44265;Nantes;47.186446;-1.517926 +0270029K;Lycée Polyvalent Les Fontenelles;Chemin Les Fontenelles;27406;Louviers;49.209294;1.156647 +0332832Z;Lycée Polyvalent Les Iris;13 Rue Saint Cricq;33305;Lormont;44.8700168;-0.5322274 +0040533H;Lycée Polyvalent Les Iscles;116 Bd Regis Ryckebush;04100;Manosque;43.831469;5.7990491 +0211986V;Lycée Polyvalent Les Marcs D'or;24 R Du Fort De La Motte Giron;21006;Dijon;47.3158772;4.9969497 +0781860Y;Lycée Polyvalent Les Pierres Vives;1 Rue Des Alouettes;78420;Carrieres Sur Seine;48.913654;2.1725297 +0932267J;Lycée Polyvalent Liberte;27 A 39 Rue De La Liberte;93230;Romainville;48.8829412;2.4294153 +9740054R;Lycée Polyvalent Lislet Geoffroy;Cite Scolaire Du Butor;97491;St Denis;48.936181;2.357443 +0380008C;Lycée Polyvalent L'oiselet;Rue Marion Prolongee;38317;Bourgoin Jallieu;45.597108;5.27212 +0701035V;Lycée Polyvalent Louis Aragon;Rue Pierre Mendes France;70400;Hericourt;47.574053;6.761598 +0691644M;Lycée Polyvalent Louis Armand;Avenue Du Beaujolais;69651;Villefranche Sur Saone;45.9990616;4.7063263 +0751708Z;Lycée Polyvalent Louis Armand;319 321 Rue Lecourbe;75015;Paris 15;48.837404;2.2850709 +0951974E;Lycée Polyvalent Louis Armand;32 Rue Stephane Proust;95600;Eaubonne;48.9970012;2.288332 +0170051N;Lycée Polyvalent Louis Audouin Dubreuil;Rue Philippe Jeannet;17415;St Jean D Angely;45.951385;-0.526721 +0782549X;Lycée Polyvalent Louis Bascan;5 Av Du Gal Leclerc;78513;Rambouillet;48.6410608;1.8245937 +0891199V;Lycée Polyvalent Louis Davier;Avenue Moliere;89306;Joigny;47.982086;3.414577 +0573227Y;Lycée Polyvalent Louis De Cormontaigne;12 Place Cormontaigne;57010;Metz;49.1243217;6.1633963 +0340030Y;Lycée Polyvalent Louis Feuillade;49 Rue Romain Rolland;34402;Lunel;43.6697437;4.1223872 +0951763A;Lycée Polyvalent Louis Jouvet;26 Rue De St Prix;95152;Taverny;49.0058062;2.2732907 +0740006E;Lycée Polyvalent Louis Lachenal-argonay;335 Route Champ Farcon;74372;Argonay;45.938068;6.132849 +0140052F;Lycée Polyvalent Louis Liard;15 Rue Saint Jean;14700;Falaise;48.8949844;-0.1949088 +0672615D;Lycée Polyvalent Louis Marchal;2 Route De La Hardt;67125;Molsheim;48.5369628;7.5012312 +0922464D;Lycée Polyvalent Louise Michel;11 Boulevard Du Midi;92000;Nanterre;48.8897402;2.193836 +0680068E;Lycée Polyvalent Louise Weiss;Route Du Stade;68160;Ste Marie Aux Mines;48.236375;7.1681767 +0781950W;Lycée Polyvalent Louise Weiss;201 Avenue Du Gal De Gaulle;78260;Acheres;48.9707469;2.0741265 +0750463W;Lycée Polyvalent Lucas De Nehou;4 Rue Des Feuillantines;75005;Paris 05;48.8412988;2.3435246 +0932117W;Lycée Polyvalent Lucie Aubrac;51 Rue Victor Hugo;93500;Pantin;48.8937132;2.4096485 +0701078S;Lycée Polyvalent Lumiere;33 Ter Rue Grammont;70306;Luxeuil Les Bains;47.8248597;6.377976 +9840023C;Lycée Polyvalent Lycée Polyvalent De Taaone;Pirae;98716;Pirae;-17.5353835;-149.5478833 +9840407V;Lycée Polyvalent Lycée Tertiaire De Pirae;Pirae;98716;Pirae;-17.5353835;-149.5478833 +0570094T;Lycée Polyvalent Mangin;34 Rue Gambetta;57401;Sarrebourg;48.7391327;7.0583227 +0342091N;Lycée Polyvalent Marc Bloch;;34500;Beziers;43.344233;3.215795 +0070003R;Lycée Polyvalent Marcel Gimond;Boulevard De L'europe;07205;Aubenas;44.619386;4.3808809 +0672806L;Lycée Polyvalent Marcel Rudloff;Avenue Francois Mitterrand;67200;Strasbourg;48.5862403;7.6958226 +0320015T;Lycée Polyvalent Marechal Lannes;Cite Scolaire 1 Pl Brossolette;32700;Lectoure;43.933604;0.623621 +0623902E;Lycée Polyvalent Marguerite Yourcenar;68 Rue Antoine De Saint Exupery;62660;Beuvry;50.51858;2.6641857 +0672677W;Lycée Polyvalent Marguerite Yourcenar;Rue Victor Schoelcher;67152;Erstein;48.4227904;7.6499811 +0911945V;Lycée Polyvalent Marguerite Yourcenar;62 Rue Des Edouets;91423;Morangis;48.7081491;2.3259431 +0342225J;Lycée Polyvalent Marianne;;34000;Montpellier;43.6047275;3.9011747 +0911962N;Lycée Polyvalent Marie Laurencin;51 Rue Paul Cezanne;91542;Mennecy;48.5616978;2.4440346 +0312746S;Lycée Polyvalent Marie Louise Dissard Francoise;5 Boulevard Alain Savary;31170;Tournefeuille;43.5423773;1.3405506 +0730003G;Lycée Polyvalent Marlioz;Chemin Du Lycée;73102;Aix Les Bains;0.0;0.0 +0754530S;Lycée Polyvalent Martin Nadaud;23 Rue De La Bidassoa;75020;Paris 20;48.8662003;2.393588 +0681882B;Lycée Polyvalent Martin Schongauer;25 Rue Voltaire;68000;Colmar;48.0696475;7.3511399 +0941951K;Lycée Polyvalent Marx Dormoy;500 Rue Du Professeur Milliez;94507;Champigny Sur Marne;48.8154449;2.5349215 +0875023M;Lycée Polyvalent Maryse Bastie;Rue Louis Amstrong;87000;Limoges;45.8162774;1.2623048 +0710010A;Lycée Polyvalent Mathias;3 Place Mathias;71321;Chalon Sur Saone;46.7865371;4.8603337 +0580761M;Lycée Polyvalent Maurice Genevoix;51 Route D'avril Sur Loire;58300;Decize;46.8284621;3.441725 +0831559U;Lycée Polyvalent Maurice Janetti;Quartier Mirade;83470;St Maximin La Ste Baume;43.452497;5.864004 +0932030B;Lycée Polyvalent Maurice Utrillo;152 Rue Jean Durand;93240;Stains;48.9524279;2.3691348 +0750502N;Lycée Polyvalent Maximilien Vox-art-dessin;5 Rue Madame;75006;Paris 06;48.8520258;2.3319993 +0133406G;Lycée Polyvalent Mediterranee (de La );Qua La Plaine Av Mediterranee;13600;La Ciotat;43.190758;5.611854 +9730309Y;Lycée Polyvalent Melkior Garre;Route De Montabo;97305;Cayenne;4.9347066;-52.2954502 +0610006A;Lycée Polyvalent Mezeray;6 Place Robert Dugue;61200;Argentan;48.7448047;-0.0151903 +0760032X;Lycée Polyvalent Michel Anguier;41 Bis Rue De La Republique;76260;Eu;50.0482297;1.4220286 +0740027C;Lycée Polyvalent Mont Blanc Rene Dayve;Rue Rene Dayve;74190;Passy;45.907806;6.708722 +0922249V;Lycée Polyvalent Montesquieu;21 Rue Du Capitaine Facq;92350;Le Plessis Robinson;48.7776941;2.2637716 +0920136Y;Lycée Polyvalent Newton-enrea;1 Place Jules Verne;92110;Clichy;48.9087541;2.3073035 +0442094M;Lycée Polyvalent Nicolas Appert;24 Avenue De La Choliere;44702;Orvault;47.251063;-1.6067232 +9720692V;Lycée Polyvalent Nord Atlantique;Boulevard De La Voie Lactee;97230;Ste Marie;14.7787858;-60.9900416 +9711082Z;Lycée Polyvalent Nord Grande Terre;Port Louis;97117;Port Louis;47.7107928;-3.3642883 +0142133T;Lycée Polyvalent P. S. De Laplace;130 Rue De La Delivrande;14075;Caen;49.1961605;-0.3600384 +0910727W;Lycée Polyvalent Parc De Vilgenis;80 Rue De Versailles;91305;Massy;48.7325208;2.257078 +0360005K;Lycée Polyvalent Pasteur;12 Boulevard Fr Mitterrand;36300;Le Blanc;48.9090111;2.4475893 +0040023D;Lycée Polyvalent Paul Arene;13 Avenue Du Stade;04203;Sisteron;44.1830487;5.9474019 +0030026M;Lycée Polyvalent Paul Constans;Rue Christophe Thivrier;03107;Montlucon;46.3359499;2.577509 +0940119U;Lycée Polyvalent Paul Doumer;2 Rue Paul Doumer;94170;Le Perreux Sur Marne;48.8336998;2.5088853 +0870040W;Lycée Polyvalent Paul Eluard;Bellvue De Glane;87205;St Junien;45.8951323;0.8918402 +0930125F;Lycée Polyvalent Paul Eluard;15 Et 17 Avenue Jean Moulin;93206;St Denis;48.941642;2.364800 +0391092A;Lycée Polyvalent Paul Emile Victor;625 Avenue De Gottmadingen;39303;Champagnole;46.7427137;5.922959 +0130143K;Lycée Polyvalent Paul Langevin;131 Avenue Dr Alexander Fleming;13691;Martigues;43.404811;5.053728 +0912163G;Lycée Polyvalent Paul Langevin;Rue Paul Langevin;91706;Ste Genevieve Des Bois;48.6281114;2.3335769 +0932229T;Lycée Polyvalent Paul Le Rolland;136 Avenue De Castelnau;93700;Drancy;48.9245764;2.461786 +0010034G;Lycée Polyvalent Paul Painleve;13 Place Des Deportes;01108;Oyonnax;46.256308;5.6597797 +0750558Z;Lycée Polyvalent Paul Poiret;19 Rue Des Taillandiers;75011;Paris 11;48.8546499;2.3750458 +0932073Y;Lycée Polyvalent Paul Robert;2-4 Rue Du Chateau;93260;Les Lilas;48.8834638;2.4213298 +0290022S;Lycée Polyvalent Paul Serusier;Av De Waldkappel;29837;Carhaix Plouguer;48.2788842;-3.5441266 +0951937P;Lycée Polyvalent Paul-emile Victor;116 Rue De Livilliers;95520;Osny;49.0723746;2.0700655 +0382895R;Lycée Polyvalent Philibert Delorme;68 Boulevard Saint Hubert;38081;L Isle D Abeau;45.6224977;5.2225335 +0520019N;Lycée Polyvalent Philippe Lebon;11 Rue De Sprendlingen;52301;Joinville;48.4440729;5.1395217 +0941975L;Lycée Polyvalent Pierre Brossolette;5 Rue Pierre Brossolette;94270;Le Kremlin Bicetre;48.8150108;2.3595076 +0290030A;Lycée Polyvalent Pierre Gueguin;Le Porzou;29182;Concarneau;47.8582636;-3.8778006 +9741263E;Lycée Polyvalent Pierre Lagourgue;30 Chemin Mazeau Trois Mares;97430;Le Tampon;-21.25766;55.50818 +0020034B;Lycée Polyvalent Pierre Mechain;19 Rue Leo Lagrange;02011;Laon;49.5587989;3.6319641 +0133015G;Lycée Polyvalent Pierre Mendes France;Avenue Yitzhak Rabin;13741;Vitrolles;43.448548;5.246601 +0580014A;Lycée Polyvalent Pierre-gilles De Gennes;Rue Du Colonel Rabier;58206;Cosne Cours Sur Loire;47.400273;2.92962 +9711033W;Lycée Polyvalent Pointe Noire;Grande Plaine;97116;Pointe Noire;-4.1428413;11.8891721 +0760054W;Lycée Polyvalent Porte Oceane;44 Rue Emile Zola;76090;Le Havre;49.4885624;0.1055085 +6200063Z;Lycée Polyvalent Porto Vecchio;Rte De L'ospedale;20137;Porto Vecchio;41.652522;9.193939 +0210003P;Lycée Polyvalent Prieur De La Cote D'or;6 Rue Vauban;21130;Auxonne;47.1947391;5.3866255 +9730328U;Lycée Polyvalent Privé A.m.javouhey Cayenne;Cite Des Manguiers;97328;Cayenne;4.9332324;-52.3234507 +9720479N;Lycée Polyvalent Privé Adventiste Rama;Cite Scolaire Adventiste Rama;97228;Ste Luce;14.4904216;-60.9530711 +0754030Y;Lycée Polyvalent Privé Albert De Mun;2 Rue D'olivet;75007;Paris 07;48.8497255;2.3195956 +9720615L;Lycée Polyvalent Privé Amep;183 Route De Redoute;97204;Fort De France;14.6282525;-61.0579894 +0430098Y;Lycée Polyvalent Privé Anne Marie Martel;2 Et 9 Rue De Vienne;43002;Le Puy En Velay;45.046138;3.888936 +0110670U;Lycée Polyvalent Privé Beausejour;16 Rue Michelet;11108;Narbonne;43.1855901;3.0063747 +0490840X;Lycée Polyvalent Privé Bourg Chevreau Ste Anne;7 Rue Du 8 Mai 1945;49504;Segre;47.6814632;-0.8721799 +0754029X;Lycée Polyvalent Privé Carcado Saisseval;121 Boulevard Raspail;75006;Paris 06;48.8453132;2.3288349 +0754045P;Lycée Polyvalent Privé Catherine Laboure;29 Rue Gassendi;75014;Paris 14;48.8335807;2.3258612 +0711896A;Lycée Polyvalent Privé Catholique Chalonnais;3 Rue General Giraud;71100;Chalon Sur Saone;46.7911722;4.8574959 +9741255W;Lycée Polyvalent Privé Catholique St Charles;Rue Auguste Babet;97458;St Pierre;-21.33756;55.47994 +0762442S;Lycée Polyvalent Privé Daniel Brottier;11 Impasse Du Chateau;76660;Smermesnil;49.8063368;1.4206513 +0300112J;Lycée Polyvalent Privé De La Cci;1 Ter Avenue General Leclerc;30020;Nimes;43.832345;4.3683606 +0150047V;Lycée Polyvalent Privé De La Communication St Geraud;23 Rue Du College;15013;Aurillac;44.9314468;2.4464301 +9830377T;Lycée Polyvalent Privé Do Kamo (asee);15 Bis Rue Taragnat;98800;Noumea;-22.2742559;166.4556316 +0711755X;Lycée Polyvalent Privé Du Sacre Coeur;22 Avenue De Charolles;71604;Paray Le Monial;46.4510984;4.124986 +0311134P;Lycée Polyvalent Privé Emilie De Rodat;25 Avenue De Lombez;31027;Toulouse;43.5959637;1.4207585 +0170100S;Lycée Polyvalent Privé Fenelon Notre-dame;36 Rue Massiou;17005;La Rochelle;46.1652912;-1.1504523 +0371184L;Lycée Polyvalent Privé Fontiville;77 Rue De Fontiville;37250;Veigne;47.2973253;0.7387831 +0711249X;Lycée Polyvalent Privé Frederic Ozanam;45 Rue De L'heritan;71031;Macon;46.3115653;4.8261909 +0951998F;Lycée Polyvalent Privé G A R A C;3 Boulevard Gallieni;95102;Argenteuil;48.9438075;2.2446175 +0150760V;Lycée Polyvalent Privé Gerbert;47 Avenue Des Prades;15000;Aurillac;44.9163841;2.4366131 +0761359P;Lycée Polyvalent Privé Germaine Coty;86 Rue De Chateaudun;76620;Le Havre;49.5152892;0.1198095 +0530068L;Lycée Polyvalent Privé Haute Follis;91 Rue Haute Follis;53000;Laval;48.0740234;-0.7854105 +0440119R;Lycée Polyvalent Privé Hotelier Ste Anne;250 Boulevard Laennec;44615;St Nazaire;47.2752568;-2.2350718 +0060750H;Lycée Polyvalent Privé Institution St Joseph Carnoles;191 Avenue Aristide Briand;06190;Roquebrune Cap Martin;43.7649927;7.4842023 +0350801F;Lycée Polyvalent Privé Jean Paul Ii;2 Rue Antoine De Saint Exupery;35762;St Gregoire;45.5157091;2.3704006 +0440257R;Lycée Polyvalent Privé Jeanne Bernard-bel Air;48 Avenue De La Baraudiere;44800;St Herblain;47.2355437;-1.5912533 +0010878Z;Lycée Polyvalent Privé Jeanne D'arc;95 Rue Du Lycée;01170;Cessy;0.0;0.0 +0350808N;Lycée Polyvalent Privé Jeanne D'arc;13 Place De La Republique;35503;Vitre;48.124893;-1.2086706 +0460039T;Lycée Polyvalent Privé Jeanne D'arc;51 Bd Colonel Teulie;46100;Figeac;44.6122989;2.0341249 +0761735Y;Lycée Polyvalent Privé Jeanne D'arc;22 Rue Du General De Gaulle;76310;Ste Adresse;49.501971;0.0892924 +0850136L;Lycée Polyvalent Privé Jeanne D'arc;3 Bis Bd Raymond Parpaillon;85603;Montaigu;46.9798514;-1.3120818 +0890064L;Lycée Polyvalent Privé Jeanne D'arc;69 Grande Rue Aristide Briand;89200;Avallon;47.4869016;3.9071141 +0490904S;Lycée Polyvalent Privé Jeanne Delanoue;11 Boulevard Jeanne D Arc;49304;Cholet;47.0640598;-0.8673109 +0480706G;Lycée Polyvalent Privé Joseph Gibelin;43 Avenue De La Gare;48200;St Chely D Apcher;44.8001253;3.273392 +0754015G;Lycée Polyvalent Privé Jules Richard Microtechniques;21 Rue Carducci;75019;Paris 19;48.8774956;2.3855772 +0611147R;Lycée Polyvalent Privé L Esperance;Boulevard Du Nord;61800;Tinchebray;48.764231;-0.736472 +0440279P;Lycée Polyvalent Privé La Baugerie;38 Bd Des Pas Enchantes;44232;St Sebastien Sur Loire;47.2100641;-1.5019567 +0761343X;Lycée Polyvalent Privé La Chataigneraie;2 Rue Charles Scherer;76240;Le Mesnil Esnard;49.4132672;1.1461844 +0342175E;Lycée Polyvalent Privé La Merci Littoral;603 Av De La Petite Motte;34280;La Grande Motte;43.5656148;4.0735973 +0150051Z;Lycée Polyvalent Privé La Presentation Notre Dame;1 Cours Spy Des Ternes;15100;St Flour;45.0344847;3.0899936 +0351930H;Lycée Polyvalent Privé La Providence;21 Rue De Rennes;35360;Montauban De Bretagne;48.1983239;-2.0463959 +0410693F;Lycée Polyvalent Privé La Providence;23 Rue Des Saintes Maries;41034;Blois;47.5930538;1.3304792 +0754042L;Lycée Polyvalent Privé Le Rebours;44 Boulevard Auguste Blanqui;75013;Paris 13;48.8301339;2.3505104 +0422160V;Lycée Polyvalent Privé Le Renouveau;All Des Bois Chateau Colcombet;42530;St Genest Lerpt;45.452188;4.341445 +0211091X;Lycée Polyvalent Privé Les Arcades;13 Rue Du Vieux College;21000;Dijon;47.3200096;5.0457552 +0761341V;Lycée Polyvalent Privé Les Tourelles;53 Rue Verte;76000;Rouen;49.4502554;1.0928484 +0580071M;Lycée Polyvalent Privé L'esperance;10 Cloitre St Cyr;58000;Nevers;46.9862254;3.1562197 +0830101J;Lycée Polyvalent Privé Maintenon;10 Boulevard Pasteur;83409;Hyeres;43.1200536;6.1340928 +0190083M;Lycée Polyvalent Privé Marguerite Bahuet;41bis Av Michelet St Antoine;19316;Brive La Gaillarde;45.14663;1.530476 +0370881G;Lycée Polyvalent Privé Marmoutier;17 Quai Marmoutier;37100;Tours;47.4018774;0.7172022 +0660552S;Lycée Polyvalent Privé Maso;7 Avenue Des Palmiers;66000;Perpignan;42.6997916;2.8874756 +0921484N;Lycée Polyvalent Privé Montalembert;238 Boulevard Saint Denis;92400;Courbevoie;48.903176;2.2760713 +0311138U;Lycée Polyvalent Privé Montalembert Ntre-dame;152 Avenue De Lespinet;31400;Toulouse;43.5761758;1.4709404 +0850130E;Lycée Polyvalent Privé Nd Du Roc;Rue Charlemagne;85035;La Roche Sur Yon;46.6839572;-1.4199783 +0340991T;Lycée Polyvalent Privé Nevers;18 Rue De La Garenne;34090;Montpellier;43.6204369;3.8692299 +0440274J;Lycée Polyvalent Privé Notre Dame;50 Rue Jean Jaures;44401;Reze;47.1877269;-1.5471117 +0850142T;Lycée Polyvalent Privé Notre Dame;Rue De Bois Fosse;85304;Challans;46.832239;-1.881754 +0851344Z;Lycée Polyvalent Privé Notre Dame;29 Rue Rabelais;85205;Fontenay Le Comte;46.467908;-0.8142398 +0290206S;Lycée Polyvalent Privé Notre Dame De Kerbertrand;154 Rue De Pont-aven;29391;Quimperle;47.8710014;-3.5663631 +0190062P;Lycée Polyvalent Privé Notre Dame De La Providence;19 Rue General De Gaulle;19200;Ussel;45.5495205;2.3075733 +0080091F;Lycée Polyvalent Privé Notre-dame;1 Place De La Basilique;08000;Charleville Mezieres;49.7607798;4.7162174 +0271070S;Lycée Polyvalent Privé Notre-dame;7 Rue Du Chantier;27000;Evreux;49.0203676;1.1525311 +0650885J;Lycée Polyvalent Privé Notre-dame De Garaison;2 Route Cier Garaison;65670;Monleon Magnoac;43.2066692;0.5032436 +0951994B;Lycée Polyvalent Privé O R T;32 Avenue De Choiseul;95400;Villiers Le Bel;48.9940718;2.4104291 +0520685M;Lycée Polyvalent Privé Oudinot;Route De Neuilly;52000;Chaumont;48.0843712;5.1460026 +0391146J;Lycée Polyvalent Privé Pasteur Mont Roland;;39107;Dole;46.9583818;5.5032587 +0751712D;Lycée Polyvalent Privé Petrelle;8 Rue Petrelle;75009;Paris 09;48.8806327;2.3486726 +0761356L;Lycée Polyvalent Privé Providence Misericorde;42 Rue De Le Nostre;76020;Rouen;49.4441716;1.0794378 +0440246D;Lycée Polyvalent Privé Sacre Coeur;3 Rue Francis Portais;44100;Nantes;47.2080132;-1.6088332 +0480022N;Lycée Polyvalent Privé Sacre Coeur;1 Rue Du College;48300;Langogne;44.7256821;3.8539001 +0790078F;Lycée Polyvalent Privé Saint Andre;14 Rue De Souche;79007;Niort;46.3274523;-0.4445119 +0211090W;Lycée Polyvalent Privé Saint Benigne;99 Rue De Talant;21000;Dijon;47.3310729;5.0153828 +0360505D;Lycée Polyvalent Privé Saint Cyr;1 Rue Lecherbonnier;36101;Issoudun;46.951712;1.995903 +0890067P;Lycée Polyvalent Privé Saint Etienne;2 Rue Louise Et Leon Vernis;89100;Sens;48.204667;3.294032 +0450106W;Lycée Polyvalent Privé Saint Francois De Sales;66 Rue Paul Bert;45504;Gien;47.6882519;2.6252631 +0370748M;Lycée Polyvalent Privé Saint Gatien;107 Rue De La Douzillere;37300;Joue Les Tours;47.3353239;0.6524983 +0060774J;Lycée Polyvalent Privé Saint Joseph;14 Rue Barla;06300;Nice;43.7018025;7.2828387 +0211089V;Lycée Polyvalent Privé Saint Joseph;39 Rue Du Transvaal;21010;Dijon;47.3153813;5.0377019 +0410954P;Lycée Polyvalent Privé Saint Joseph;18 Rue Lemyre De Villers;41100;Vendome;47.792551;1.065343 +0580778F;Lycée Polyvalent Privé Saint Joseph;118 Bis Rue Des Montapins;58000;Nevers;46.977082;3.1350203 +0790080H;Lycée Polyvalent Privé Saint Joseph;4 Rue Du Docteur Brillaud;79302;Bressuire;46.8380755;-0.4907658 +0160083D;Lycée Polyvalent Privé Saint Joseph L'amandier;Allee Jean Pierre Gault;16710;St Yrieix Sur Charente;45.6931026;0.1181619 +0711494N;Lycée Polyvalent Privé Saint Lazare;7 Rue St Germain;71404;Autun;46.9493625;4.2955232 +0912161E;Lycée Polyvalent Privé Saint Leon;8 Quai De L Essonne;91100;Corbeil Essonnes;48.6088484;2.4825139 +0450107X;Lycée Polyvalent Privé Saint Louis;Au Chateau;45203;Montargis;48.0391073;2.7103188 +0450758E;Lycée Polyvalent Privé Saint Paul-bourdon Blanc;4 Rue Neuve Saint Aignan;45057;Orleans;47.8984756;1.9160429 +0610698C;Lycée Polyvalent Privé Saint Thomas D'aquin;1 Place C Duperron;61105;Flers;48.7491921;-0.5655076 +0212023K;Lycée Polyvalent Privé Saint Vincent De Paul;9 Rue E Humblot;21401;Chatillon Sur Seine;47.859253;4.5757386 +0831444U;Lycée Polyvalent Privé Sainte Jeanne D Arc;Avenue Dreo;83170;Brignoles;43.4059478;6.0668721 +0160079Z;Lycée Polyvalent Privé Sainte Marthe Chavagnes;51 Rue Du Minage;16000;Angouleme;45.6497236;0.15127 +0360693H;Lycée Polyvalent Privé Sainte Solange;1 Place De La Gare;36028;Chateauroux;46.810104;1.698855 +0660077A;Lycée Polyvalent Privé Sainte-louise-de-marillac;68 Avenue Victor Dalbiez;66000;Perpignan;42.6895911;2.8844606 +0311131L;Lycée Polyvalent Privé Sainte-therese;16 Rue Du Bugatet;31804;St Gaudens;43.1061649;0.7217848 +0753217P;Lycée Polyvalent Privé Sainte-therese;40 Rue La Fontaine;75016;Paris 16;48.8509879;2.2719157 +0341523W;Lycée Polyvalent Privé Saint-joseph;11 Avenue Marx Dormoy;34202;Sete;43.4038819;3.6931944 +0480023P;Lycée Polyvalent Privé Saint-joseph;2 Rue Des Penitents;48100;Marvejols;44.5542067;3.2915228 +0500126R;Lycée Polyvalent Privé Saint-joseph;25 Rue Des Ecoles;50800;Villedieu Les Poeles;48.8370642;-1.2167469 +0754025T;Lycée Polyvalent Privé Saint-nicolas;92 Rue De Vaugirard;75006;Paris 06;48.8468268;2.3260737 +0110045P;Lycée Polyvalent Privé Saint-stanislas;77 Rue Aime Ramond;11000;Carcassonne;43.2118475;2.3495443 +0580076T;Lycée Polyvalent Privé Simone Dounon;5 Rue Marcelin Berthelot;58200;Cosne Cours Sur Loire;47.4125079;2.9241087 +0720818Y;Lycée Polyvalent Privé St Charles;75 Avenue Bollee;72000;Le Mans;48.0020737;0.2075092 +0350793X;Lycée Polyvalent Privé St Etienne;99 Rue De La Chalotais;35512;Cesson Sevigne;48.1295633;-1.597344 +0440247E;Lycée Polyvalent Privé St Felix;27 Rue Du Ballet;44001;Nantes;47.2316606;-1.560045 +0610692W;Lycée Polyvalent Privé St Francois De Sales;100 Rue Labillardiere;61007;Alencon;48.430496;0.0965975 +0440253L;Lycée Polyvalent Privé St Jb De La Salle;14 Rue Du Ballet;44010;Nantes;47.2317713;-1.5611167 +0180571Y;Lycée Polyvalent Privé St Jean Baptiste De La Salle;52 Avenue De La Liberation;18020;Bourges;47.1051882;2.4041241 +0912117G;Lycée Polyvalent Privé St Jean St Paul;1 Av De Ratisbonne;91000;Evry;48.6437303;2.4357094 +0240081H;Lycée Polyvalent Privé St Joseph;Boulevard Eugene Leroy;24205;Sarlat La Caneda;44.8902364;1.2144075 +0290184T;Lycée Polyvalent Privé St Joseph;Route De Pencran;29413;Landerneau;48.4479875;-4.2370715 +0441653H;Lycée Polyvalent Privé St Joseph La Joliverie;141 Route De Clisson;44232;St Sebastien Sur Loire;47.1897277;-1.5177365 +0721478R;Lycée Polyvalent Privé St Joseph Lorraine;Le Pizieux;72700;Pruille Le Chetif;47.984176;0.1387529 +0490888Z;Lycée Polyvalent Privé St Julien La Baronnerie;Rue Helene Boucher;49481;St Sylvain D Anjou;47.487831;-0.496899 +0850137M;Lycée Polyvalent Privé St Louis;104 Rue Pierre Brossolette;85007;La Roche Sur Yon;46.6861278;-1.4404186 +0511145T;Lycée Polyvalent Privé St Michel;39 Rue Martin Peller;51100;Reims;49.2466602;4.0133052 +0910843X;Lycée Polyvalent Privé St Pierre;70 Rue De Montgeron;91800;Brunoy;48.6945366;2.4899797 +0440256P;Lycée Polyvalent Privé St Pierre La Joliverie;141 Route De Clisson;44232;St Sebastien Sur Loire;47.1897277;-1.5177365 +0300127A;Lycée Polyvalent Privé St Vincent De Paul;3 Bd De Bruxelles;30020;Nimes;43.834096;4.362298 +0511147V;Lycée Polyvalent Privé St Vincent De Paul;1 Rue De La Fraternite;51001;Chalons En Champagne;48.9779739;4.3614798 +0720843A;Lycée Polyvalent Privé Ste Anne;5 Rue Alain De Rouge;72305;Sable Sur Sarthe;47.8412381;-0.3363364 +0560119A;Lycée Polyvalent Privé Ste Anne-st Louis;2 Route De Locmaria;56400;Ste Anne D Auray;47.7047103;-2.9507241 +0720822C;Lycée Polyvalent Privé Ste Catherine;202 Rue De St Aubin;72000;Le Mans;48.0222385;0.1782785 +0451335G;Lycée Polyvalent Privé Ste Croix St Euverte;28 Rue De L Etelon;45043;Orleans;47.9025554;1.9171035 +0850135K;Lycée Polyvalent Privé Ste Marie;12 Place Jeanne D Arc;85111;Chantonnay;46.6862118;-1.0522409 +0850133H;Lycée Polyvalent Privé Ste Marie Du Port;La Meriniere;85108;Les Sables D Olonne;46.509488;-1.765298 +0350840Y;Lycée Polyvalent Privé Ste Therese;27 Rue Sully Prudhomme;35000;Rennes;48.0948205;-1.6669327 +0850077X;Lycée Polyvalent Privé Ste Ursule;56 Rue Georges Clemenceau;85403;Lucon;46.4577364;-1.1672514 +0440252K;Lycée Polyvalent Privé Talensac;18 Rue De Talensac;44002;Nantes;47.2218408;-1.5585936 +9720782T;Lycée Polyvalent Privé Techno-compta;188 Avenue Maurice Bishop;97200;Fort De France;14.6087039;-61.0560875 +0931026K;Lycée Polyvalent Protectorat Saint Joseph;36 Rue Jacques Duclos;93600;Aulnay Sous Bois;48.9456045;2.4946283 +0530949U;Lycée Polyvalent Raoul Vadepied;Boulevard Rossignol;53602;Evron;48.1517259;-0.40075 +0310040A;Lycée Polyvalent Raymond Naves;139 Route D'albi;31018;Toulouse;43.6371858;1.4645232 +0762880T;Lycée Polyvalent Raymond Queneau;Rue Du Docteur Zamenhof;76194;Yvetot;49.613437;0.769068 +0830007G;Lycée Polyvalent Raynouard;Rue G. Pelissier;83170;Brignoles;43.40655;6.061187 +0922149L;Lycée Polyvalent Rene Auffray;23 Rue Fernand Pelloutier;92582;Clichy;48.9033466;2.3000039 +0710048S;Lycée Polyvalent Rene Cassin;49 Boulevard Des Neuf Cles;71018;Macon;46.3147768;4.8211064 +0771941S;Lycée Polyvalent Rene Cassin;1 Av P Mendes Frances Noisiel;77426;Champs Sur Marne;48.8425281;2.6013372 +0932222K;Lycée Polyvalent Rene Cassin;16 Allee Des Bosquets;93340;Le Raincy;48.9008371;2.5202268 +0772223Y;Lycée Polyvalent Rene Descartes;4 Boulevard Copernic;77420;Champs Sur Marne;48.844743;2.5858742 +0500049G;Lycée Polyvalent Robert De Mortain;30 Rue De La 30e Div Americaine;50140;Mortain;48.649222;-0.941546 +0720017C;Lycée Polyvalent Robert Garnier;Avenue Du General De Gaulle;72405;La Ferte Bernard;48.184306;0.648803 +0741669M;Lycée Polyvalent Roger Frison Roche;Promenade Du Fori;74401;Chamonix Mont Blanc;45.928101;6.872169 +9740002J;Lycée Polyvalent Roland Garros;Rue Roland Garros;97839;Le Tampon;-21.2735691;55.5219446 +0580008U;Lycée Polyvalent Romain Rolland;5 Rue Pablo Neruda;58503;Clamecy;47.4566989;3.5317777 +0950667J;Lycée Polyvalent Romain Rolland;21 Av De Montmorency;95190;Goussainville;49.0299597;2.453191 +0660856X;Lycée Polyvalent Rosa Luxemburg;2 Avenue Jean Moulin;66141;Canet En Roussillon;42.7039851;3.021471 +0831616F;Lycée Polyvalent Rouviere;Quartier Sainte Musse;83070;Toulon;43.124228;5.928 +0771238C;Lycée Polyvalent Saint Aspais;36 Rue Saint Barthelemy;77007;Melun;48.542074;2.6558301 +0930936M;Lycée Polyvalent Saint Benoist De L'europe;82 Avenue Gambetta;93172;Bagnolet;48.870888;2.4176316 +0640057P;Lycée Polyvalent Saint Cricq;4 B Av Des Etats Unis;64015;Pau;43.2982785;-0.3595802 +0890070T;Lycée Polyvalent Saint Joseph;1 Bd De La Marne;89015;Auxerre;47.811752;3.56117 +0771246L;Lycée Polyvalent Sainte Genevieve;12 Rue De La Visitation;77109;Meaux;48.9653532;2.8843027 +0772153X;Lycée Polyvalent Sainte Marie;41 Rue De Chaage;77109;Meaux;48.9658178;2.8806747 +0010006B;Lycée Polyvalent Saint-exupery;15 Avenue Saint-exupery;01206;Bellegarde Sur Valserine;46.1142691;5.8180076 +0312686B;Lycée Polyvalent Saint-exupery;1 Place Alain Savary;31703;Blagnac;43.6816094;1.4178833 +9730371R;Lycée Polyvalent Saint-laurent Ii;3 All?e Des Buissons Ardents;97393;St Laurent Du Maroni;0.0;0.0 +0281077U;Lycée Polyvalent Silvia Monfort;6 Rue De L Orme De Sours;28600;Luisant;48.4192711;1.4555464 +0772310T;Lycée Polyvalent Simone Signoret;Place Du 14 Juillet;77000;Vaux Le Penil;48.5310815;2.6726108 +0212015B;Lycée Polyvalent Simone Weil;1 Rue Pelletier De Chambure;21000;Dijon;47.3210003;5.0500088 +0783447Y;Lycée Polyvalent Simone Weil;Rue Du Val D Oise;78700;Conflans Ste Honorine;49.0066604;2.0870198 +0772332S;Lycée Polyvalent Sonia Delaunay;1 Rue Du Lycée;77240;Cesson;0.0;0.0 +0781952Y;Lycée Polyvalent Sonia Delaunay;Avenue De St Germain;78450;Villepreux;48.8301026;1.9931895 +0542208G;Lycée Polyvalent Stanislas;468 Rue De Vandoeuvre;54600;Villers Les Nancy;48.6636628;6.1530599 +0670114K;Lycée Polyvalent Stanislas;7 Rue Du Lycée;67163;Wissembourg;0.0;0.0 +9741052A;Lycée Polyvalent Stella;;97424;St Leu;48.8626934;2.3496713 +0932121A;Lycée Polyvalent Suger;6 Avenue Le Roy Des Barres;93200;St Denis;48.936181;2.357443 +0870019Y;Lycée Polyvalent Suzanne Valadon;39 Rue Francois Perrin;87032;Limoges;45.8293539;1.2447165 +0680016Y;Lycée Polyvalent Theodore Deck;5 Rue Des Chanoines;68504;Guebwiller;47.9061156;7.2133487 +0480688M;Lycée Polyvalent Theophile Roussel;15 Rue Du Docteur Yves Dalle;48200;St Chely D Apcher;44.8039562;3.2735377 +0770942F;Lycée Polyvalent Thibaut De Champagne;3 Rue Du College;77160;Provins;48.5612146;3.2921093 +0840918S;Lycée Polyvalent Val De Durance;Rte Etang De La Bonde;84123;Pertuis;43.694275;5.501843 +0511884W;Lycée Polyvalent Val De Murigny;2 Rue Vauban;51097;Reims;49.2237219;4.0277678 +0031082K;Lycée Polyvalent Valery Larbaud;8 Boulevard Gabriel Peronnet;03306;Cusset;46.126235;3.445922 +0290012F;Lycée Polyvalent Vauban;Rue De Kerichen;29801;Brest;48.4284436;-4.4688478 +0623981R;Lycée Polyvalent Vauban;1 Rue De Bretagne;62120;Aire Sur La Lys;50.6333856;2.4090237 +0781984H;Lycée Polyvalent Vaucanson;14 Rue Albert Thomas;78132;Les Mureaux;48.982369;1.923312 +0390027T;Lycée Polyvalent Victor Berard;35 Quai Aime Lamy;39403;Morez;46.5171316;6.0256123 +0650005C;Lycée Polyvalent Victor Duruy;Allees Jean Jaures;65201;Bagneres De Bigorre;43.0637029;0.153508 +0342090M;Lycée Polyvalent Victor Hugo;;34400;Lunel;43.67445;4.135366 +0330126H;Lycée Polyvalent Victor Louis;2 Avenue De Thouars;33405;Talence;44.8017092;-0.593576 +0070021K;Lycée Polyvalent Vincent D'indy;9 Boulevard Du Lycée;07006;Privas;0.0;0.0 +0781859X;Lycée Polyvalent Vincent Van Gogh;Rue Jules Ferry;78410;Aubergenville;48.954462;1.855006 +9830483H;Lycée Polyvalent Williama Haudra;;98820;Lifou;-22.2518502;166.4523382 +0010032E;Lycée Polyvalent Xavier Bichat;Avenue Du Lac;01130;Nantua;46.1557254;5.601148 +0071351F;Lycée Polyvalent Xavier Mallet;Rue Frederic Mistral;07400;Le Teil;44.5379585;4.6891329 +0250043L;Lycée Polyvalent Xavier Marmier;53 Rue De Doubs;25304;Pontarlier;46.9158344;6.3514785 +0622196A;Lycée Privé Baudimont Saint-charles;17 Rue Saint Maurice;62008;Arras;50.2937988;2.770153 +0910819W;Lycée Privé Cours Secondaire;11 Rue De Courtaboeuf;91400;Orsay;48.6946177;2.1908145 +0451565G;Lycée Privé De L Horticulture Et Du Paysage D Orleans;66 Avenue De La Mouillere;45072;Orleans;47.881944;1.9102444 +0430968U;Lycée Privé Europeen St-jacques De Compostelle;4-10 Boulevard Montferrand;43000;Le Puy En Velay;45.0473977;3.8821493 +0291574D;Lycée Privé Hortic Et Paysage De Kerbernez;Kerbernez;29700;Plomelin;47.943747;-4.129242 +0593225K;Lycée Privé Lycée Privé De Marcq;170 Rue Du College;59700;Marcq En Baroeul;50.6829913;3.1036007 +0753825A;Lycée Privé Pour Handicapes Morvan;68 Rue De La Chaussee D'antin;75009;Paris 09;48.8758516;2.3320853 +0754089M;Lycée Privé Pour Handicapes Votre Ecole Chez Vous;29 Rue Merlin;75011;Paris 11;48.8613416;2.3848162 +0011058V;Lycée Privé Prof Rural Le Molard;10 Place De La Halle;01150;St Sorlin En Bugey;45.9040178;5.3485298 +0592921E;Lycée Privé Saint Remi;10 Rue Notre Dame Des Victoires;59100;Roubaix;50.6950525;3.1805112 +0641535W;Lycée Prof Agric Horticol Pr Armand David;1 Route De Mssionnaires;64240;Hasparren;43.3811075;-1.3049408 +0601768W;Lycée Prof Agric Privé Du Valois;15 Rue D'ognes;60440;Nanteuil Le Haudouin;49.136273;2.813378 +0400784K;Lycée Prof Agricole Pr;Au Rayon;40230;Saubrigues;43.610771;-1.314267 +0351958N;Lycée Prof Agricole Privé;26 Rue Du Chatelet;35605;Redon;47.6473408;-2.0925548 +0351960R;Lycée Prof Agricole Privé;15 Rue Du Bourg Au Loup;35140;St Aubin Du Cormier;48.2568795;-1.396259 +0561460H;Lycée Prof Agricole Privé;20 Place Du Docteur Queinnec;56140;Malestroit;47.8099502;-2.3846483 +0561464M;Lycée Prof Agricole Privé;Rue Du Chanoine Niol;56230;Questembert;47.663927;-2.454126 +0351947B;Lycée Prof Agricole Privé Edmond Michelet;Parc De Montaubert;35300;Fougeres;48.3572455;-1.2009328 +0280946B;Lycée Prof Agricole Privé Efagrir;2 Rue Des Fleurs;28630;Mignieres;48.3595403;1.4288729 +0280942X;Lycée Prof Agricole Privé Gabriel Bridet;40 Rte Hubert Baraine;28260;Anet;48.8526049;1.435093 +0351943X;Lycée Prof Agricole Privé La Noe St Yves;Route De Chateaubriant;35470;Bain De Bretagne;47.8025633;-1.7386898 +0221677D;Lycée Prof Agricole Privé Le Restmeur;Pabu;22200;Pabu;48.7057532;-3.252573 +0221675B;Lycée Prof Agricole Privé Notre Dame;Rue St Vincent;22290;Lanvollon;48.630202;-2.9889739 +0291573C;Lycée Prof Agricole Privé Pierre Tremintin;2 Rue St Pol;29430;Plouescat;48.6583593;-4.1696711 +0561456D;Lycée Prof Agricole Privé St Yves;2 Rue De La Liberation;56110;Gourin;48.1407693;-3.6033035 +0221671X;Lycée Prof Agricole Privé Ste Marie;29 Rue De La Barriere;22250;Broons;48.3221871;-2.2642991 +0291575E;Lycée Prof Agricole Privé Ste Marie;Rue Du 11 Novembre;29610;Plouigneau;48.5651547;-3.6991958 +0690275Z;Lycée Prof Agricole Viticole;37 Route De Beaujeu;69220;St Jean D Ardieres;46.1136651;4.7273837 +0221670W;Lycée Prof Horticole Privé;Coat An Doc'h;22170;Lanrodec;48.532569;-3.033864 +0941724N;Lycée Prof Privé Des Metiers Batiment Et Travaux Publics;18 Rue De Belfort;94307;Vincennes;48.8501369;2.4311329 +0332495H;Lycée Prof Privé Des Metiers Bel Orme;67 Rue Bel Orme;33000;Bordeaux;44.8497536;-0.5941378 +0693373S;Lycée Prof Privé Des Metiers Carrel;7 Rue Pierre Robin;69362;Lyon 07;45.7493853;4.8581918 +0290200K;Lycée Prof Privé Des Metiers Des Metiers Le Porsmeur;4 Rue Du Docteur Prouff;29678;Morlaix;48.5769099;-3.8335892 +0420998G;Lycée Prof Privé Des Metiers Des Monts Du Lyonnais;9 Rue De Montbrison;42140;Chazelles Sur Lyon;45.6376681;4.3846408 +0693371P;Lycée Prof Privé Des Metiers Don Bosco;12 Et 24 Montee Saint Laurent;69322;Lyon 05;45.7510345;4.8167844 +0931369H;Lycée Prof Privé Des Metiers Francoise Cabrini;20 Rue Du Docteur Sureau;93167;Noisy Le Grand;48.8461618;2.5491228 +0312062Y;Lycée Prof Privé Des Metiers Issec Pigier;36 Rue Alsace Lorraine;31000;Toulouse;43.605598;1.445689 +0690712Z;Lycée Prof Privé Des Metiers Jamet Buffereau;17 Rue Des Chartreux;69283;Lyon 01;45.7720374;4.8233234 +0292140U;Lycée Prof Privé Des Metiers La Croix Rouge;2 Rue Mirabeau;29229;Brest;48.4075125;-4.4894481 +0693374T;Lycée Prof Privé Des Metiers La Mache;75 Boulevard Jean Xxiii;69373;Lyon 08;45.7364603;4.8695511 +0801946F;Lycée Prof Privé Des Metiers La Providence;146 Bd De Saint Quentin;80094;Amiens;49.8789518;2.3007807 +0292144Y;Lycée Prof Privé Des Metiers Le Likes;20 Place De La Tourbie;29196;Quimper;47.9996048;-4.1024423 +0420991Z;Lycée Prof Privé Des Metiers Le Marais Sainte Therese;48 Boulevard Thiers;42000;St Etienne;45.4507808;4.3896969 +0121429N;Lycée Prof Privé Des Metiers Louis Querbes;29 Rue Maurice Bompard;12000;Rodez;44.3485775;2.5792732 +0220122N;Lycée Prof Privé Des Metiers Montbareil;2 Rue Marechal Joffre;22200;Guingamp;48.5632365;-3.1498718 +0070114L;Lycée Prof Privé Des Metiers Notre Dame;26 Avenue Du Vanel;07000;Privas;44.7358608;4.6056151 +0693375U;Lycée Prof Privé Des Metiers Notre Dame;72 Rue Des Jardiniers;69657;Villefranche Sur Saone;45.9885367;4.7206723 +0810113H;Lycée Prof Privé Des Metiers Notre-dame;Avenue D'hauterive;81101;Castres;43.599188;2.244541 +0690653K;Lycée Prof Privé Des Metiers Orsel;30 Rue Orsel;69600;Oullins;45.7164792;4.8123384 +0691680B;Lycée Prof Privé Des Metiers Saint Charles;2831 Route De Strasbourg;69140;Rillieux La Pape;45.820291;4.8954392 +0021999M;Lycée Prof Privé Des Metiers Saint Joseph;4 Place Thiers;02402;Chateau Thierry;49.0428865;3.4009902 +0690641X;Lycée Prof Privé Des Metiers Saint Marc;4 Rue Sainte Helene;69287;Lyon 02;45.7554117;4.828113 +0801948H;Lycée Prof Privé Des Metiers Saint Martin;68 Rue Delpech;80043;Amiens;49.8842672;2.29491 +0801950K;Lycée Prof Privé Des Metiers Saint Remi;4 Rue Des Sergents;80006;Amiens;49.8942788;2.2987524 +0421006R;Lycée Prof Privé Des Metiers Saint Vincent;Place De L'egalite;42400;St Chamond;45.4798433;4.5132717 +0421980Z;Lycée Prof Privé Des Metiers Sainte Barbe;10 Rue Franklin;42028;St Etienne;45.4285207;4.3845892 +0420985T;Lycée Prof Privé Des Metiers Sainte Claire;8 Rue Des Parottes;42450;Sury Le Comtal;45.5360878;4.187921 +0420984S;Lycée Prof Privé Des Metiers Sainte Marie;39 Rue Des Freres Chappe;42007;St Etienne;45.4399091;4.395858 +0020498F;Lycée Prof Privé Des Metiers Sainte Sophie;22 Rue Curie;02110;Bohain En Vermandois;49.9830328;3.4565714 +0650886K;Lycée Prof Privé Des Metiers Saint-pierre;24 Avenue D'azereix;65000;Tarbes;43.2289169;0.0578127 +0290202M;Lycée Prof Privé Des Metiers St Gabriel;Rue Jean Lautredou;29125;Pont L Abbe;47.8619569;-4.216529 +0020492Z;Lycée Prof Privé Des Metiers St Vincent De Paul;13 Avenue De Reims;02205;Soissons;49.3754463;3.3353105 +0421742R;Lycée Prof Privé Des Metiers Tezenas Du Montcel;14 Place Girodet;42000;St Etienne;45.4480193;4.3809644 +0352446U;Lycée Prof Privé Hotelier Ste Therese;18 Rue Du Four;35130;La Guerche De Bretagne;47.9429138;-1.2313142 +0011057U;Lycée Prof Privé Rural De L'ain;5 Rue Docteur Levrat;01130;Nantua;46.1537153;5.6035237 +0351962T;Lycée Prof Rural Privé;50 Rue Nationale;35190;Tinteniac;48.3282833;-1.8332082 +0561458F;Lycée Prof Rural Privé Ker Anna;3 Rue Ker Anna;56700;Kervignac;47.7614575;-3.238536 +0761744H;Lycée Prof. Agricole Annexe Du Pays De Bray;4 Ave Des Canadiens;76270;Neufchatel En Bray;49.7304107;1.4327269 +0430114R;Lycée Prof. Agricole Privé Isvt St Dominique Vals;La Roche Arnaud;43001;Le Puy En Velay;45.038381;3.891388 +0121293R;Lycée Prof.agricole Privé;14 Rue Frayssinous;12130;St Geniez D Olt;44.466538;2.9759538 +0211504W;Lycée Prof.agricole Privé;20 Rue Danton;21210;Saulieu;47.2773555;4.2315727 +0820724S;Lycée Prof.agricole Privé;30 Rue De La Republique;82500;Beaumont De Lomagne;43.8823822;0.9864743 +0771740Y;Lycée Prof.agricole Privé Assomption;2 Rue De Salins;77130;Forges;48.418206;2.960877 +0820725T;Lycée Prof.agricole Privé Clair Foyer Caussadais;Route De Negrepelisse;82300;Caussade;44.14681;1.5243137 +0711441F;Lycée Prof.agricole Privé De La Bresse;600 Avenue Fernand Point;71500;Louhans;46.6235063;5.2333128 +0441782Y;Lycée Prof.agricole Privé Grand Blottereau;34 Chemin Du Ponceau;44300;Nantes;47.224905;-1.51142 +0121291N;Lycée Prof.agricole Privé Horticole;Route De Villefranche;12390;Rignac;44.4130599;2.2824371 +0460504Y;Lycée Prof.agricole Privé Institut Alain De Solminihac;Rue Du 8 Mai;46220;Prayssac;44.5050085;1.1855864 +0580662E;Lycée Prof.agricole Privé Iperma;Domaine Laveyne;58330;St Saulge;47.104764;3.514389 +0441784A;Lycée Prof.agricole Privé Jean-baptiste Eriau;Espace Rohan;44150;Ancenis;47.365464;-1.177491 +0441794L;Lycée Prof.agricole Privé Kerguenec;;44350;St Molf;47.390932;-2.424955 +0341472R;Lycée Prof.agricole Privé Le Cep D Or;Avenue De La Piscine;34800;Clermont L Herault;43.6314047;3.4337484 +0341473S;Lycée Prof.agricole Privé Le Roc Blanc;1 Rue De L Albarede;34190;Ganges;43.9336912;3.7098028 +0491801S;Lycée Prof.agricole Privé Les Buissonnets;7 Boulevard Daviers;49100;Angers;47.4791568;-0.5584661 +0721328C;Lycée Prof.agricole Privé Les Horizons;15 Rue De Touraine;72220;St Gervais En Belin;47.8742353;0.2176217 +0441785B;Lycée Prof.agricole Privé Les Prateaux;28 Bis Rue Bizeul;44130;Blain;47.4756295;-1.769521 +0530815Y;Lycée Prof.agricole Privé Lp Rural Privé;Pannard;53500;Ernee;48.3039668;-0.9412869 +0881690J;Lycée Prof.agricole Privé Lycée Prof.rural;684 Rue De La Mairie;88270;Harol;48.1542309;6.247106 +0912126S;Lycée Prof.agricole Privé M.f.h.essonne Verte;Avenue Du 8 Mai 1945;91154;Etampes;48.4402827;2.1552271 +0341606L;Lycée Prof.agricole Privé Maurice Clavel;Rue De La Raffinerie;34110;Frontignan;43.4434006;3.7583253 +0721337M;Lycée Prof.agricole Privé Nazareth;;72340;Ruille Sur Loir;47.7497789;0.620518 +0721336L;Lycée Prof.agricole Privé Notre Dame;10 Rue Du Tertre;72400;La Ferte Bernard;48.1886105;0.6387134 +0781071R;Lycée Prof.agricole Privé Privé Le Notre;Domaine De Pinceloup;78120;Sonchamp;48.5868348;1.8825612 +0711439D;Lycée Prof.agricole Privé Reine Antier;Au Bourg;71620;St Martin En Bresse;46.340815;5.0723608 +0530813W;Lycée Prof.agricole Privé Robert Schuman;62 Rue De La Division Leclerc;53200;Chateau Gontier;47.8339799;-0.7164153 +0311843K;Lycée Prof.agricole Privé Rural;7 Place Des Marchands;31370;Rieumes;43.4127035;1.12037 +0311842J;Lycée Prof.agricole Privé Rural L'oustal;15 Place D'orleans;31380;Montastruc La Conseillere;43.7168431;1.5901694 +0441790G;Lycée Prof.agricole Privé Saint Joseph;40 Rue Michel Grimault;44141;Chateaubriant;47.7156138;-1.3765267 +0441789F;Lycée Prof.agricole Privé Saint Martin;Rte De La Foret;44270;Machecoul;46.9980392;-1.8028421 +0371259T;Lycée Prof.agricole Privé Sainte Jeanne D Arc;;37600;Verneuil Sur Indre;47.055817;1.042124 +0121292P;Lycée Prof.agricole Privé Vaxergues Charles Palies;198 Avenue Du Dr Galtier;12400;St Affrique;43.95819;2.887126 +0891001E;Lycée Prof.rural Privé Sainte Colombe;Rue Henri Cavallier;89100;St Denis;48.214857;3.273402 +0332382K;Lycée Profes Agricole Privé St Clement;Domaine Labeyrie;33430;Cudos;44.3933095;-0.2146137 +0120867C;Lycée Profess Agricole Privé Francois Marty;;12200;Monteils;44.26547;1.998179 +0673005C;Lycée Professessionnel I.e.s.c.;14 Rue Du Zornhoff;67700;Saverne;48.7492289;7.3773102 +0730012S;Lycée Professionnel;Rue Du Grand Barberaz;73190;Challes Les Eaux;45.5498015;5.9820346 +0750787Y;Lycée Professionnel;61 Rue Corvisart;75013;Paris 13;48.8305676;2.3487691 +0811324Z;Lycée Professionnel;Rue Du Lycée;81207;Mazamet;0.0;0.0 +0020025S;Lycée Professionnel;Chateau Potel;02460;La Ferte Milon;49.177915;3.124745 +0110027V;Lycée Professionnel;1 Av Edouard Herriot;11500;Quillan;42.875129;2.18546 +0120036Z;Lycée Professionnel;Avenue Leo Lagrange;12300;Decazeville;44.5676272;2.2523499 +0490020F;Lycée Professionnel;Rue De La Tuilerie;49321;Cholet;47.0709727;-0.8903426 +0500090B;Lycée Professionnel;Rue Dauphine;50600;St Hilaire Du Harcouet;48.5715264;-1.0976365 +0542306N;Lycée Professionnel;4 Rue De La Tuilerie;54803;Jarny;49.1581264;5.8767019 +0590123N;Lycée Professionnel;115 Rue Francisco Ferrer;59007;Lille;50.63052;3.0934521 +0590125R;Lycée Professionnel;1 Rue Michel Servet;59003;Lille;50.628714;3.0361729 +0593496E;Lycée Professionnel;89 Rue De La Chaussiette;59163;Conde Sur L Escaut;50.4598956;3.5860573 +0620030W;Lycée Professionnel;60 Route D'albert;62452;Bapaume;50.1059853;2.8437041 +0620191W;Lycée Professionnel;800 Rue Leon Blum;62251;Henin Beaumont;50.4306013;2.9485013 +0622240Y;Lycée Professionnel;1 Rue Porte Becquerelle;62170;Montreuil;50.4644173;1.7663501 +0720028P;Lycée Professionnel;Rue Jean Jaures;72600;Mamers;48.349964;0.363104 +0750786X;Lycée Professionnel;92 96 Rue Barrault;75013;Paris 13;48.8230263;2.3467166 +0752608C;Lycée Professionnel;135 Rue Belliard;75018;Paris 18;48.8962641;2.3344205 +0800060F;Lycée Professionnel;Avenue Robert Solente;80301;Albert;49.9999141;2.6573268 +0850029V;Lycée Professionnel;29 Boulevard Guitton;85020;La Roche Sur Yon;46.658489;-1.4330727 +0880031F;Lycée Professionnel;Route De La Rochotte;88407;Gerardmer;48.064932;6.8739463 +0950709E;Lycée Professionnel;100 Av Charles Vaillant;95400;Arnouville Les Gonesse;48.9854345;2.4073595 +0750770E;Lycée Professionnel Abbe Gregoire;70 Bis Rue De Turbigo;75003;Paris 03;48.8661184;2.3593985 +0131709M;Lycée Professionnel Adam De Craponne;218 Rue Chateau Redon;13665;Salon De Provence;43.6355195;5.0986535 +0031047X;Lycée Professionnel Agricole;;03310;Durdat Larequille;46.251014;2.69973 +0071231A;Lycée Professionnel Agricole;Montee Du Savel;07100;Annonay;45.2407678;4.675018 +0071234D;Lycée Professionnel Agricole;19 Av Victor Descours;07270;Lamastre;44.9871441;4.5842853 +0350940G;Lycée Professionnel Agricole;La Lande De La Rencontre;35140;St Aubin Du Cormier;48.2645709;-1.428232 +0382375A;Lycée Professionnel Agricole;5 Rue De La Republique;38440;St Jean De Bournay;45.5023241;5.1423932 +0740276Y;Lycée Professionnel Agricole;;74130;Contamine Sur Arve;46.128077;6.34251 +0150599V;Lycée Professionnel Agricole;Domaine De Volzac;15100;St Flour;45.024512;3.06915 +0190624A;Lycée Professionnel Agricole;;19460;Naves;45.312913;1.767888 +0370794M;Lycée Professionnel Agricole;104 Avenue De La Republique;37170;Chambray Les Tours;47.3405814;0.7240973 +0381818V;Lycée Professionnel Agricole;La Marteliere;38500;Voiron;45.377585;5.592192 +0381888W;Lycée Professionnel Agricole;164 Allee Louis Clerget;38110;La Tour Du Pin;45.565881;5.4382031 +0410626H;Lycée Professionnel Agricole;6 Rue De L Agriculture;41800;Montoire Sur Le Loir;47.7495268;0.8763012 +0430111M;Lycée Professionnel Agricole;Avenue De L'hermitage;43000;Espaly St Marcel;45.055765;3.86089 +0450027K;Lycée Professionnel Agricole;7 Rue Des Deportes;45340;Beaune La Rolande;48.0709911;2.433444 +0450836P;Lycée Professionnel Agricole;11 Rue Des Pervenches;45270;Bellegarde;47.9881528;2.438748 +0470107L;Lycée Professionnel Agricole;Fazanis;47400;Tonneins;44.37731;0.348298 +0501234V;Lycée Professionnel Agricole;Route De Fougeres;50600;St Hilaire Du Harcouet;48.5683384;-1.095299 +0520741Y;Lycée Professionnel Agricole;5 Ruelle Aux Loups;52500;Fayl Billot;47.7791736;5.6033372 +0530520C;Lycée Professionnel Agricole;4o Route De Sable;53200;Chateau Gontier;47.8306463;-0.6922463 +0601265Z;Lycée Professionnel Agricole;91 Rue Andre Regnier;60170;Ribecourt Dreslincourt;49.5135257;2.9208686 +0610746E;Lycée Professionnel Agricole;Avenue Du General Leclerc;61000;Alencon;48.4172116;0.089494 +0631225P;Lycée Professionnel Agricole;Le Marchedial;63210;Rochefort Montagne;45.6864149;2.800831 +0640253C;Lycée Professionnel Agricole;Route De Bordeaux;64300;Orthez;43.4943844;-0.7597037 +0640254D;Lycée Professionnel Agricole;Soeix;64404;Oloron Ste Marie;43.1629834;-0.5907772 +0650138X;Lycée Professionnel Agricole;59 Route De Pau;65000;Tarbes;43.2381467;0.0400529 +0671779V;Lycée Professionnel Agricole;33 Avenue De La Gare;67152;Erstein;48.419731;7.6515589 +0711073F;Lycée Professionnel Agricole;Les Perrieres;71700;Tournus;46.5636067;4.892747 +0711120G;Lycée Professionnel Agricole;Chemin D'ouze;71120;Charolles;46.4307098;4.2649363 +0790767E;Lycée Professionnel Agricole;130 Route De Coulonges;79011;Niort;46.3410959;-0.4743859 +0870590U;Lycée Professionnel Agricole;Route De La Souterraine;87190;Magnac Laval;46.214577;1.166048 +0870671G;Lycée Professionnel Agricole;Domaine De La Faye;87500;St Yrieix La Perche;45.511306;1.249044 +0890849P;Lycée Professionnel Agricole;1 Avenue Du Docteur Schweitzer;89290;Champs Sur Yonne;47.7347694;3.603143 +0771357G;Lycée Professionnel Agricole Agricole;La Bretonniere;77120;Chailly En Brie;48.813781;3.152278 +0131848N;Lycée Professionnel Agricole Amenagement Paysager (de L');89 Traverse Parangon;13008;Marseille 08;43.2463268;5.3798313 +0720907V;Lycée Professionnel Agricole Andre Provots;Le Haut Bois;72250;Brette Les Pins;47.917679;0.332145 +0561758G;Lycée Professionnel Agricole Antenne Lpa St Jean Brevelay;76 Rue Du Talhouet;56701;Hennebont;47.8168284;-3.2640287 +0030118M;Lycée Professionnel Agricole Antoine Brun;44 Avenue Charles De Gaulle;03120;Lapalisse;46.2443117;3.6338821 +0021523V;Lycée Professionnel Agricole Aumont;Place Foch;02380;Coucy La Ville;49.533112;3.328146 +0382376B;Lycée Professionnel Agricole Bellevue;4 Rue Des Recollets;38160;St Marcellin;45.1563352;5.3213845 +0170394L;Lycée Professionnel Agricole Chadignac;Le Petit Chadignac;17119;Saintes;45.721281;-0.663772 +0341059S;Lycée Professionnel Agricole Charles Marie De La Condamine;4 Allee General Montagne;34120;Pezenas;43.4618994;3.4197081 +0660039J;Lycée Professionnel Agricole Claude Simon;4 Rue Pasteur;66602;Rivesaltes;42.7661082;2.8699034 +0595770B;Lycée Professionnel Agricole Communaute Urbaine Dunkerque;1972 Rue De Leffrinckoucke;59240;Dunkerque;51.0462169;2.4456828 +0860818X;Lycée Professionnel Agricole Danielle Mathiron;Domaine Des Chevaliers;86540;Thure;46.831987;0.459635 +0400139J;Lycée Professionnel Agricole De Chalosse;Route De Pomarez;40250;Mugron;43.751052;-0.748762 +0730812L;Lycée Professionnel Agricole De Cognin;13 Avenue Henry Bordeaux;73160;Cognin;45.5643012;5.8933314 +0801335S;Lycée Professionnel Agricole De La Baie De Somme;21 Rue Du Lieutenant Caron;80100;Abbeville;50.1108191;1.8427116 +0801328J;Lycée Professionnel Agricole De La Haute Somme;10 Rue Du Quinconce;80200;Peronne;49.9358497;2.9280029 +0421210M;Lycée Professionnel Agricole De Montravel-villars;Montravel;42390;Villars;45.4751884;4.3506677 +9840277D;Lycée Professionnel Agricole De Opunohu;A;98728;Moorea Maiao;-17.6555246;-150.6343154 +0580054U;Lycée Professionnel Agricole De Plagny;128 Rte De Lyon;58000;Sermoise Sur Loire;46.9632774;3.1571413 +9740097M;Lycée Professionnel Agricole De Saint Joseph;24 Rue Raphael Babet;97480;St Joseph;-21.3766259;55.6161038 +0631223M;Lycée Professionnel Agricole Des Combrailles;Avenue De La Gare;63390;St Gervais D Auvergne;46.030667;2.814032 +0040056P;Lycée Professionnel Agricole Digne-carmejane;Route D Espinouse;04510;Le Chaffaut St Jurson;44.047146;6.158502 +0763004C;Lycée Professionnel Agricole Du Bois;Rue Du General De Gaulle;76630;Envermeu;49.8930353;1.2701613 +0752426E;Lycée Professionnel Agricole Du Breuil-ec.hort.et Arbor.;Route De La Ferme;75012;Paris 12;48.8235759;2.4572633 +0580582T;Lycée Professionnel Agricole Du Morvan;Rue Mendes France;58120;Chateau Chinon Ville;47.059639;3.938102 +0761767H;Lycée Professionnel Agricole Du Pays De Bray;Le Chateau;76220;Bremontier Merval;49.494288;1.652919 +0261069W;Lycée Professionnel Agricole Du Tricastin-baronnies;25 Le Courreau;26130;St Paul Trois Chateaux;44.3472476;4.7671547 +0171590L;Lycée Professionnel Agricole Ensmic;Avenue Francois Mitterrand;17700;Surgeres;46.1087429;-0.7680787 +0460661U;Lycée Professionnel Agricole Epla De Cahors;Lacoste;46090;Le Montat;44.377312;1.441901 +0810578N;Lycée Professionnel Agricole Epla De Lavaur;Domaine De Flamarens;81500;Lavaur;43.694745;1.864111 +0320557G;Lycée Professionnel Agricole Epla De Mirande;Domaine De Valentees;32300;Mirande;43.516823;0.403639 +0320646D;Lycée Professionnel Agricole Epla De Mirande;Route De Pau;32400;Riscle;43.658594;-0.0868189 +0820531G;Lycée Professionnel Agricole Epla De Moissac;Avenue Du Sarlac;82201;Moissac;44.101327;1.09785 +0120938E;Lycée Professionnel Agricole Epla De Saint-affrique;Route De Bournac;12400;St Affrique;43.9578528;2.8419831 +0311268K;Lycée Professionnel Agricole Epla Saint-gaudens;16 Rue Olivier De Serres;31806;St Gaudens;43.1174843;0.730278 +0160108F;Lycée Professionnel Agricole Felix Gaillard;Chez Fouquet;16300;Salles De Barbezieux;45.4515173;-0.1120687 +9720779P;Lycée Professionnel Agricole Four A Chaux;Four A Chaux;97231;Le Robert;14.6647441;-60.936892 +0271108H;Lycée Professionnel Agricole Gilbert Martin;Rue Pierre Corneille;27110;Le Neubourg;49.146564;0.907873 +0341001D;Lycée Professionnel Agricole Honore De Balzac;Avenue De La Galine;34172;Castelnau Le Lez;43.630843;3.902609 +0861145C;Lycée Professionnel Agricole Inst Maison Famil Vienne;47 Route De Montmorillon;86300;Chauvigny;46.5635216;0.646916 +0161002C;Lycée Professionnel Agricole Inst Rur Education Orientation;;16370;Cherves Richemont;45.744433;-0.356736 +0860820Z;Lycée Professionnel Agricole Jean Marie Bouloux;Chateau Ringuet;86501;Montmorillon;46.4163527;0.8441393 +0741556P;Lycée Professionnel Agricole Jeanne Antide;55 Impasse Du Brevent;74930;Reignier;46.1310261;6.2712676 +0370878D;Lycée Professionnel Agricole La Gabilliere;46 Av Emile Gounin;37403;Amboise;47.3929481;0.9793789 +0071239J;Lycée Professionnel Agricole La Pelissiere Vallee Du Rhone;4 Rue Du Repos;07300;Tournon Sur Rhone;45.0652226;4.8340225 +0861122C;Lycée Professionnel Agricole La Perriere;La Perriere;86200;La Roche Rigault;46.974342;0.186944 +0840606C;Lycée Professionnel Agricole La Ricarde;Avnue Jean Boin;84800;L Isle Sur La Sorgue;43.9219696;5.0448297 +0560274U;Lycée Professionnel Agricole Le Suillo;;56660;St Jean Brevelay;47.845229;-2.720734 +0790977H;Lycée Professionnel Agricole Le Val De L Ouin;3 Au 9 Rue De La Sagesse;79700;Mauleon;46.9223173;-0.7528173 +0382369U;Lycée Professionnel Agricole Le Vallon;13 Rue De L Eglise;38690;Chabons;45.4443564;5.4306894 +0595771C;Lycée Professionnel Agricole Legta Du Nord;Avenue Du Chateau;59590;Raismes;50.3959279;3.4830104 +0382370V;Lycée Professionnel Agricole Les Alpes;42 Rue Des Alpes;38350;La Mure;44.9054002;5.7895942 +0131715U;Lycée Professionnel Agricole Les Alpilles;Rue Edouard Heriot;13210;St Remy De Provence;43.790754;4.835772 +0830184Z;Lycée Professionnel Agricole Les Arcs;Route De Draguignan D555;83460;Les Arcs;43.4855012;6.4849386 +0741225E;Lycée Professionnel Agricole Les Roselieres;3260 Route D Albertville;74320;Sevrier;45.8541292;6.1438701 +0741224D;Lycée Professionnel Agricole Les Trois Vallees;2 Avenue De L'ermitage;74200;Thonon Les Bains;46.3679703;6.48484 +0160981E;Lycée Professionnel Agricole Ltpr Claire Champagne;1 Rue Malestiers;16130;Segonzac;45.61808;-0.2231605 +0261315N;Lycée Professionnel Agricole Lycée De Val De Drome;Route De Valence N;26760;Monteleger;44.868636;4.9412779 +0390809T;Lycée Professionnel Agricole Mancy;410 Montee Gauthier Villars;39015;Lons Le Saunier;46.6644134;5.5482578 +0550754W;Lycée Professionnel Agricole Martial Brousse;Place Saint Paul;55100;Verdun;49.163828;5.3843585 +0110710M;Lycée Professionnel Agricole Martin Luther King;Centre Pierre Reverdy;11782;Narbonne;43.184277;3.003078 +0110712P;Lycée Professionnel Agricole Martin Luther King;36 Avenue De Narbonne;11220;St Laurent De La Cabrerisse;43.0856549;2.7015968 +0490963F;Lycée Professionnel Agricole Montreuil-bellay;Rte De Meron;49260;Montreuil Bellay;47.1312384;-0.1332827 +0861123D;Lycée Professionnel Agricole Odile Pasquier;5 Place De La Marne;86700;Couhe;46.3002383;0.1827319 +0382371W;Lycée Professionnel Agricole Paul Claudel;16 Rue Du Marche Vieux;38460;Cremieu;45.7264346;5.253525 +0320530C;Lycée Professionnel Agricole Pavie;;32550;Pavie;43.609862;0.591676 +9840356P;Lycée Professionnel Agricole Privé Anne Marie Javourey;Place Notre Dame;98714;Papeete;-17.535022;-149.569594 +9760181T;Lycée Professionnel Agricole Privé De Coconi;;97670;Chiconi;43.4865563;-0.7687041 +0221032C;Lycée Professionnel Agricole Public;Route De Dinan;22350;Caulnes;48.2968278;-2.1519311 +9760206V;Lycée Professionnel Agricole Public De Coconi;;97670;Chiconi;43.4865563;-0.7687041 +0382378D;Lycée Professionnel Agricole Saint Exupery;264 Rue Des Frenes;38870;St Simeon De Bressieux;45.3301563;5.2612136 +9730364H;Lycée Professionnel Agricole Savane Matiti;Savane Matiti Macouria;97355;Macouria;4.9155544;-52.3693218 +0261065S;Lycée Professionnel Agricole Val De Drome;Route De Valence;26760;Monteleger;44.868636;4.9412779 +0711815M;Lycée Professionnel Agricole Velet;Route De St Didier-sur-arroux;71190;Etang Sur Arroux;46.8627761;4.1780787 +0141421U;Lycée Professionnel Agricole Vire;Route De Caen;14500;Vire;48.8541038;-0.8794987 +0840218F;Lycée Professionnel Agricole Viticole;Mas Mongin Quartier Du Gres;84100;Orange;44.1380989;4.807511 +0640028H;Lycée Professionnel Aizpurdi;1 Les Allees;64702;Hendaye;43.3570403;-1.7742956 +0570061G;Lycée Professionnel Alain Fournier;Rue Emile Boilvin;57000;Metz;49.10305;6.1761419 +0595787V;Lycée Professionnel Alain Savary;Rue Alain Savary;59391;Wattrelos;50.7099702;3.2012067 +0620257T;Lycée Professionnel Alain Savary;14 Avenue Jean Zay;62022;Arras;50.2838793;2.7487122 +0550026E;Lycée Professionnel Alain-fournier;12 Av President Kennedy;55107;Verdun;49.1447039;5.3973437 +0251557G;Lycée Professionnel Albert Camus;Impasse Camus;25200;Bethoncourt;47.537051;6.8015439 +0420074C;Lycée Professionnel Albert Camus;32 Bis Rue De La Loire;42704;Firminy;45.3883922;4.2830196 +0440056X;Lycée Professionnel Albert Chassagne;5 Rue Alexis Maneyrol;44560;Paimboeuf;47.2866375;-2.0259524 +0030061A;Lycée Professionnel Albert Londres;Avenue De La Liberation;03306;Cusset;46.1306952;3.4360709 +0572756L;Lycée Professionnel Albert Schweitzer;44 Rue Schellenthal;57230;Bitche;49.0567339;7.4180043 +0420077F;Lycée Professionnel Albert Thomas;20 Rue Albert Thomas;42328;Roanne;46.0440474;4.0740983 +0710080B;Lycée Professionnel Alexandre Dumaine;95 Espace Alexandre Dumaine;71018;Macon;46.30415;4.833486 +0840113S;Lycée Professionnel Alexandre Dumas;Rue Alphonse Jauffret;84300;Cavaillon;43.8326963;5.0287304 +0500089A;Lycée Professionnel Alexis De Tocqueville;34 Avenue Henri Poincare;50100;Cherbourg Octeville;49.6304753;-1.6077873 +0931198X;Lycée Professionnel Alfred Costes;146 Avenue Henri Barbusse;93000;Bobigny;48.9083628;2.42744 +0690107S;Lycée Professionnel Alfred De Musset;128 Rue De La Poudrette;69100;Villeurbanne;45.75696;4.9211312 +0061561P;Lycée Professionnel Alfred Hutinel;21 Rue De Cannes;06150;Cannes;43.5533656;6.970213 +0590264S;Lycée Professionnel Alfred Kastler;123 Rue Paul Elie Casanova;59723;Denain;50.3293842;3.4072591 +0801704T;Lycée Professionnel Alfred Manessier;33 Rue De La Resistance;80420;Flixecourt;50.0091454;2.0757544 +0540077R;Lycée Professionnel Alfred Mezieres;3 Avenue Andre Malraux;54401;Longwy;49.5216573;5.7601943 +0040007L;Lycée Professionnel Alphonse Beau De Rochas;10 Avenue General Leclerc;04000;Digne Les Bains;43.0535129;2.2158732 +0622174B;Lycée Professionnel Alphonse Daudet;Rue Alphonse Daudet;62620;Barlin;50.456584;2.619812 +0350009V;Lycée Professionnel Alphonse Pelle;30 Rue Montplaisir;35120;Dol De Bretagne;48.5435906;-1.7620966 +0130146N;Lycée Professionnel Alpilles (les);Quartier Les Molieres;13140;Miramas;43.588896;5.002136 +0261100E;Lycée Professionnel Amblard;43 Rue Amblard;26000;Valence;44.9278503;4.9011313 +0400049L;Lycée Professionnel Ambroise Croizat;92 Av Marcel Paul;40220;Tarnos;43.5355002;-1.4901891 +0594532F;Lycée Professionnel Ambroise Croizat;Rue Marcel Paul;59950;Auby;50.4150948;3.0507573 +0740062R;Lycée Professionnel Amedee Gordini;31 Route De Sacconges;74602;Seynod;45.8851811;6.109274 +9740479C;Lycée Professionnel Amiral Lacaze;Rue Stanislas Gimart;97493;St Denis;48.936181;2.357443 +0130072H;Lycée Professionnel Ampere;56 Bd Romain Rolland;13395;Marseille 10;43.2796927;5.4237647 +0560019S;Lycée Professionnel Ampere;Rue Guethennoc;56120;Josselin;47.9598749;-2.547896 +0720064D;Lycée Professionnel Ampere;Boulevard Du Quebec;72206;La Fleche;47.7087173;-0.0792501 +0600049C;Lycée Professionnel Amyot D Inville;Chemin De Reims;60309;Senlis;49.192707;2.5701139 +0330011H;Lycée Professionnel Anatole De Monzie;12 Cours Gambetta;33430;Bazas;44.4332382;-0.213939 +9720515C;Lycée Professionnel Andre Aliker;34 Boulevard Amilcar Cabral;97200;Fort De France;14.609371;-61.07256 +0381605N;Lycée Professionnel Andre Argouges;61 Rue Leon Jouhaux;38029;Grenoble;45.1753906;5.7445124 +0440315D;Lycée Professionnel Andre Boulloche;32 Rue Du Plessis;44606;St Nazaire;47.2873869;-2.2243657 +0640031L;Lycée Professionnel Andre Campa;29 Avenue Joliot Curie;64110;Jurancon;43.2913485;-0.4011011 +0692968B;Lycée Professionnel Andre Cuzin;42 Chemin De Crepieux;69300;Caluire Et Cuire;45.8010809;4.8573366 +0594653M;Lycée Professionnel Andre Jurenil;Boulevard Du 8 Mai 1945;59723;Denain;50.32402;3.396566 +0622089J;Lycée Professionnel Andre Malraux;314 Rue Jules Massenet;62408;Bethune;50.5200303;2.6516077 +0911037H;Lycée Professionnel Andre-marie Ampere;12b A 26 Rte Du Bois Pommier;91390;Morsang Sur Orge;48.6571262;2.3453375 +0810018E;Lycée Professionnel Anne Veaute;46 Boulevard Magenta;81104;Castres;43.6008543;2.2348464 +0480505N;Lycée Professionnel Annexe Lp E Peytavin Mende;Quartier Des Chauvets;48300;Langogne;44.727236;3.855416 +0211356K;Lycée Professionnel Antoine Antoine;5 Rue De Longvic;21303;Chenove;47.2897502;5.00852 +0592850C;Lycée Professionnel Antoine De Saint Exupery;1 Rue Louis Pergaud;59432;Halluin;50.7783564;3.1134241 +0470004Z;Lycée Professionnel Antoine Lomet;221 Av D Italie;47000;Agen;44.1865224;0.6245447 +0762765T;Lycée Professionnel Antoine-laurent Lavoisier;Rue Des Moteaux;76620;Le Havre;49.5263165;0.1002857 +0140005E;Lycée Professionnel Arcisse De Caumont;3 Rue Baron Gerard;14402;Bayeux;49.2843359;-0.7097585 +0840763Y;Lycée Professionnel Argensol (quartier De L');Rue Henri Dunant;84106;Orange;44.1317994;4.8348234 +0090019X;Lycée Professionnel Aristide Berges;Avenue De La Gare;09201;St Girons;43.6583119;-0.5866688 +0271268G;Lycée Professionnel Aristide Briand;2 Rue Pierre Semard;27031;Evreux;49.0168441;1.1602102 +0670062D;Lycée Professionnel Aristide Briand;12 Rue Du Barrage;67301;Schiltigheim;48.6038727;7.7515437 +0930831Y;Lycée Professionnel Aristide Briand;120 Avenue Aristide Briand;93155;Le Blanc Mesnil;48.94482;2.4493596 +0840046U;Lycée Professionnel Aristide Briand (cours);7 Cours Aristide Briand;84100;Orange;44.138288;4.8045546 +0752700C;Lycée Professionnel Armand Carrel;45 Rue Armand Carrel;75019;Paris 19;48.8827398;2.3759898 +0080028M;Lycée Professionnel Armand Malaise;84 Rue Du Bois Fortant;08003;Charleville Mezieres;49.7465138;4.7139474 +0240050Z;Lycée Professionnel Arnaud Daniel;Rue Couleau;24600;Riberac;45.245019;0.336231 +0601822E;Lycée Professionnel Arthur Rimbaud;319 Rue Aristide Briand;60170;Ribecourt Dreslincourt;49.5074328;2.9250178 +0931738J;Lycée Professionnel Arthur Rimbaud;112-114 Avenue Jean Jaures;93120;La Courneuve;48.9219026;2.4080165 +0520008B;Lycée Professionnel Ashton;47 Avenue D'ashton Under Lyne;52000;Chaumont;48.0919255;5.143953 +0710087J;Lycée Professionnel Astier;Rue De Bourgogne;71600;Paray Le Monial;46.4628376;4.1041988 +0810047L;Lycée Professionnel Aucouturier;Route De Blaye;81400;Carmaux;44.0511877;2.1480562 +0430023S;Lycée Professionnel Auguste Aymard;2 Rue Saint Marcel;43000;Espaly St Marcel;45.0490932;3.8675833 +0620110H;Lycée Professionnel Auguste Behal;6 Rue Paul Eluard;62300;Lens;50.4456289;2.8230467 +0260044G;Lycée Professionnel Auguste Bouvet;10 Rue Bouvet;26102;Romans Sur Isere;45.0481031;5.0466555 +0061635V;Lycée Professionnel Auguste Escoffier;Chemin Du Brecq;06801;Cagnes Sur Mer;43.6713865;7.1486137 +0951618T;Lycée Professionnel Auguste Escoffier;77 Rue De Pierrelaye;95610;Eragny;49.0215662;2.1093175 +0770944H;Lycée Professionnel Auguste Perdonnet;1 Allee Du Chateau;77407;Lagny Sur Marne;48.8541166;2.6465725 +0911343R;Lycée Professionnel Auguste Perret;Avenue De La Liberte;91024;Evry;48.6405766;2.4346873 +9710746J;Lycée Professionnel Augustin Arron;Rue De La Republique;97122;Baie Mahault;16.2655166;-61.5871614 +0271606Z;Lycée Professionnel Augustin Boismard;Rue Emile Neuville;27800;Brionne;49.197356;0.713757 +0270018Y;Lycée Professionnel Augustin Hebert;20 Rue Dugesclin;27025;Evreux;49.0193767;1.1716123 +9830460H;Lycée Professionnel Augustin Ty;Touho;98831;Touho;-20.7797486;165.2385163 +0590144L;Lycée Professionnel Automobile Alfred Mongy;129 Rue De La Briqueterie;59700;Marcq En Baroeul;50.6678924;3.0819707 +0594400M;Lycée Professionnel Automobile Et Transports;Rue Alexis Carrel;59791;Grande Synthe;51.0042875;2.3198217 +0630073M;Lycée Professionnel B.t.p;29 Route De Marsat;63530;Volvic;45.8754617;3.0454878 +0690003D;Lycée Professionnel Barthelemy Thimonnier;160 Avenue Andre Lassagne;69592;L Arbresle;45.8343637;4.6130454 +0700019R;Lycée Professionnel Bartholdi;31 Rue Du Dr Deubel;70202;Lure;47.6844457;6.4874168 +9720424D;Lycée Professionnel Bateliere;1 Rue Mon Ideal;97233;Schoelcher;0.0;0.0 +0060038J;Lycée Professionnel Batiment;17 Boulevard Pierre Sola;06300;Nice;43.7074475;7.2861065 +0750793E;Lycée Professionnel Beaugrenelle;62 Rue Saint Charles;75015;Paris 15;48.8482389;2.2868428 +0593187U;Lycée Professionnel Beaupre;Avenue De Beaupre;59481;Haubourdin;50.6018;2.9906864 +0371211R;Lycée Professionnel Beauregard;15 Rue Andre Bauchant;37110;Chateau Renault;47.585999;0.906274 +0420075D;Lycée Professionnel Beauregard;4 Avenue Paul Cezanne;42605;Montbrison;45.6137248;4.0570332 +0700024W;Lycée Professionnel Beauregard;32 Rue P Rimey;70306;Luxeuil Les Bains;47.8220301;6.3674511 +0350050P;Lycée Professionnel Bel Air;7 Rue Ernest Renan;35190;Tinteniac;48.3324662;-1.8241016 +0420049A;Lycée Professionnel Benoit Charvet;30 Avenue Benoit Charvet;42021;St Etienne;45.4455705;4.3772934 +0470641S;Lycée Professionnel Benoit D Azy;Av Charles De Gaulle;47501;Fumel;44.498344;0.958251 +0420958N;Lycée Professionnel Benoit Fourneyron;24 Rue Virgile;42014;St Etienne;45.4176774;4.4197149 +0620124Y;Lycée Professionnel Bernard Chochoy;Rue Marx Dormoy;62380;Lumbres;50.709505;2.129646 +0170078T;Lycée Professionnel Bernard Palissy;1 Rue De Gascogne;17107;Saintes;45.7360174;-0.6152519 +0760022L;Lycée Professionnel Bernard Palissy;5 Sente Aux Loups;76152;Maromme;49.481859;1.044099 +0190674E;Lycée Professionnel Bernart De Ventadour;Boulevard De La Jaloustre;19200;Ussel;45.541611;2.315461 +0540085Z;Lycée Professionnel Bertrand Schwartz;5 Rue Ste Anne;54340;Pompey;48.7738149;6.107286 +9710090W;Lycée Professionnel Blachon Lamentin;Blachon;97129;Lamentin;0.0;0.0 +0130059U;Lycée Professionnel Blaise Pascal;49 Traverse Capron;13012;Marseille 12;43.313125;5.4208382 +0170052P;Lycée Professionnel Blaise Pascal;11 Rue De Dampierre;17415;St Jean D Angely;45.9498953;-0.5125652 +0490785M;Lycée Professionnel Blaise Pascal;2 Rue Du Lycée;49502;Segre;0.0;0.0 +0520029Z;Lycée Professionnel Blaise Pascal;1 Avenue Marcel Paul;52100;St Dizier;48.6456201;4.9737363 +0570031Z;Lycée Professionnel Blaise Pascal;5 Rue Paul Ney;57608;Forbach;49.1806835;6.8958864 +0890057D;Lycée Professionnel Blaise Pascal;20 Ter Avenue Des Cosmonautes;89400;Migennes;47.9674694;3.5100005 +0810962F;Lycée Professionnel Borde Basse;Cite Scolaire;81108;Castres;43.606214;2.241295 +0190008F;Lycée Professionnel Bort-artense;259 Rue Du Lycée;19110;Bort Les Orgues;0.0;0.0 +0820032P;Lycée Professionnel Bourdelle;3 Boulevard Edouard Herriot;82003;Montauban;44.0128398;1.3704149 +0750794F;Lycée Professionnel Brassai;8 Rue Quinault;75015;Paris 15;48.844121;2.2984015 +0350059Z;Lycée Professionnel Brequigny;7 Avenue Georges Graff;35205;Rennes;48.0836213;-1.6920985 +0440074S;Lycée Professionnel Brossaud-blancho;10 Bd Pierre De Coubertin;44616;St Nazaire;47.2693774;-2.2334295 +0131606A;Lycée Professionnel Calade (la);430 Che De La Madrague Ville;13015;Marseille 15;43.3408984;5.3530112 +0020088K;Lycée Professionnel Camille Claudel;9 Rue De Panleu;02209;Soissons;49.3786895;3.3245369 +0140019V;Lycée Professionnel Camille Claudel;57 Av Mal De Lattre De Tassigny;14070;Caen;49.2034569;-0.3849055 +0590260M;Lycée Professionnel Camille Claudel;1 Rue Paul Lafargue;59613;Fourmies;50.0122895;4.0404278 +0631409P;Lycée Professionnel Camille Claudel;4 Rue De La Charme;63039;Clermont Ferrand;45.7981945;3.1198096 +0690125L;Lycée Professionnel Camille Claudel;15 Rue De Cuire;69004;Lyon 04;45.7765856;4.8303022 +0881140L;Lycée Professionnel Camille Claudel;2 Rue Du Parmont;88202;Remiremont;48.014224;6.5852025 +0940145X;Lycée Professionnel Camille Claudel;4 Rue Des Carrieres;94400;Vitry Sur Seine;48.7998475;2.3944324 +0501860A;Lycée Professionnel Camille Corot;242 Rue De L'exode;50015;St Lo;49.1092676;-1.0899166 +0750419Y;Lycée Professionnel Camille Jenatzy;6 Rue Charles Hermite;75018;Paris 18;48.8998372;2.3697896 +0130068D;Lycée Professionnel Camille Jullian;50 Boulevard De La Barasse;13396;Marseille 11;43.2859618;5.4774775 +0670043H;Lycée Professionnel Camille Schneider;13 Avenue De La Gare;67120;Molsheim;48.5387452;7.4970582 +0420076E;Lycée Professionnel Carnot;35 Avenue Carnot;42300;Roanne;46.0419603;4.0699904 +9710722H;Lycée Professionnel Carnot;28 Rue Jean Jaures;97110;Pointe A Pitre;0.0;0.0 +0790928E;Lycée Professionnel Carrosserie G Barre;Rue Jean Perrin;79010;Niort;46.315039;-0.439958 +0590266U;Lycée Professionnel Cesar Baggio;Boulevard D'alsace;59000;Lille;50.6177376;3.0676499 +0220070G;Lycée Professionnel Chaptal;6 Allee Chaptal;22015;St Brieuc;48.5125105;-2.7377027 +0240012H;Lycée Professionnel Chardeuil Metiers Du Batiment;Batiment Chardeuil;24420;Coulaures;45.304424;0.955207 +0160047P;Lycée Professionnel Charles A Coulomb;Avenue Joachim Du Bellay;16016;Angouleme;45.6303964;0.1570913 +0340061G;Lycée Professionnel Charles Allies;24 Boulevard Joliot Curie;34120;Pezenas;43.4561047;3.417854 +0771880A;Lycée Professionnel Charles Baudelaire;Bd Du Chevalier Bayard;77333;Meaux;48.954379;2.914558 +0911254U;Lycée Professionnel Charles Baudelaire;Avenue De La Liberte;91000;Evry;48.6405766;2.4346873 +0660015H;Lycée Professionnel Charles Blanc;Rue Charles Blanc;66000;Perpignan;42.694663;2.9296329 +0110823K;Lycée Professionnel Charles Cros;1 Rue Michel Verges;11870;Carcassonne;43.2278833;2.3385008 +0720049M;Lycée Professionnel Charles Cros;Rue St Denis;72305;Sable Sur Sarthe;47.838785;-0.342691 +0601871H;Lycée Professionnel Charles De Gaulle;Rue Jacques Daguerre;60321;Compiegne;49.387482;2.7840919 +0750508V;Lycée Professionnel Charles De Gaulle;17 Rue Ligner;75020;Paris 20;48.8580201;2.3973207 +0570088L;Lycée Professionnel Charles Jully;59 Rue Marechal Foch;57501;St Avold;49.1057612;6.6927566 +0130151U;Lycée Professionnel Charles Mongrand;10 Bd Cristofol;13110;Port De Bouc;43.405449;4.985931 +0330076D;Lycée Professionnel Charles Peguy;2 Rue Du College Technique;33326;Eysines;44.8886389;-0.626501 +0680074L;Lycée Professionnel Charles Pointet;5 Rue Des Tirailleurs Marocains;68802;Thann;47.810702;7.102382 +0130171R;Lycée Professionnel Charles Privat;10 Rue Lucien Guintoli;13632;Arles;43.6705402;4.6268451 +0680037W;Lycée Professionnel Charles Stoessel;1 Rue Du Fil;68068;Mulhouse;47.7478368;7.3198372 +0351054F;Lycée Professionnel Charles Tillon;9 Allee Georges Palante;35009;Rennes;48.1069004;-1.6607643 +0590085X;Lycée Professionnel Charlotte Perriand;Chateau Du Bois;59242;Genech;50.523207;3.218428 +0451442Y;Lycée Professionnel Chateau Blanc;1 Rue Saint Just;45120;Chalette Sur Loing;48.0043061;2.7179901 +9720501M;Lycée Professionnel Chateauboeuf;Z A C De Chateauboeuf;97255;Fort De France;14.62199;-61.0428795 +0360003H;Lycée Professionnel Chateauneuf;83 Avenue Rollinat;36200;Argenton Sur Creuse;46.5824816;1.514488 +0130055P;Lycée Professionnel Chatelier (le);108 Avenue Roger Salengro;13003;Marseille 03;43.3145473;5.3705429 +0750783U;Lycée Professionnel Chenneviere Malezieux;31 35 Avenue Ledru Rollin;75012;Paris 12;48.8474871;2.37054 +0490059Y;Lycée Professionnel Chevrollier;2 Rue Adrien Recouvreur;49001;Angers;47.4543325;-0.5571808 +0790038M;Lycée Professionnel Cite Scolaire Jean Moulin;Rue Albert Buisson;79101;Thouars;46.9937436;-0.2131568 +0590268W;Lycée Professionnel Cite Scolaire Pierre Forest;Boulevard Charles De Gaulle;59605;Maubeuge;50.2784762;3.9816647 +0750436S;Lycée Professionnel Claude Anthime Corbon;5 Rue Corbon;75015;Paris 15;48.8363768;2.303238 +0720003M;Lycée Professionnel Claude Chappe;Rue Des Colleges;72231;Arnage;47.935313;0.185062 +0921626T;Lycée Professionnel Claude Chappe;54 A 80 Rue Des Alouettes;92000;Nanterre;48.8830353;2.2112838 +0430028X;Lycée Professionnel Claude Favard;6 Rue Jean Catinot;43250;Ste Florine;45.4024955;3.3243487 +0420078G;Lycée Professionnel Claude Lebois;8 Boulevard Alamagny;42403;St Chamond;45.4685576;4.5111391 +0761322Z;Lycée Professionnel Claude Monet;267 Rue Felix Faure;76085;Le Havre;49.5009436;0.110217 +0320030J;Lycée Professionnel Clement Ader;10 Rue Du Chemin Neuf;32130;Samatan;43.4952631;0.9297273 +9740679V;Lycée Professionnel Cluny Catholique Ste Suzanne;4 Chemin Des Trois Freres;97441;Ste Suzanne;-20.90902;55.6055231 +0351878B;Lycée Professionnel Coetlogon;53 Rue Antoine Joly;35083;Rennes;48.1229261;-1.6921595 +0020052W;Lycée Professionnel Colard Noel;132 Avenue De La Republique;02100;St Quentin;49.8628358;3.3085027 +0130071G;Lycée Professionnel Colbert;13 Rue Capitaine Dessemond;13284;Marseille 07;43.2882341;5.3567872 +0590269X;Lycée Professionnel Colbert;2 Parvis Jean Baptiste Colbert;59208;Tourcoing;50.724993;3.16207 +0760087G;Lycée Professionnel Colbert;197 Avenue Des Allies;76141;Le Petit Quevilly;49.4211557;1.0520198 +0783249H;Lycée Professionnel Colbert;52 Avenue Pasteur;78170;La Celle St Cloud;48.8397868;2.1336782 +9830006P;Lycée Professionnel Commercial Et Hotelier;2 Ë 4 Rue G. Baudoux;98845;Noumea;0.0;0.0 +0250014E;Lycée Professionnel Conde;5 Place Marulaz;25030;Besancon;47.2394964;6.0179211 +0370888P;Lycée Professionnel D Arsonval;Place De La Marne;37305;Joue Les Tours;47.347121;0.6668929 +9830517V;Lycée Professionnel D Ouvea (alp);Lekine;98814;Ouvea;0.0;0.0 +0921595J;Lycée Professionnel Daniel Balavoine;7 Rue Marceau Delorme;92270;Bois Colombes;48.9199025;2.2723388 +0690018V;Lycée Professionnel Danielle Casanova;7 Avenue Danielle Casanova;69700;Givors;45.5945754;4.7674675 +0190013L;Lycée Professionnel Danton;Rue Danton;19311;Brive La Gaillarde;45.1590287;1.5216108 +0540032S;Lycée Professionnel Darche;2 Rue Vauban;54405;Longwy;49.523518;5.764968 +0320026E;Lycée Professionnel D'artagnan;Avenue Des Pyrenees;32110;Nogaro;43.754576;-0.029898 +0622271G;Lycée Professionnel D'artois;Rue Jules Mousseron;62290;Noeux Les Mines;50.4729178;2.6598973 +0440036A;Lycée Professionnel De Bougainville;2 Rue Eugene Leroux;44100;Nantes;47.1980237;-1.5930076 +9760296T;Lycée Professionnel De Chirongui;Chirongui;97620;Chirongui;0.0;0.0 +0441550W;Lycée Professionnel De Guerande Olivier Guichard;3 Rue Des Colleges;44350;Guerande;47.338907;-2.424615 +9830511N;Lycée Professionnel De Houailou (alp);Wani Route Provinciale 3;98816;Houailou;0.0;0.0 +9760125G;Lycée Professionnel De Kahani;;97600;Ouangani;0.0;0.0 +9760163Y;Lycée Professionnel De Kaweni;Kaweni;97600;Mamoudzou;-12.780556;45.227778 +9830514S;Lycée Professionnel De Kone (alp);Lotissement Internat;98860;Kone;0.0;0.0 +9830515T;Lycée Professionnel De Koumac (alp);Rue Willy Boarat;98850;Koumac;0.0;0.0 +0861408N;Lycée Professionnel De Kyoto;35 Rue Pierre De Coubertin;86034;Poitiers;46.589285;0.3654183 +0800013E;Lycée Professionnel De L Acheuleen;21 Bis Rue Du 31 Aout 1944;80097;Amiens;49.8809507;2.3249847 +0800065L;Lycée Professionnel De L Authie;20 Rue De Routequeue;80600;Doullens;50.149672;2.3555172 +0332781U;Lycée Professionnel De L Estuaire;41 Rue Jauffre Rudel;33390;Blaye;45.126841;-0.662941 +0210032W;Lycée Professionnel De La Ceramique Henry Moisand;9 Rue Du Lycée;21110;Longchamp;0.0;0.0 +9830509L;Lycée Professionnel De La Foa (alp);;98880;La Foa;45.980309;4.177918 +0601845E;Lycée Professionnel De La Foret;Place Georges Paquier;60635;Chantilly;49.181337;2.462574 +0881411F;Lycée Professionnel De La Haute Moselotte;125 Rue Du Bois Des Dames;88290;Saulxures Sur Moselotte;47.9503028;6.7725191 +9830516U;Lycée Professionnel De La Roche (alp);Route Municipale 1;98828;Mare;49.0742798;-0.0712908 +0740056J;Lycée Professionnel De La Vallee De L'arve P.beche;165 Avenue P. Bechet;74304;Cluses;46.0649788;6.5745449 +9830508K;Lycée Professionnel De La Vallee Du Tir (alp);30 Rue Calmette;98801;Noumea;-22.2620695;166.447292 +0620163R;Lycée Professionnel De L'aa;1 Avenue De Rome;62505;St Omer;50.748789;2.278218 +0510050C;Lycée Professionnel De L'argonne;Avenue Pertison;51801;Ste Menehould;49.096369;4.916346 +0650014M;Lycée Professionnel De L'arrouza;28 Boulevard Roger Cazenave;65107;Lourdes;43.0895992;-0.0486093 +9740737H;Lycée Professionnel De L'horizon;25 Avenue Georges Brassens;97408;St Denis;44.9368252;-0.4863571 +0490013Y;Lycée Professionnel De Narce;Route De Narce;49800;Brain Sur L Authion;47.445012;-0.4313428 +9830512P;Lycée Professionnel De Poindimie (alp);30 Bis Route Provinciale 3;98822;Poindimie;0.0;0.0 +0920150N;Lycée Professionnel De Prony;4 Rue De Bretagne;92600;Asnieres Sur Seine;48.9041761;2.2846969 +9740575G;Lycée Professionnel De Saint Pierre;180 Rue Marius Et Ary Leblond;97410;St Pierre;0.0;0.0 +0230008J;Lycée Professionnel Delphine Gay;Avenue Joliot Curie;23400;Bourganeuf;45.954755;1.749135 +0100016N;Lycée Professionnel Denis Diderot;102 Avenue Jean Jaures;10100;Romilly Sur Seine;48.5166632;3.7160661 +0900355X;Lycée Professionnel Denis Diderot;Rue D'alembert;90800;Bavilliers;47.627813;6.837506 +0410036S;Lycée Professionnel Denis Papin;46 Rue De La Deniserie;41206;Romorantin Lanthenay;47.3673731;1.7432173 +0930128J;Lycée Professionnel Denis Papin;34 Avenue Michelet;93120;La Courneuve;48.928846;2.3863127 +0310090E;Lycée Professionnel Deodat De Severac;26 Boulevard Deodat De Severac;31076;Toulouse;43.5869625;1.4260334 +0762602R;Lycée Professionnel Des 4 Cantons - Grieu;1 Avenue Des Quatre Cantons;76044;Rouen;49.4538868;1.1333653 +0610050Y;Lycée Professionnel Des Andaines;3 Place Du General De Gaulle;61600;La Ferte Mace;48.5869648;-0.3589215 +0620131F;Lycée Professionnel Des Deux Caps;56 Rue Pasteur;62250;Marquise;50.8170749;1.7088829 +0330033G;Lycée Professionnel Des Menuts;36 Rue Des Douves;33800;Bordeaux;44.829973;-0.5631822 +0290092T;Lycée Professionnel Des Metiers;Rue De Logodec;29590;Pont De Buis Les Quimerch;48.251652;-4.091863 +9730372S;Lycée Professionnel Des Metiers;Quartier Balata Rn1;97351;Matoury;4.847214;-52.33112 +0291633T;Lycée Professionnel Des Metiers Rene Laennec;61 Rue Du Lycée;29120;Pont L Abbe;0.0;0.0 +0590102R;Lycée Professionnel Des Monts De Flandre;4 Avenue Des Flandres;59522;Hazebrouck;50.6933308;2.5173338 +0594652L;Lycée Professionnel Des Plaines Du Nord;74 Avenue De Petite Synthe;59792;Grande Synthe;51.0148286;2.3096488 +0622801H;Lycée Professionnel Des Travaux Publics;Le Bois Des Dames;62702;Bruay La Buissiere;50.4999418;2.5616801 +0440541Z;Lycée Professionnel Des Trois Rivieres;1 Rue Des Cormiers;44160;Pontchateau;47.4435735;-2.0964974 +0630061Z;Lycée Professionnel Desaix;Le Mas Boutin;63700;St Eloy Les Mines;46.161333;2.8192499 +0760036B;Lycée Professionnel Descartes;1575 Boulevard Nelson Mandela;76401;Fecamp;49.7442498;0.3892768 +9720429J;Lycée Professionnel Dillon;Cite Dillon;97200;Fort De France;14.6035016;-61.0460438 +0594375K;Lycée Professionnel Dinah Derycke;365 Rue Jules Guesde;59658;Villeneuve D Ascq;50.6395081;3.1380213 +0810995S;Lycée Professionnel Docteur Clement De Pemille;17 Bis Avenue De L'europe;81302;Graulhet;43.7650158;1.9948594 +0570095U;Lycée Professionnel Dominique Labroise;22 Rue De La Mesange;57403;Sarrebourg;48.736734;7.0686686 +0600048B;Lycée Professionnel Donation De Rothschild;Chateau De Laversine;60740;St Maximin;49.235208;2.451886 +0090024C;Lycée Professionnel Dr Philippe Tissie;17 Rue Du Capus;09700;Saverdun;43.2330329;1.5797801 +0690092A;Lycée Professionnel Du 1er Film;14 Rue Du Premier Film;69008;Lyon 08;45.745231;4.8688581 +0640050G;Lycée Professionnel Du 4 Septembre 1870;22 Av Du Quatre Septembre;64400;Oloron Ste Marie;43.1977225;-0.6090365 +0160792Z;Lycée Professionnel Du Batiment;360 Route De Bordeaux Sillac;16022;Angouleme;45.648377;0.1562369 +0860048K;Lycée Professionnel Du Batiment - Auguste Perret;46 Rue De La Bugellerie;86036;Poitiers;46.5973386;0.3314259 +0280009H;Lycée Professionnel Du Batiment Ph De L Orme;7 Rue Jules Ferry;28114;Luce;48.4374249;1.4602902 +0560039N;Lycée Professionnel Du Blavet;43 Rue Charles Gounod;56306;Pontivy;48.0568748;-2.9592048 +0310006N;Lycée Professionnel Du Bois;Route De Luchon;31110;Montauban De Luchon;42.80681;0.60472 +0011010T;Lycée Professionnel Du Bugey;113 Rue Du 5eme Rtm;01306;Belley;45.7606158;5.68952 +0740059M;Lycée Professionnel Du Chablais;Chemin De Morcy;74207;Thonon Les Bains;46.359481;6.458842 +0620188T;Lycée Professionnel Du Detroit;200 Rue Guillaume Apollinaire;62107;Calais;50.939417;1.8738335 +0451037H;Lycée Professionnel Du Giennois;20 Rue Du 32 Eme R I;45500;Gien;47.698059;2.624758 +0951572T;Lycée Professionnel Du Grand Cerf;1 Et 3 Rue Karl Marx;95870;Bezons;48.923505;2.2077554 +0590270Y;Lycée Professionnel Du Hainaut;1 Avenue Villars;59322;Valenciennes;50.3582813;3.5107769 +0420065T;Lycée Professionnel Du Haut Forez;Le Bourg;42600;Verrieres En Forez;45.570879;3.996448 +0801739F;Lycée Professionnel Du Marquenterre;2 Rue Du Marais;80120;Rue;50.2688787;1.6760326 +0311324W;Lycée Professionnel Du Mirail;Avenue Jean Baylet;31081;Toulouse;43.5846573;1.3942102 +0730032N;Lycée Professionnel Du Nivolet;Route De Barby;73491;La Ravoire;45.5713919;5.9668199 +0170070J;Lycée Professionnel Du Pays D'aunis;Rue Du Stade;17700;Surgeres;46.108536;-0.747223 +0680041A;Lycée Professionnel Du Rebberg;1 Rue De Verdun;68100;Mulhouse;47.7403099;7.3460648 +0951282C;Lycée Professionnel Du Vexin;Route De Marines;95750;Chars;49.1580627;1.9424217 +0800061G;Lycée Professionnel Du Vimeu;3 Rue Denis Papin;80534;Friville Escarbotin;50.0832324;1.5461642 +9710690Y;Lycée Professionnel Ducharmoy;Cite Huyghes Despointes;97120;St Claude;46.387405;5.8677659 +0290108K;Lycée Professionnel Dupuy De Lome;34 Rue Dupuy De Lome;29287;Brest;48.3909239;-4.509126 +0671696E;Lycée Professionnel Economique;Place Du Dr Fr. Kretz;67604;Selestat;48.259386;7.454241 +0671737Z;Lycée Professionnel Economique;3 Quai Charles Frey;67000;Strasbourg;48.5784816;7.7465186 +0520795G;Lycée Professionnel Edme Bouchardon;16 Rue Youri Gagarine;52903;Chaumont;48.104773;5.1478457 +0500032N;Lycée Professionnel Edmond Doucet;Rue Paul Doumer;50120;Equeurdreville Hainneville;49.646515;-1.6551279 +0590265T;Lycée Professionnel Edmond Labbe;817 Rue Charles Bourseul;59508;Douai;50.3764002;3.0685187 +0690129R;Lycée Professionnel Edmond Labbe;9 Chemin Des Chassagnes;69600;Oullins;45.7224869;4.8100176 +0760007V;Lycée Professionnel Edmond Labbe;52 Avenue Aristide Briand;76360;Barentin;49.5422723;0.956811 +0750800M;Lycée Professionnel Edmond Rostand;15 Rue De L'evangile;75018;Paris 18;48.8927119;2.3626153 +0620193Y;Lycée Professionnel Edouard Branly;2 Rue De La Porte Gayole;62321;Boulogne Sur Mer;50.7198852;1.6152414 +0801336T;Lycée Professionnel Edouard Branly;70 Boulevard De Saint Quentin;80098;Amiens;49.8791607;2.2965051 +0850028U;Lycée Professionnel Edouard Branly;5 Boulevard Edouard Branly;85006;La Roche Sur Yon;46.6746179;-1.4417319 +0860006P;Lycée Professionnel Edouard Branly;2 Rue Edouard Branly;86106;Chatellerault;46.8021551;0.5431246 +0800062H;Lycée Professionnel Edouard Gand;70 Bd De St Quentin;80098;Amiens;49.8791607;2.2965051 +0593661J;Lycée Professionnel Edouard Lalo;11 Rue De Thionville;59009;Lille;50.6434451;3.065631 +0870041X;Lycée Professionnel Edouard Vaillant;38 Route Du Derot;87200;St Junien;45.8992579;0.911986 +9730308X;Lycée Professionnel Elie Castor;Rue Edjide Duchesne;97388;Kourou;5.1518573;-52.644595 +0760088H;Lycée Professionnel Elisa Lemonnier;59 Boulevard Charles De Gaulle;76141;Le Petit Quevilly;49.4193664;1.0601232 +0310033T;Lycée Professionnel Elisabeth Et Norbert Casteret;27 Avenue De L'isle;31806;St Gaudens;43.1126792;0.7258415 +0520032C;Lycée Professionnel Emile Baudot;Route De Saint Dizier;52130;Wassy;48.499508;4.948753 +0331882S;Lycée Professionnel Emile Combes;23 Rue Emile Combes;33321;Begles;44.8064674;-0.5473837 +0371258S;Lycée Professionnel Emile Delataille;Place Des Prebendes;37602;Loches;47.1263983;0.98353 +0880064S;Lycée Professionnel Emile Galle;5 Rue Auguste Dedecker;88151;Thaon Les Vosges;48.2491595;6.4083611 +0540015Y;Lycée Professionnel Emile Levassor;2 Rue Levassor;54110;Dombasle Sur Meurthe;48.6079509;6.359411 +0480019K;Lycée Professionnel Emile Peytavin;Avenue Du 11 Novembre;48001;Mende;44.5233613;3.4833187 +0132569X;Lycée Professionnel Emile Zola;Av De L Arc De Meyran;13181;Aix En Provence;43.5118833;5.4584873 +0550003E;Lycée Professionnel Emile Zola;5 Rue D'anjou;55012;Bar Le Duc;48.7816739;5.1710159 +0440141P;Lycée Professionnel Emilien Maillard;230 Rue Du Pressoir Rouge;44154;Ancenis;47.3752742;-1.1822546 +0760024N;Lycée Professionnel Emulation Dieppoise;2 Rue De Stalingrad;76201;Dieppe;49.917085;1.082993 +0850033Z;Lycée Professionnel Eric Tabarly;48 Rue Du Moulin;85108;Les Sables D Olonne;46.4964057;-1.8007527 +0752846L;Lycée Professionnel Erik Satie;2-4 Rue Durouchoux;75014;Paris 14;48.8331604;2.3263037 +0590193P;Lycée Professionnel Ernest Couteaux;37 Avenue Du College;59734;St Amand Les Eaux;50.4477034;3.4431281 +0130058T;Lycée Professionnel Estaque (l');310 Rue Rabelais;13016;Marseille 16;43.3620918;5.3247729 +0750798K;Lycée Professionnel Esthetique;9 Rue Fortuny;75017;Paris 17;48.8820156;2.3062483 +0750808W;Lycée Professionnel Etienne Dolet;7 9 Rue D'eupatoria;75020;Paris 20;48.8687503;2.3868082 +0440294F;Lycée Professionnel Etienne Lenoir;9 Rue De L Europe;44146;Chateaubriant;47.729359;-1.3637892 +0420079H;Lycée Professionnel Etienne Mimard;32 Rue Etienne Mimard;42021;St Etienne;45.438102;4.3960568 +0510008G;Lycée Professionnel Etienne Oehmichen;8 Avenue Du Mont Hery;51037;Chalons En Champagne;48.9761202;4.3588161 +0130025G;Lycée Professionnel Etoile (de L');14 Rue Jules Ferry;13541;Gardanne;43.4529944;5.469251 +0550891V;Lycée Professionnel Eugene Freyssinet;6 Av President Kennedy;55107;Verdun;49.1456674;5.3935978 +0210056X;Lycée Professionnel Eugene Guillaume;12 Av Mal De Lattre De Tassigny;21506;Montbard;47.6226812;4.3398528 +0311092U;Lycée Professionnel Eugene Montel;2 Boulevard Marcel Dassault;31770;Colomiers;43.6101188;1.3508466 +0680039Y;Lycée Professionnel F.d. Roosevelt;17 Bd Presid Roosevelt;68060;Mulhouse;47.7486201;7.3305514 +0220083W;Lycée Professionnel Felix Le Dantec;Rue Des Cordiers;22303;Lannion;48.7310544;-3.446948 +0950657Y;Lycée Professionnel Ferdinand Buisson;245 Rue Ferdinand Buisson;95120;Ermont;48.9779421;2.2726886 +0390914G;Lycée Professionnel Ferdinand Fillod;1 Rue Lamartine;39160;St Amour;46.4378786;5.3440389 +0120038B;Lycée Professionnel Ferdinand Foch;1 Rue Vieussens;12000;Rodez;44.350556;2.5620421 +0840700E;Lycée Professionnel Ferdinand Revoul;Route De Nyons;84601;Valreas;44.3870032;5.0112201 +0620018H;Lycée Professionnel Fernand Degrugillier;1 Boulevard Emile Basly;62260;Auchel;50.5069023;2.4663519 +0693094N;Lycée Professionnel Fernand Forest;Rue De L'egalite;69800;St Priest;45.691598;4.9463832 +0250001R;Lycée Professionnel Fernand Leger;6 Rue Rene Girardot;25404;Audincourt;47.4837032;6.8498439 +0340006X;Lycée Professionnel Fernand Leger;63 Rte De Clermont L Herault;34600;Bedarieux;43.6120939;3.1676971 +0590263R;Lycée Professionnel Fernand Leger;Route De Steendam;59411;Coudekerque Branche;51.0187231;2.3991535 +0610022T;Lycée Professionnel Fernand Leger;Allee Eugene Cabrol;61104;Flers;48.743349;-0.555477 +0762836V;Lycée Professionnel Fernand Leger;Chemin Des Coquereaux;76530;Grand Couronne;49.362074;1.0172754 +0130157A;Lycée Professionnel Ferrages (quartier Les);Quartier Les Ferrages;13250;St Chamas;43.550669;5.034508 +6200004K;Lycée Professionnel Finosello;Av Mal Lyautey;20189;Ajaccio;41.9379483;8.7447941 +0611287T;Lycée Professionnel Flers;;61100;Flers;48.750059;-0.575794 +0330060L;Lycée Professionnel Flora Tristan;Domaine De La Chausse;33360;Camblanes Et Meynac;44.7653476;-0.4941232 +0610019P;Lycée Professionnel Flora Tristan;7 9 Av Meunier De La Raillere;61600;La Ferte Mace;48.589377;-0.356027 +0620121V;Lycée Professionnel Flora Tristan;9 Ave Du General De Gaulle;62192;Lillers;50.5309405;2.5081599 +0920170K;Lycée Professionnel Florian;9 Bis Rue De La Marne;92330;Sceaux;48.7815135;2.3034754 +0130056R;Lycée Professionnel Floride (la);54 Bd Gay Lussac Zin 903;13014;Marseille 14;0.0;0.0 +0060023T;Lycée Professionnel Francis De Croisset;34 Chemin De La Cavalerie;06130;Grasse;43.6526492;6.9315142 +0640053K;Lycée Professionnel Francis Jammes;Route De Mont De Marsan;64301;Orthez;43.48756;-0.7739279 +0511748Y;Lycée Professionnel Francois 1er;Faubourg De Vitry Le Brule;51308;Vitry Le Francois;48.729036;4.59224 +0620144V;Lycée Professionnel Francois Albert;Rue Jules Mousseron;62290;Noeux Les Mines;50.4729178;2.6598973 +0440033X;Lycée Professionnel Francois Arago;23 Rue Du Recteur Schmitt;44322;Nantes;47.2520908;-1.5519956 +0090020Y;Lycée Professionnel Francois Camel;28 Rue Jules Ferry;09201;St Girons;42.9738873;1.1537953 +0693095P;Lycée Professionnel Francois Cevert;104 Chemin De La Sauvegarde;69132;Ecully;45.7836623;4.7878684 +0623377J;Lycée Professionnel Francois Hennebique;Rue Colbert;62804;Lievin;50.4257392;2.7540206 +0260041D;Lycée Professionnel Francois Jean Armorin;35 Avenue H.grand;26402;Crest;44.731422;5.0164927 +0594302F;Lycée Professionnel Francois Mansart;270 Avenue Henri Barbusse;59582;Marly;50.3445555;3.5571247 +0580552K;Lycée Professionnel Francois Mitterrand;Rue Pierre Mendes France;58120;Chateau Chinon Ville;47.059639;3.938102 +0595894L;Lycée Professionnel Francois Rabelais;817 Rue Charles Bourseul;59507;Douai;50.3764002;3.0685187 +0021479X;Lycée Professionnel Francoise Dolto;4 Avenue Des Lilas;02120;Guise;49.9027382;3.639282 +0382274R;Lycée Professionnel Francoise Dolto;4 Rue Piardiere;38120;Fontanil Cornillon;45.2512325;5.666418 +0710079A;Lycée Professionnel Francoise Dolto;1 Rue Capitaine Priet;71307;Montceau Les Mines;46.681852;4.3871729 +0930138V;Lycée Professionnel Frederic Bartholdi;12 Rue De La Liberte;93200;St Denis;48.9438959;2.3653605 +0690106R;Lycée Professionnel Frederic Fays;46 Rue Frederic Fays;69615;Villeurbanne;45.7625759;4.9047165 +0130062X;Lycée Professionnel Frederic Mistral;46 Boulevard Sainte Anne;13417;Marseille 08;43.2580155;5.392781 +0220187J;Lycée Professionnel Fulgence Bienvenue;Rue Eon De L'etoile;22606;Loudeac;48.1851796;-2.7600028 +0540084Y;Lycée Professionnel Fulgence Bienvenue;10 Rue Prosper Merimee;54580;Auboue;49.2107677;5.9836034 +0720034W;Lycée Professionnel Funay-helene Boucher;22 Rue Rodolphe Diesel;72003;Le Mans;47.984752;0.2253982 +0310056T;Lycée Professionnel Gabriel Peri;30 Rue Gabriel Peri;31013;Toulouse;43.6061733;1.454834 +0940132H;Lycée Professionnel Gabriel Peri;41 Avenue Boileau;94500;Champigny Sur Marne;48.8067115;2.5458851 +0011118K;Lycée Professionnel Gabriel Voisin;21 Avenue De Jasseron;01000;Bourg En Bresse;46.2113123;5.2450739 +0100945Y;Lycée Professionnel Gabriel Voisin;Chemin Des Champs De La Loge;10012;Troyes;48.31499;4.069673 +0380100C;Lycée Professionnel Galilee;124 Avenue General Leclerc;38209;Vienne;45.5052095;4.8559693 +0750785W;Lycée Professionnel Galilee;28 Rue De Patay;75013;Paris 13;48.8252444;2.3751304 +0380010E;Lycée Professionnel Gambetta;14 Avenue Gambetta;38309;Bourgoin Jallieu;45.5880165;5.273173 +0130006L;Lycée Professionnel Gambetta (cours);100 Bis Cours Gambetta;13100;Aix En Provence;43.5203551;5.4599761 +0100906F;Lycée Professionnel Gaston Bachelard;5 Bis Rue Gaston Bachelard;10200;Bar Sur Aube;48.2298972;4.7045231 +0750553U;Lycée Professionnel Gaston Bachelard;2 Rue Tagore;75013;Paris 13;48.8206816;2.3602263 +0300057Z;Lycée Professionnel Gaston Darboux;7 Rue Jules Raimu;30908;Nimes;43.8184647;4.3295053 +0530778H;Lycée Professionnel Gaston Lesnard;84 Boulevard Volney;53015;Laval;48.0565772;-0.7961695 +0020077Y;Lycée Professionnel Gay Lussac;Boulevard Gambetta;02302;Chauny;49.613832;3.2214 +0870013S;Lycée Professionnel George Sand;9 Rue De L'hozanne;87210;Le Dorat;46.2145054;1.0820723 +0720065E;Lycée Professionnel George Washington;8 Place Washington;72002;Le Mans;47.9956706;0.2030578 +0572175E;Lycée Professionnel Georges Bastide;Square Georges Bastide;57150;Creutzwald;49.204987;6.6962469 +0880056H;Lycée Professionnel Georges Baumont;Rue De L'orme;88102;St Die Des Vosges;48.29796;6.946644 +0300951W;Lycée Professionnel Georges Brassens;Avenue Vigan Braquet;30205;Bagnols Sur Ceze;44.1607643;4.6116424 +0590250B;Lycée Professionnel Georges Bustin;13 Rue Du 8 Mai 1945;59690;Vieux Conde;50.4642251;3.569136 +0190045W;Lycée Professionnel Georges Cabanis;6 Boulevard Henry De Jouvenel;19311;Brive La Gaillarde;45.1600815;1.5232644 +0270052K;Lycée Professionnel Georges Dumezil;Route D'ivry;27207;Vernon;49.0818358;1.4769566 +0590198V;Lycée Professionnel Georges Guynemer;99 Rue De La Republique;59430;St Pol Sur Mer;51.0287687;2.3564086 +0740054G;Lycée Professionnel Germain Sommeiller;2 Boulevard Taine;74008;Annecy;45.906769;6.1291068 +0280925D;Lycée Professionnel Gilbert Courtois;2 Rue Salvador Allende;28105;Dreux;48.7366183;1.3444523 +0630053R;Lycée Professionnel Gilbert Romme;75 Rue Du Creux;63201;Riom;45.8859718;3.131179 +0170025K;Lycée Professionnel Gilles Jamain;2a Boulevard Pouzet;17312;Rochefort;45.9309537;-0.9657396 +0510017S;Lycée Professionnel Godart Roger;8 Rue Godart Roger;51331;Epernay;49.0398693;3.9654465 +0831014B;Lycée Professionnel Golf-hotel;Allee Georges Dussauge;83400;Hyeres;43.1306904;6.1572977 +0940140S;Lycée Professionnel Gourdou-leseurre;50-56 Boulevard De Champigny;94210;St Maur Des Fosses;48.8024866;2.5069878 +0141640G;Lycée Professionnel Guibray;8 Rue Des Champs St Georges;14700;Falaise;48.8900926;-0.1882493 +0030905T;Lycée Professionnel Gustave Eiffel;Rue Jules Bertin;03800;Gannat;46.088356;3.197488 +0110034C;Lycée Professionnel Gustave Eiffel;2 Rue Jean Moulin;11108;Narbonne;43.1899207;3.01902 +0130013U;Lycée Professionnel Gustave Eiffel;Avenue Manoukian;13682;Aubagne;43.2867325;5.5484578 +0370053G;Lycée Professionnel Gustave Eiffel;1 Rue Marechal Augereau;37073;Tours;47.4209434;0.67437 +0590262P;Lycée Professionnel Gustave Eiffel;96 Rue Jules Lebleu;59427;Armentieres;50.6880881;2.8733271 +0690008J;Lycée Professionnel Gustave Eiffel;6 A 12 Av Ferdinand Gaillard;69530;Brignais;45.6766545;4.7549711 +0752961L;Lycée Professionnel Gustave Eiffel;1 Rue Du General Camou;75007;Paris 07;48.858841;2.2994949 +0770945J;Lycée Professionnel Gustave Eiffel;4 Av. D'ormes;77130;Varennes Sur Seine;48.3741592;2.9264466 +0910632T;Lycée Professionnel Gustave Eiffel;9 Avenue De La Republique;91300;Massy;48.7351223;2.2952471 +0951673C;Lycée Professionnel Gustave Eiffel;9 Allee Jean De Florette;95120;Ermont;48.9837039;2.2450203 +0750775K;Lycée Professionnel Gustave Ferrie;7 Rue Des Ecluses St Martin;75010;Paris 10;48.8765153;2.3673877 +0760142S;Lycée Professionnel Gustave Flaubert;1 Rue Albert Dupuis;76044;Rouen;49.4508628;1.1226207 +0590255G;Lycée Professionnel Guy Debeyre;R Ctre Torpilleur Le Triomphant;59383;Dunkerque;51.03456;2.3752019 +0310051M;Lycée Professionnel Guynemer;43 Rue Leo Lagrange;31400;Toulouse;43.5874782;1.4525238 +0640049F;Lycée Professionnel Guynemer;1 Av Du 19 Mars 1962;64404;Oloron Ste Marie;43.1835704;-0.6322436 +0670067J;Lycée Professionnel Haute-bruche;18 Rue Des Grives;67131;Schirmeck;48.4859231;7.2352351 +0750802P;Lycée Professionnel Hector Guimard;19 Rue Curial;75019;Paris 19;48.8918906;2.3723661 +0441823T;Lycée Professionnel Heinlex;1 Rue Albert Einstein;44600;St Nazaire;47.2646046;-2.2632765 +0540059W;Lycée Professionnel Helene Bardot;12 Place Saint Antoine;54701;Pont A Mousson;48.9037504;6.0558391 +0690093B;Lycée Professionnel Helene Boucher;18 Rue Rosenberg;69631;Venissieux;45.6981022;4.8907074 +0371099U;Lycée Professionnel Henri Becquerel;1 Rue Jules Ladoumegue;37000;Tours;47.377011;0.731656 +0332344U;Lycée Professionnel Henri Brulle;Route De St Emilion;33503;Libourne;44.9122971;-0.2202256 +0620190V;Lycée Professionnel Henri Darras;Chemin Des Manufactures;62803;Lievin;50.412424;2.766446 +0490784L;Lycée Professionnel Henri Dunant;15 Rue De Haarlem;49017;Angers;47.4842499;-0.5241609 +0700011G;Lycée Professionnel Henri Fertet;Place Du General Boichut;70104;Gray;47.444006;5.59541 +0780584L;Lycée Professionnel Henri Matisse;55 Rue De Montfort;78196;Trappes;48.7814528;1.9945968 +0623328F;Lycée Professionnel Henri Senez;Bd F Darchicourt;62252;Henin Beaumont;50.405948;2.962536 +0541290J;Lycée Professionnel Henri Wallon;1 Rue Henry Wallon;54190;Villerupt;49.4639405;5.9266187 +0640058R;Lycée Professionnel Honore Baradat;Avenue Du Loup;64050;Pau;43.3132924;-0.3555257 +0660520G;Lycée Professionnel Hotel Resto Leon Blum;15 Avenue Paul Alduy;66103;Perpignan;42.6741796;2.9046494 +0070016E;Lycée Professionnel Hotelier;Route De Tauriers;07110;Largentiere;44.55113;4.279915 +0910629P;Lycée Professionnel Hotelier Chateau Des Coudraies;2 Bd Charles De Gaulle;91450;Etiolles;48.6349849;2.466748 +0650874X;Lycée Professionnel Hotelier Et Econom Lautreamont;Avenue D Azereix;65016;Tarbes;43.227193;0.055477 +0740014N;Lycée Professionnel Hotelier Francois Bise;86 Rue D'asniere;74136;Bonneville;46.0780249;6.409053 +0640042Y;Lycée Professionnel Hotelier Haute Vue;Avenue Des Cimes;64160;Morlaas;43.3406345;-0.2758834 +9740738J;Lycée Professionnel Hotelier La Renaissance;63 Rue Auguste Vinson;97867;St Paul;-21.0284314;55.2652076 +0573080N;Lycée Professionnel Hurlevent;Rue Du Petit Bois;57460;Behren Les Forbach;49.172573;6.926953 +0590257J;Lycée Professionnel Ile De Flandre;246 Bis Quai De La Derivation;59427;Armentieres;50.6911473;2.8904884 +0595480L;Lycée Professionnel Ile Jeanty;Rue Auguste Waeteraere;59140;Dunkerque;51.030326;2.361877 +0330089T;Lycée Professionnel Indust.et Hotel.j.monnet;40 Avenue Henri Brulle;33506;Libourne;44.9053141;-0.2418679 +0950658Z;Lycée Professionnel Industriel Chateau D Epluches;45 Avenue Du Chateau;95310;St Ouen L Aumone;49.0549212;2.1309912 +0340088L;Lycée Professionnel Irene Et Frederic Joliot Curie;19 Boulevard Joliot Curie;34200;Sete;43.3972261;3.6659039 +0090543S;Lycée Professionnel Irenee Cros;2 Avenue De L'ariege;09103;Pamiers;43.1098821;1.6140065 +0880023X;Lycée Professionnel Isabelle Viviani;75 Rue De Remiremont;88020;Epinal;48.1629574;6.4496322 +9740921H;Lycée Professionnel Isnelle Amelin;Rue Marcel Goulette Duparc;97438;Ste Marie;0.0;0.0 +0801252B;Lycée Professionnel J C Athanase Peltier;8 Avenue Jean Moulin;80400;Ham;49.7510579;3.077901 +0070002P;Lycée Professionnel J Et E Montgolfier;17 Rue Du Capitaine De Canson;07100;Annonay;45.2442892;4.6718918 +0420073B;Lycée Professionnel Jacob Holtzer;5 Rue Michelet;42704;Firminy;45.3882874;4.2914303 +0760006U;Lycée Professionnel Jacquard;2 Rue Antoine Bourdelle;76360;Barentin;49.5417967;0.9497241 +0880057J;Lycée Professionnel Jacques Augustin;20 Rue Du Nouvel Hopital;88100;St Die Des Vosges;48.2885775;6.9603839 +0692718E;Lycée Professionnel Jacques Brel;7 Avenue D'oschatz;69200;Venissieux;45.6952773;4.8802134 +0180042Y;Lycée Professionnel Jacques Coeur;108 Rue Jean Baffier;18026;Bourges;47.0738989;2.409201 +0470029B;Lycée Professionnel Jacques De Romas;Route De Mezin;47600;Nerac;44.1274306;0.311799 +0060002V;Lycée Professionnel Jacques Dolle;120 Chemin De Saint Claude;06600;Antibes;43.5929211;7.1137122 +0620162P;Lycée Professionnel Jacques Durand;9 Rue Monsigny;62505;St Omer;50.7501372;2.2593951 +0620011A;Lycée Professionnel Jacques Le Caron;61 Avenue De L'hippodrome;62000;Arras;50.2926345;2.7440119 +0380023U;Lycée Professionnel Jacques Prevert;9 Rue La Republique;38601;Fontaine;45.1951041;5.6674681 +0390015E;Lycée Professionnel Jacques Prevert;31 Place Barberousse;39107;Dole;47.0886756;5.488823 +0782603F;Lycée Professionnel Jacques Prevert;88 Avenue Des Etats Unis;78000;Versailles;48.81003;2.1492612 +0590249A;Lycée Professionnel Jacques-yves Cousteau;27 Rue Pasteur;59290;Wasquehal;50.6723546;3.1272292 +0620037D;Lycée Professionnel Jan Lavezzari;15 Avenue Du Phare;62602;Berck;50.3988519;1.5605537 +0380037J;Lycée Professionnel Jean Jaures;5 Rue Ancien Champ De Mars;38000;Grenoble;45.1887412;5.7201699 +0160862A;Lycée Professionnel Jean Albert Gregoire;Rue Jean Albert Gregoire;16800;Soyaux;45.629823;0.217669 +0130064Z;Lycée Professionnel Jean Baptiste Brochier;9 Bd Mireille Lauze;13010;Marseille 10;43.282372;5.4039153 +0870051H;Lycée Professionnel Jean Baptiste Darnet;28 Avenue De Perigueux;87500;St Yrieix La Perche;45.5048579;1.2020927 +0820039X;Lycée Professionnel Jean Baylet;20 Avenue Du Marechal Leclerc;82403;Valence;44.1144715;0.8939859 +0490058X;Lycée Professionnel Jean Bertin;25 Rue Marceau;49401;Saumur;47.250839;-0.0746699 +0160036C;Lycée Professionnel Jean Caillaud;Cite Scolaire De Puyguillen;16600;Ruelle Sur Touvre;45.6767619;0.219218 +0240007C;Lycée Professionnel Jean Capelle;Av Marechal Leclerc;24108;Bergerac;44.8433876;0.477406 +0290072W;Lycée Professionnel Jean Chaptal;35 Chemin Des Justices;29191;Quimper;47.9918404;-4.1193383 +0371123V;Lycée Professionnel Jean Chaptal;Rue Du Clos Des Gardes;37400;Amboise;47.3993791;0.9837666 +0400047J;Lycée Professionnel Jean D Arcet;1 Place Ste Quitterie;40801;Aire Sur L Adour;43.6966232;-0.2690223 +0450750W;Lycée Professionnel Jean De La Taille;Allee De Burglengenfeld;45300;Pithiviers;48.1679602;2.2424772 +0820700R;Lycée Professionnel Jean De Prades;Route De Toulouse;82102;Castelsarrasin;44.0334192;1.1211267 +0090003E;Lycée Professionnel Jean Durroux;1 Et 3 Avenue Jean Durroux;09000;Ferrieres Sur Ariege;42.942515;1.61601 +0280700J;Lycée Professionnel Jean Felix Paulsen;1 Boulevard Du 8 Mai 1945;28205;Chateaudun;48.0626165;1.3363333 +0670129B;Lycée Professionnel Jean Geiler;14 Rue Des Bateliers;67064;Strasbourg;48.5802264;7.755564 +0350761M;Lycée Professionnel Jean Guehenno;3 Promenade Du Gue Maheu;35305;Fougeres;48.3418263;-1.2027846 +0560053D;Lycée Professionnel Jean Guehenno;79 Avenue De La Marne;56017;Vannes;47.6597846;-2.7844548 +0610049X;Lycée Professionnel Jean Guehenno;16 Rue Pierre Huet;61105;Flers;48.744858;-0.552585 +0541283B;Lycée Professionnel Jean Hanzelet;Place De Trey;54701;Pont A Mousson;48.9095605;6.0576284 +0440310Y;Lycée Professionnel Jean Jacques Audubon;10 Boulevard Paul Langevin;44220;Coueron;47.2137802;-1.7226389 +0230003D;Lycée Professionnel Jean Jaures;38 Rue Jean Jaures;23200;Aubusson;45.7620497;3.541304 +0350031U;Lycée Professionnel Jean Jaures;24 Rue Victor Rault;35205;Rennes;48.0924623;-1.6893605 +0141599M;Lycée Professionnel Jean Jooris;1 Rue Salvador Allende;14162;Dives Sur Mer;49.2802055;-0.0990374 +0132211H;Lycée Professionnel Jean Lurcat;Chemin De St Macaire;13693;Martigues;43.4217515;5.0344096 +0451067R;Lycée Professionnel Jean Lurcat;4 Rue Du Perron;45401;Fleury Les Aubrais;47.9295823;1.9245113 +0690045Z;Lycée Professionnel Jean Lurcat;4 Rue Ludovic Arrachart;69008;Lyon 08;45.7323666;4.8628141 +0572075W;Lycée Professionnel Jean Mace;58 Avenue Jean Mermoz;57290;Fameck;49.3016789;6.1145402 +0941232D;Lycée Professionnel Jean Mace;103 Rue Mirabeau;94600;Choisy Le Roi;48.7637484;2.4216827 +0541605B;Lycée Professionnel Jean Marc Reiser;1 Rue Du Stade;54810;Longlaville;49.5258418;5.7963167 +9730094P;Lycée Professionnel Jean Marie Michotte;Boulevard De La Republique;97305;Cayenne;4.9367721;-52.323572 +0142178S;Lycée Professionnel Jean Mermoz;1 Rue Georges Fauvel;14504;Vire;48.8384706;-0.8776385 +0150036H;Lycée Professionnel Jean Mermoz;10 Rue Du Docteur Chibret;15005;Aurillac;44.9354809;2.4517539 +0180010N;Lycée Professionnel Jean Mermoz;Allee Des Colleges;18028;Bourges;47.0995745;2.4231125 +0340012D;Lycée Professionnel Jean Mermoz;25 Rue Ferdinand De Lesseps;34500;Beziers;43.3492469;3.2328792 +0950949R;Lycée Professionnel Jean Mermoz;21 Rue Emile Combres;95560;Montsoult;49.0673876;2.3237953 +0020022N;Lycée Professionnel Jean Monnet;22 Rue D Aboville;02800;La Fere;49.6600295;3.3666683 +0030060Z;Lycée Professionnel Jean Monnet;39 Place Jules Ferry;03401;Yzeure;46.5660374;3.3552285 +0220075M;Lycée Professionnel Jean Monnet;9 Rue Des Ursulines;22800;Quintin;48.4057441;-2.9105637 +0470782V;Lycée Professionnel Jean Monnet;Rue Marcel Pagnol;47510;Foulayronnes;44.231803;0.6430793 +0541816F;Lycée Professionnel Jean Monnet;8 Rue St Don;54110;Dombasle Sur Meurthe;48.6255302;6.3542519 +0590124P;Lycée Professionnel Jean Monnet;111 Avenue De Dunkerque;59042;Lille;50.6334462;3.0310112 +0910631S;Lycée Professionnel Jean Monnet;51 Ave Du Gal De Gaulle;91260;Juvisy Sur Orge;48.6952319;2.3801097 +0920164D;Lycée Professionnel Jean Monnet;128 Avenue Jean Jaures;92120;Montrouge;48.813054;2.3056284 +0540086A;Lycée Professionnel Jean Morette;1 Rue Du College;54970;Landres;49.3163495;5.8025968 +0080066D;Lycée Professionnel Jean Moulin;996 Av De La Cite Scolaire;08500;Revin;49.9269895;4.6510517 +0130150T;Lycée Professionnel Jean Moulin;1 Boulevard Marcel Cachin;13110;Port De Bouc;43.4076675;4.9949774 +0180026F;Lycée Professionnel Jean Moulin;45 Rue Jean Moulin;18206;St Amand Montrond;46.7345343;2.5142771 +0340013E;Lycée Professionnel Jean Moulin;Avenue Martyrs De La Resistance;34521;Beziers;43.3429502;3.233546 +0590188J;Lycée Professionnel Jean Moulin;49 Bd Du General De Gaulle;59051;Roubaix;50.68501;3.17217 +0623327E;Lycée Professionnel Jean Moulin;3 Rue Jean Moulin;62117;Brebieres;50.3312733;3.0211252 +0782602E;Lycée Professionnel Jean Moulin;18 Rue Du Docteur Audigier;78150;Le Chesnay;48.8263325;2.1329428 +0931739K;Lycée Professionnel Jean Moulin;9 Rue Jean Moulin;93110;Rosny Sous Bois;48.8656577;2.4930275 +0940143V;Lycée Professionnel Jean Moulin;8 Rue Du Docteur Lebel;94307;Vincennes;48.8465644;2.4312472 +7200093N;Lycée Professionnel Jean Nicoli;Cours Pierangeli;20296;Bastia;42.6985128;9.4509783 +0782593V;Lycée Professionnel Jean Perrin;1 Rue Lucien Sampaix;78210;St Cyr L Ecole;48.8028083;2.0597426 +0910715H;Lycée Professionnel Jean Perrin;26 Rue Leontine Sohier;91163;Longjumeau;48.6909037;2.3033805 +9740910W;Lycée Professionnel Jean Perrin;Rue Du Lycée;97440;St Andre;0.0;0.0 +0540081V;Lycée Professionnel Jean Prouve;53 Rue De Bonsecours;54052;Nancy;48.6757518;6.1959881 +0721301Y;Lycée Professionnel Jean Rondeau;Route De Rahay;72120;St Calais;47.932495;0.7654461 +0160049S;Lycée Professionnel Jean Rostand;Rue Louise Leriget;16017;Angouleme;45.6532678;0.1797211 +0580050P;Lycée Professionnel Jean Rostand;9 Boulevard Saint Exupery;58019;Nevers;47.0040888;3.1563606 +0760082B;Lycée Professionnel Jean Rostand;Hameau De Neufmesnil;76550;Offranville;49.857248;1.019358 +0572012C;Lycée Professionnel Jean Victor Poncelet;7 Rue Paul Valery;57070;Metz;49.1090537;6.2192344 +0120014A;Lycée Professionnel Jean Vigo;Le Puits De Cales;12100;Millau;44.090247;3.059129 +0590256H;Lycée Professionnel Jean-baptiste Carpeaux;Rue Pelabon;59154;Crespin;50.4079774;3.6689185 +0300056Y;Lycée Professionnel Jean-baptiste Dumas;Place De La Belgique;30104;Ales;44.128435;4.0760861 +0622275L;Lycée Professionnel Jean-charles Cazin;42 Rue Cazin;62321;Boulogne Sur Mer;50.7206515;1.609213 +0790015M;Lycée Professionnel Jean-francois Cail;1 Rue Maurice Gadioux;79110;Chef Boutonne;46.1042153;-0.0694183 +0670127Z;Lycée Professionnel Jean-frederic Oberlin;4 Rue De L'academie;67000;Strasbourg;48.5816645;7.7601602 +0820006L;Lycée Professionnel Jean-louis Etienne;4 Rue Lavoisier;82303;Caussade;44.1612238;1.5403785 +0450043C;Lycée Professionnel Jeannette Verdier;35 Avenue Gaillardin;45207;Montargis;47.9940559;2.7288555 +0640040W;Lycée Professionnel Jean-pierre Champo;7 Rue Des Evades De France;64130;Mauleon Licharre;43.2241138;-0.8777013 +0931024H;Lycée Professionnel Jean-pierre Timbaud;103 Avenue De La Republique;93300;Aubervilliers;48.9044948;2.3912217 +0330119A;Lycée Professionnel Jehan Duperier;Chemin Tiran;33166;St Medard En Jalles;44.8960834;-0.705443 +0310091F;Lycée Professionnel Jolimont;44 Chemin Cassaing;31079;Toulouse;43.6168618;1.4763001 +0620150B;Lycée Professionnel Joliot Curie;9 Rue Leo Lagrange;62590;Oignies;50.4673489;2.9974803 +0150022T;Lycée Professionnel Joseph Constant;3 Rue De La Coste;15300;Murat;45.1109178;2.860881 +0573283J;Lycée Professionnel Joseph Cressot;6 Rue Mozart;57310;Guenange;49.2998824;6.2004744 +0371100V;Lycée Professionnel Joseph Cugnot;Les Hucherolles;37500;Chinon;47.170435;0.2522948 +0310050L;Lycée Professionnel Joseph Gallieni;39 Rue Adonis;31086;Toulouse;43.6313119;1.4271439 +0220072J;Lycée Professionnel Joseph Savina;5 Place De La Republique;22220;Treguier;48.7859048;-3.2335656 +0680027K;Lycée Professionnel Joseph Vogt;5 Rue Paul Burgi;68290;Masevaux;47.7681754;6.9983689 +0010017N;Lycée Professionnel Joseph-marie Carriat;1 Rue De Crouy;01011;Bourg En Bresse;46.1981597;5.2225406 +0690281F;Lycée Professionnel Joseph-marie Jacquard;20 Rue Louis Auguste Blanqui;69921;Oullins;45.7118138;4.8102856 +0250063H;Lycée Professionnel Jouffroy D'abbans;16 Rue De L'helvetie;25112;Baume Les Dames;47.3504244;6.3520577 +0570237Y;Lycée Professionnel Jouffroy D'abbans;39 Chemin D'avril;57250;Moyeuvre Grande;49.264711;6.0130248 +0260042E;Lycée Professionnel Jules Algoud;Rue Barthelemy De Laffemas;26901;Valence;44.917059;4.915496 +6200003J;Lycée Professionnel Jules Antonini;Av Noel Franchini;20186;Ajaccio;41.919229;8.738635 +0340045P;Lycée Professionnel Jules Ferry;270 Avenue De La Colline;34077;Montpellier;43.607123;3.8419213 +0620142T;Lycée Professionnel Jules Ferry;61 Rue Bocquet Flochel;62022;Arras;50.2791979;2.7961293 +0900019G;Lycée Professionnel Jules Ferry;18 Rue De Verdun;90101;Delle;47.5088411;6.99076 +0110008Z;Lycée Professionnel Jules Fil;Boulevard Joliot Curie;11021;Carcassonne;43.2148527;2.3675002 +0760062E;Lycée Professionnel Jules Lecesne;99 Rue Jules Lecesne;76090;Le Havre;49.4931496;0.1201446 +0290102D;Lycée Professionnel Jules Lesven;34 Rue Jules Lesven;29801;Brest;48.4056622;-4.4787824 +0760132F;Lycée Professionnel Jules Siegfried;1 Rue Dume D'aplemont;76054;Le Havre;49.4974627;0.1241609 +0600063T;Lycée Professionnel Jules Uhry;10 Rue Aristide Briand;60107;Creil;49.2580804;2.4755361 +0020013D;Lycée Professionnel Jules Verne;;02401;Chateau Thierry;49.064633;3.479051 +0140020W;Lycée Professionnel Jules Verne;12 Rue Lucien Bossoutrot;14126;Mondeville;49.176963;-0.319769 +0220019B;Lycée Professionnel Jules Verne;Route De Corlay;22205;Guingamp;48.5538053;-3.1497201 +0601897L;Lycée Professionnel Jules Verne;Rue Caradane;60210;Grandvilliers;49.665342;1.939705 +0620187S;Lycée Professionnel Jules Verne;54 Avenue Du Mont Levin;62630;Etaples;50.5205723;1.6454476 +0690130S;Lycée Professionnel Jules Verne;75 Route De Saint Clement;69173;Tarare;45.8973407;4.4437914 +0020078Z;Lycée Professionnel Julie Daubie;3 Place Robert Aumont;02011;Laon;49.5611106;3.6109299 +0570145Y;Lycée Professionnel Julie Daubie;Rue Joffre;57120;Rombas;49.2534261;6.0999961 +0560042S;Lycée Professionnel Julien Crozet;4 Rue Des Recollets;56290;Port Louis;47.706917;-3.3562259 +0710077Y;Lycée Professionnel Julien De Balleure;141 Avenue Boucicaut;71321;Chalon Sur Saone;46.7888739;4.8359262 +9740082W;Lycée Professionnel Julien De Rontaunay;Cite Scolaire Du Butor;97492;St Denis;48.936181;2.357443 +0500037U;Lycée Professionnel Julliot De La Morandiere;Rue De La Crete;50406;Granville;48.8264841;-1.5782999 +0570109J;Lycée Professionnel La Briquerie;15 Route De La Briquerie;57100;Thionville;49.363583;6.1609848 +0731043M;Lycée Professionnel La Cardiniere;191 Chemin De La Cardiniere;73017;Chambery;45.5625736;5.9310737 +0350709F;Lycée Professionnel La Champagne;2 Rue Du Sergent Harris;35506;Vitre;48.1210809;-1.229255 +0440032W;Lycée Professionnel La Chauviniere;2 Rue De La Fantaisie;44322;Nantes;47.2535027;-1.5719339 +0220064A;Lycée Professionnel La Closerie;10 Rue Pierre Loti;22410;St Quay Portrieux;48.6576705;-2.8477013 +0831354W;Lycée Professionnel La Coudouliere;Chemin De La Coudouliere;83140;Six Fours Les Plages;43.0975054;5.8170999 +0511190S;Lycée Professionnel La Fontaine Du Ve;Avenue De La Fontaine Du Ve;51122;Sezanne;48.726156;3.7170255 +0572755K;Lycée Professionnel La Malgrange;Chaussee D'amerique;57129;Thionville;49.3717995;6.16505 +0330069W;Lycée Professionnel La Morlette;62 Rue Camille Pelletan;33151;Cenon;44.8564425;-0.5190718 +0620220C;Lycée Professionnel La Peupleraie;Rue Jules Mattez;62430;Sallaumines;50.4131724;2.8628092 +0500027H;Lycée Professionnel La Roquelle;5 Rue Des Courtilles;50207;Coutances;49.0412575;-1.4410444 +0490005P;Lycée Professionnel La Roseraie;200 Rue Du Docteur Guichard;49015;Angers;47.4491825;-0.5502084 +0691676X;Lycée Professionnel La Sauvagere;5 Rue Communieu;69256;Lyon 09;45.7963868;4.8256612 +0940137N;Lycée Professionnel La Source;54 Avenue De La Source;94130;Nogent Sur Marne;48.8298866;2.4656826 +0540060X;Lycée Professionnel La Tournelle;2 Rue De Lorraine;54550;Pont St Vincent;48.6020214;6.1028929 +0920158X;Lycée Professionnel La Tournelle;87 Boulevard National;92250;La Garenne Colombes;48.9076332;2.2381311 +9720430K;Lycée Professionnel La Trinite;Cite Scolaire Frantz Fanon;97220;La Trinite;0.0;0.0 +0190701J;Lycée Professionnel Lavoisier;Rue Lavoisier;19311;Brive La Gaillarde;45.1527958;1.5133529 +0590189K;Lycée Professionnel Lavoisier;31 Rue Lavoisier;59059;Roubaix;50.676634;3.1298011 +0601470X;Lycée Professionnel Lavoisier;8 Rue Jules Ferry;60110;Meru;49.2297048;2.125951 +0771995A;Lycée Professionnel Le Champ De Claye;71 Rue Pasteur;77414;Claye Souilly;48.9380503;2.6749314 +0080048J;Lycée Professionnel Le Chateau;1 Place Du Chateau;08208;Sedan;49.6930187;4.9075711 +0880001Y;Lycée Professionnel Le Chesnois;44 Rue Du Chesnois;88240;Bains Les Bains;48.0003643;6.2481647 +0020061F;Lycée Professionnel Le Corbusier;Passage Le Corbusier;02200;Soissons;49.376636;3.32342 +0590216P;Lycée Professionnel Le Corbusier;74 Rue De Lille;59208;Tourcoing;50.7224263;3.1544077 +0950656X;Lycée Professionnel Le Corbusier;30 Rue Jean Jaures;95240;Cormeilles En Parisis;48.9784415;2.2015132 +0390020K;Lycée Professionnel Le Corbusier - Batiment;255 Rue Charles Ragmey;39015;Lons Le Saunier;46.6790667;5.5648843 +0860039A;Lycée Professionnel Le Dolmen;71 Rue Du Dolmen;86036;Poitiers;46.5781164;0.3674768 +0730006K;Lycée Professionnel Le Grand Arc;265 Chemin De La Charrette;73200;Albertville;45.661974;6.3661905 +0881339C;Lycée Professionnel Le Haut De Bellieu;Route Du Stand;88306;Neufchateau;48.360448;5.706625 +0520940P;Lycée Professionnel Le Haut Du Val;9 Rue St Exupery;52008;Chaumont;48.0985269;5.1329186 +0760114L;Lycée Professionnel Le Hurle-vent;1 Avenue Jean Moulin;76470;Le Treport;50.0566018;1.3684502 +0580042F;Lycée Professionnel Le Mont-chatelet;Boulevard Saint Saturnin;58210;Varzy;47.359394;3.383169 +0740010J;Lycée Professionnel Le Saleve;59 Route D'etrembieres;74107;Annemasse;46.1860606;6.2311348 +0810016C;Lycée Professionnel Le Sidobre;80 Avenue Rene Cassin;81103;Castres;43.5958949;2.2370177 +0861113T;Lycée Professionnel Le Verger;14 Rue Jean Pidoux;86100;Chatellerault;46.8190339;0.5624545 +0130063Y;Lycée Professionnel Leau (bd);63 Boulevard Leau;13008;Marseille 08;43.2534617;5.3862316 +0440116M;Lycée Professionnel Leloup Bouhier;11 Boulevard De Launay;44100;Nantes;47.2102412;-1.5728065 +0460012N;Lycée Professionnel Leo Ferre;75 Avenue Cavaignac;46300;Gourdon;44.7341503;1.3771929 +0620059C;Lycée Professionnel Leo Lagrange;Rue Victor Schoelcher;62160;Bully Les Mines;50.4289606;2.764828 +0830016S;Lycée Professionnel Leon Blum;1111 Boulevard Leon Blum;83011;Draguignan;43.5271939;6.4854513 +0060022S;Lycée Professionnel Leon Chiris;51 Chemin Des Capucins;06130;Grasse;43.6507373;6.9307514 +9740552G;Lycée Professionnel Leon De Lepervanche;Av Raymond Mondon;97828;Le Port;-20.9343006;55.3032171 +0070009X;Lycée Professionnel Leon Pavin;Route De La Gare;07210;Chomerac;44.7068354;4.6613722 +0130172S;Lycée Professionnel Leonard De Vinci;8 Rue Du Rempart;13007;Marseille 07;43.2888013;5.3649989 +0240984P;Lycée Professionnel Leonard De Vinci;Chemin De Saltgourde;24000;Perigueux;45.1949847;0.6770043 +0260050N;Lycée Professionnel Leonard De Vinci;Avenue Henri Becquerel;26702;Pierrelatte;44.380434;4.698 +0440035Z;Lycée Professionnel Leonard De Vinci;31 Rue De La Bottiere;44319;Nantes;47.2381866;-1.5215084 +0530079Y;Lycée Professionnel Leonard De Vinci;129 Boulevard De L Europe;53103;Mayenne;48.3010639;-0.623681 +0595678B;Lycée Professionnel Leonard De Vinci;Rue De La Renaissance;59125;Trith St Leger;50.330411;3.482308 +0791029P;Lycée Professionnel Leonard De Vinci;37 Boulevard Lescure;79300;Bressuire;46.8321172;-0.4870723 +0920680P;Lycée Professionnel Leonard De Vinci;5 Avenue Henri Barbusse;92220;Bagneux;48.7979653;2.3079713 +0170030R;Lycée Professionnel Leonce Vieljeux;118 Rue Des Gonthieres;17001;La Rochelle;46.1774956;-1.1513475 +0741164N;Lycée Professionnel Les Carillons;3 Avenue De Prelevet;74962;Cran Gevrier;45.8949329;6.1014022 +0360011S;Lycée Professionnel Les Charmilles;;36019;Chateauroux;46.859935;1.7200194 +0331460H;Lycée Professionnel Les Chartrons;130 Rue Du Jardin Public;33026;Bordeaux;44.8558775;-0.573314 +0921592F;Lycée Professionnel Les Cotes De Villebon;3 Rue Henri Etlin;92360;Meudon;48.794339;2.2211379 +0520041M;Lycée Professionnel Les Franchises;65 Rue Eugene Gallion;52200;Langres;47.8670425;5.3428612 +0911493D;Lycée Professionnel Les Freres Moreau;Rte De Brunoy;91480;Quincy Sous Senart;48.6769958;2.5295462 +0630256L;Lycée Professionnel Les Gravouses;4 Rue De Barante;63000;Clermont Ferrand;45.7867625;3.0677884 +0790090U;Lycée Professionnel Les Grippeaux;1 Rue Edouard Herriot;79204;Parthenay;46.638001;-0.2595192 +0592833J;Lycée Professionnel Les Hauts De Flandre;Rue Jude Blanckaert;59474;Seclin;50.5438352;3.0261402 +0250067M;Lycée Professionnel Les Huisselets;8 Avenue De Lattre De Tassigny;25206;Montbeliard;47.5117132;6.7966991 +0600004D;Lycée Professionnel Les Jacobins;2 Rue Vincent De Beauvais;60000;Beauvais;49.4306564;2.086988 +0100039N;Lycée Professionnel Les Lombards;12 Avenue Des Lombards;10025;Troyes;48.2747672;4.0725464 +0060042N;Lycée Professionnel Les Palmiers;15 Avenue Banco;06300;Nice;43.6985849;7.2919792 +0500028J;Lycée Professionnel Les Sapins;7 Rue Des Courtilles;50207;Coutances;49.0412575;-1.4410444 +0440537V;Lycée Professionnel Les Savarieres;5 Avenue De Glinde;44230;St Sebastien Sur Loire;47.211077;-1.4905961 +0860010U;Lycée Professionnel Les Terres Rouges;Rue Jean Moulin;86400;Civray;46.134811;0.284259 +0550004F;Lycée Professionnel Ligier Richier;3 R Du Stade;55013;Bar Le Duc;48.7757163;5.1677519 +0772225A;Lycée Professionnel Lino Ventura;Avenue Marcel Pagnol;77834;Ozoir La Ferriere;48.7607611;2.6924768 +0381602K;Lycée Professionnel L'odyssee;5 Boulevard Des Colleges;38232;Pont De Cheruy;45.7571727;5.1765032 +0270027H;Lycée Professionnel Louis Aragon;23 Rue Vieille D'eragny;27140;Gisors;49.279774;1.776579 +0440352U;Lycée Professionnel Louis Armand;Bd Jean De Grandmaison;44270;Machecoul;46.9910112;-1.8193584 +0592712C;Lycée Professionnel Louis Armand;Rue Mermoz;59571;Jeumont;50.2945453;4.1134706 +0731249L;Lycée Professionnel Louis Armand;321 Rue Du Grand Champ;73020;Chambery;45.5947932;5.9203139 +0860052P;Lycée Professionnel Louis Armand;63 Rue De La Bugellerie;86022;Poitiers;46.5974421;0.3312595 +0910756C;Lycée Professionnel Louis Armand;9 Rue Pierre De Coubertin;91330;Yerres;48.7130134;2.4793774 +0130033R;Lycée Professionnel Louis Bleriot;8 Bd De La Liberation;13721;Marignane;43.4192168;5.251698 +0592611T;Lycée Professionnel Louis Bleriot;Rue Gauthier;59407;Cambrai;50.162795;3.254045 +0780273Y;Lycée Professionnel Louis Bleriot;Rue Leo Lagrange;78197;Trappes;48.777248;1.981663 +0911401D;Lycée Professionnel Louis Bleriot;2 Avenue Des Meuniers;91154;Etampes;48.4369719;2.1466572 +0921505L;Lycée Professionnel Louis Dardenne;25 Rue Louis Dardenne;92170;Vanves;48.8200923;2.287475 +0400004M;Lycée Professionnel Louis Darmante;23 Rue Jean Baptiste Gabarra;40130;Capbreton;43.6409194;-1.4311885 +0640012R;Lycée Professionnel Louis De Foix;4 Av Jean Rostand;64103;Bayonne;43.4849353;-1.4754015 +0160119T;Lycée Professionnel Louis Delage;245 Avenue Victor Hugo;16100;Cognac;45.6873156;-0.310592 +9710052E;Lycée Professionnel Louis Delgres;Rue Amedee Fengarol;97160;Le Moule;0.0;0.0 +0250002S;Lycée Professionnel Louis Garnier;29 Rue Des Cantons;25404;Audincourt;47.4827916;6.8496192 +0230027E;Lycée Professionnel Louis Gaston Roussillat;La Valette;23320;St Vaury;46.220712;1.76194 +0881370L;Lycée Professionnel Louis Geisler;3 Rue De La Belle Orge;88110;Raon L Etape;48.4037543;6.8345632 +0920163C;Lycée Professionnel Louis Girard;85 Rue Louis Girard;92240;Malakoff;48.8121182;2.2822567 +0350032V;Lycée Professionnel Louis Guilloux;76 Av Des Buttes De Coesmes;35703;Rennes;48.1249592;-1.6332938 +0590187H;Lycée Professionnel Louis Loucheur;8 Boulevard De Lyon;59059;Roubaix;50.6776818;3.1752082 +0040011R;Lycée Professionnel Louis Martin Bret;Allee Du Parc;04100;Manosque;43.827348;5.7848689 +0251002D;Lycée Professionnel Louis Pergaud;91 93 Boulevard Blum;25022;Besancon;47.263734;6.0453694 +0590037V;Lycée Professionnel Louise De Bettignies;Boulevard Paul Bezin;59407;Cambrai;50.1724242;3.2399915 +0690046A;Lycée Professionnel Louise Labe;65 Boulevard Yves Farge;69007;Lyon 07;45.7396294;4.8310855 +0161003D;Lycée Professionnel Louise Michel;Rue Villebois Mareuil;16700;Ruffec;46.0266253;0.1927592 +0380099B;Lycée Professionnel Louise Michel;30 Rue Louise Michel;38037;Grenoble;45.1687882;5.7081825 +0440063E;Lycée Professionnel Louis-jacques Goussier;20 Rue Du Chateau De Reze;44400;Reze;47.1806714;-1.5600159 +0783214V;Lycée Professionnel Lucien Rene Duchesne;49 Av Maurice De Hirsch;78170;La Celle St Cloud;48.8413881;2.1185136 +0491646Y;Lycée Professionnel Ludovic Menard;24 Place Des Tilleuls;49800;Trelaze;47.4464427;-0.504172 +0700038L;Lycée Professionnel Luxembourg;16 Pl Du 11e Chasseurs;70014;Vesoul;47.6206224;6.1507974 +9840267T;Lycée Professionnel Lycée Professionnel De Faaa;Faaa;98704;Faaa;0.0;0.0 +0595856V;Lycée Professionnel Lycée Professionnel De L'yser;18 Bis Route D'esquelbecq;59726;Wormhout;50.8568096;2.4376592 +9840341Y;Lycée Professionnel Lycée Professionnel De Mahina;Mahina;98709;Mahina;-41.2713267;174.9088245 +9840166H;Lycée Professionnel Lycée Professionnel D'uturoa;Uturoa;98735;Uturoa;-16.7331706;-151.4403326 +9840160B;Lycée Professionnel Lycée Professionnel St Joseph;Papeete;98718;Punaauia;-17.5753232;-149.6059799 +9840019Y;Lycée Professionnel Lycée Protestant Tuteao A Vaih;Uturoa;98735;Uturoa;-16.7331706;-151.4403326 +0600016S;Lycée Professionnel M.grenet (industriel);13 Avenue De Huy;60321;Compiegne;49.4025055;2.8188505 +0600017T;Lycée Professionnel M.grenet (mixte);13 Avenue De Huy;60321;Compiegne;49.4025055;2.8188505 +0690047B;Lycée Professionnel Magenta;64 66 Rue Magenta;69100;Villeurbanne;45.7660861;4.8714756 +0060043P;Lycée Professionnel Magnan;34 Rue Auguste Renoir;06000;Nice;43.693971;7.2481912 +0720013Y;Lycée Professionnel Mal Leclerc Hauteclocque;Rue Du Grand Douai;72500;Chateau Du Loir;47.6925943;0.4146083 +0860022G;Lycée Professionnel Marc Godrie;19 Rue Des Meures;86206;Loudun;47.0112905;0.0893771 +0692418D;Lycée Professionnel Marc Seguin;20 Boulevard Marcel Sembat;69694;Venissieux;45.7159355;4.8819736 +0331668J;Lycée Professionnel Marcel Dassault;3 Rue Chateaubriand;33695;Merignac;44.8051719;-0.6258497 +0750788Z;Lycée Professionnel Marcel Deprez;39 Rue De La Roquette;75011;Paris 11;48.8552481;2.3736701 +0880007E;Lycée Professionnel Marcel Goulette;3 Rue Etienne Simard;88132;Charmes;48.3785984;6.2980364 +0610004Y;Lycée Professionnel Marcel Mezen;25 Rue Marcel Mezen;61041;Alencon;48.4296677;0.1044785 +0870730W;Lycée Professionnel Marcel Pagnol;103 Rue De Feytiat;87039;Limoges;45.8201238;1.2850476 +0930129K;Lycée Professionnel Marcel Pagnol;1 Rue Jules Guesde;93140;Bondy;48.9041305;2.4741275 +0760146W;Lycée Professionnel Marcel Sembat;128 Rue Leon Salva;76300;Sotteville Les Rouen;49.4152133;1.0855593 +0010021T;Lycée Professionnel Marcelle Parde;47 Avenue Alsace Lorraine;01011;Bourg En Bresse;46.2019083;5.2208402 +0611157B;Lycée Professionnel Marechal Leclerc;30 Rue Jean Henri Fabre;61014;Alencon;48.4113332;0.0934634 +0840041N;Lycée Professionnel Maria Casares;1 Rue Des Bavardages;84082;Avignon;43.9356519;4.8292506 +0753350J;Lycée Professionnel Maria Deraismes;19 Rue Maria Deraismes;75017;Paris 17;48.8947945;2.3274455 +0300041G;Lycée Professionnel Marie Curie;Route De Florac;30270;St Jean Du Gard;44.1140867;3.8718827 +0600062S;Lycée Professionnel Marie Curie;Boulevard Pierre De Coubertin;60180;Nogent Sur Oise;49.266627;2.464235 +0630023H;Lycée Professionnel Marie Curie;19 Boulevard Ambroise Brugiere;63039;Clermont Ferrand;45.7894827;3.1182095 +0690109U;Lycée Professionnel Marie Curie;64 Boulevard Eugene Reguillon;69100;Villeurbanne;45.7614463;4.8962541 +0100027A;Lycée Professionnel Marie De Champagne;2 Avenue Marie De Champagne;10026;Troyes;48.3040593;4.0617662 +0750776L;Lycée Professionnel Marie Laurencin;114 Quai De Jemmapes;75010;Paris 10;48.8746642;2.3635341 +0540061Y;Lycée Professionnel Marie Marvingt;8 Rue Jean Moulin;54510;Tomblaine;48.6960232;6.2152112 +0171432P;Lycée Professionnel Maritime;Avenue Du Marechal Juin;17022;La Rochelle;46.1575289;-1.180007 +0561486L;Lycée Professionnel Maritime;38 Avenue Louis Bougo;56410;Etel;47.6612096;-3.2015002 +9720787Y;Lycée Professionnel Maritime;Quartier Beausejour;97220;La Trinite;0.0;0.0 +0641770B;Lycée Professionnel Maritime;Av Eugene Corre Qu De L Untxin;64500;Ciboure;43.387537;-1.675432 +0762735K;Lycée Professionnel Maritime Anita Conti;84 Quai Guy De Maupassant;76402;Fecamp;49.763033;0.3711007 +7200118R;Lycée Professionnel Maritime Batterie Les Turquines;Batterie Les Turquines;20200;Bastia;42.697283;9.450881 +0561861U;Lycée Professionnel Maritime Centre Francois Toullec;10 Rue Francois Toullec;56100;Lorient;47.7323195;-3.3792775 +0623903F;Lycée Professionnel Maritime De Boulogne Le Portel;6 Rue Georges Honore;62480;Le Portel;50.7190753;1.5860061 +0442092K;Lycée Professionnel Maritime Des Pays De La Loire;Rue Du Port Boyer;44315;Nantes;47.2410135;-1.5374604 +0291107W;Lycée Professionnel Maritime Du Guilvinec;Zone D'activites De Kervarc'h;29730;Treffiagat;47.807191;-4.261599 +9741098A;Lycée Professionnel Maritime E.a.m Le Port;1 Rue De La Poste;97821;Le Port;-20.9341273;55.291486 +0501599S;Lycée Professionnel Maritime Et Aquacole;Rue Matignon;50103;Cherbourg Octeville;49.63774;-1.618745 +0341699M;Lycée Professionnel Maritime Paul Bousquet;Rue Des Cormorans;34207;Sete;43.4192489;3.6729485 +0221624W;Lycée Professionnel Maritime Pierre Loti;1 Rue Pierre Loti;22501;Paimpol;48.8017255;-3.0192327 +0351914R;Lycée Professionnel Maritime Public;116 Bd Des Tallards;35403;St Malo;48.64063;-2.0087017 +0070031W;Lycée Professionnel Marius Bouvier;Avenue Lamastre;07301;Tournon Sur Rhone;45.068112;4.799918 +0570077Z;Lycée Professionnel Maryse Bastie;Rue Du Tivoli;57700;Hayange;49.3347331;6.0763366 +0350062C;Lycée Professionnel Maupertuis;Rue Pierre De Coubertin;35407;St Malo;48.6400752;-2.0058975 +0590133Z;Lycée Professionnel Maurice Duhamel;1079 Rue Guy Mocquet;59373;Loos;50.6025679;3.0118909 +0132319A;Lycée Professionnel Maurice Genevoix;Av Du General De Gaulle;13700;Marignane;43.4128456;5.2048951 +0030059Y;Lycée Professionnel Maurice Guyot;Rue Ernest Montuses;03105;Montlucon;46.3228476;2.6201222 +0623864N;Lycée Professionnel Maximilien De Robespierre;89 Rue Leon Blum;62307;Lens;50.4452423;2.7877082 +0711322B;Lycée Professionnel Metiers De L Automobile;1 Rue De La Manutention;71321;Chalon Sur Saone;46.7792864;4.8435194 +0750784V;Lycée Professionnel Metiers De L'ameublement;9 Rue Pierre Bourdan;75012;Paris 12;48.8468966;2.3914855 +0570144X;Lycée Professionnel Metiers Du Batiment Et T.p;154 Chemin De Blory;57950;Montigny Les Metz;49.0873538;6.1678431 +0440034Y;Lycée Professionnel Michelet;41 Boulevard Michelet;44322;Nantes;47.2343621;-1.556604 +0941298A;Lycée Professionnel Michelet;1 Rue Michelet;94120;Fontenay Sous Bois;48.8518993;2.4732442 +0270051J;Lycée Professionnel Modeste Leroy;32 Rue Pierre Brossolette;27016;Evreux;49.0216265;1.1618627 +0640080P;Lycée Professionnel Moliere;11 Rue Moliere;64301;Orthez;43.4800842;-0.7688565 +0730050H;Lycée Professionnel Monge;1 Avenue Marius Berroir;73000;Chambery;45.5660699;5.9341975 +0801194N;Lycée Professionnel Montaigne;3 Rue Montaigne;80084;Amiens;49.9115347;2.3217034 +0390021L;Lycée Professionnel Montciel;1 Avenue De Montciel;39016;Lons Le Saunier;46.6724435;5.5427757 +0260116K;Lycée Professionnel Montesquieu;2 Rue De Montesquieu;26000;Valence;44.9384823;4.9256564 +0250064J;Lycée Professionnel Montjoux;25 Av Du Cdt Marceau;25010;Besancon;47.2490074;6.016123 +0910755B;Lycée Professionnel Nadar;42bis Rue Charles Mory;91210;Draveil;48.6831045;2.4326334 +0610027Y;Lycée Professionnel Napoleon;15 Rue Des Sports;61306;L Aigle;48.7582148;0.6363548 +0330032F;Lycée Professionnel Nicolas Bremontier;152 Cours De L Yser;33077;Bordeaux;44.823338;-0.5706489 +0623463C;Lycée Professionnel Normandie-niemen;Avenue Normandie-niemen;62228;Calais;50.9523426;1.9037152 +0750796H;Lycée Professionnel Octave Feuillet;9 Rue Octave Feuillet;75016;Paris 16;48.8613442;2.2723331 +0330102G;Lycée Professionnel Odilon Redon;R Maquis Vignes Oudides;33250;Pauillac;45.1907714;-0.7495194 +0441656L;Lycée Professionnel Pablo Neruda;Rue Pablo Neruda;44340;Bouguenais;47.16973;-1.623866 +0240028A;Lycée Professionnel Pablo Picasso;64 Av Georges Pompidou;24004;Perigueux;45.1924021;0.7267429 +0620255R;Lycée Professionnel Pablo Picasso;2 Boulevard Anatole France;62210;Avion;50.4005382;2.8354531 +0693200D;Lycée Professionnel Pablo Picasso;12 Che De La Cote A Cailloux;69700;Givors;45.6012034;4.7609173 +0830059N;Lycée Professionnel Parc St Jean;Place Du 4 Septembre;83059;Toulon;43.1230487;5.9530768 +0320040V;Lycée Professionnel Pardailhan;Chemin De Baron;32008;Auch;43.6546824;0.5749589 +9740472V;Lycée Professionnel Patu De Rosemont;72 Rue Joseph Hubert;97470;St Benoit;0.0;0.0 +0910628N;Lycée Professionnel Paul Belmondo;23 Av De La Division Leclerc;91290;Arpajon;48.5966104;2.2461236 +0640013S;Lycée Professionnel Paul Bert;73 Rue Bourgneuf;64115;Bayonne;43.4908724;-1.471528 +0941355M;Lycée Professionnel Paul Bert;1 Rue Du Gue Aux Aurochs;94700;Maisons Alfort;48.8155654;2.4242767 +0330114V;Lycée Professionnel Paul Broca;7 Avenue De Verdun;33220;Ste Foy La Grande;44.8382054;0.2152357 +0141276L;Lycée Professionnel Paul Cornu;9 Rue Paul Cornu;14107;Lisieux;49.15711;0.2224269 +0570072U;Lycée Professionnel Paul Dassenoy;Route De Conthil;57340;Morhange;48.9118442;6.6439595 +0490801E;Lycée Professionnel Paul Emile Victor;101 Rue Des Roses;49243;Avrille;47.5010511;-0.5887594 +0450786K;Lycée Professionnel Paul Gauguin;10 Av De La Rech Scientifique;45071;Orleans;47.8395647;1.9428858 +0790043T;Lycée Professionnel Paul Guerin;19 Rue Des Fiefs;79004;Niort;46.3145227;-0.443402 +0050008G;Lycée Professionnel Paul Heraud;25 Chemin De Bonne;05000;Gap;44.5671853;6.0801577 +0730900G;Lycée Professionnel Paul Heroult;307 Avenue Du Mont Cenis;73302;St Jean De Maurienne;45.2725564;6.3502605 +9710418C;Lycée Professionnel Paul Lacave;Avenue Germain St-ruff;97130;Capesterre Belle Eau;16.05;-61.57 +0300011Z;Lycée Professionnel Paul Langevin;21 Rue De La Redoute;30301;Beaucaire;43.8118169;4.6414128 +0593495D;Lycée Professionnel Paul Langevin;Rue Paul Langevin;59119;Waziers;50.387032;3.102779 +0600061R;Lycée Professionnel Paul Langevin;3 Avenue Montaigne;60009;Beauvais;49.414897;2.1063143 +0921677Y;Lycée Professionnel Paul Langevin;9 Rue Paul Langevin;92000;Nanterre;48.890582;2.184257 +9740934X;Lycée Professionnel Paul Langevin;6 Allee Des Hibiscus;97480;St Joseph;-21.3813367;55.6096619 +0540037X;Lycée Professionnel Paul Lapie;6 Avenue Du Docteur Kahn;54301;Luneville;48.5978471;6.5103348 +0540082W;Lycée Professionnel Paul Louis Cyffle;1 Rue Cyffle;54000;Nancy;48.6867685;6.1812641 +0921625S;Lycée Professionnel Paul Painleve;5 Rue De La Montagne;92400;Courbevoie;48.8955744;2.2588753 +0060028Y;Lycée Professionnel Paul Valery;1 Avenue Saint Jacques;06500;Menton;43.7834263;7.511873 +0080838T;Lycée Professionnel Paul Verlaine;Rue Du Docteur Gobinet;08305;Rethel;49.5167514;4.3704596 +0130012T;Lycée Professionnel Perdiguier;Che Des Moines Qua Du Trebon;13200;Arles;43.6017739;4.6243751 +9720091S;Lycée Professionnel Petit Manoir;Quartier Petit Manoir;97286;Le Lamentin;0.0;0.0 +9830306R;Lycée Professionnel Petro Attiti;1 Rue Teillard De Chardin;98849;Noumea;-22.2758;166.458 +0332345V;Lycée Professionnel Philadelphe De Gerde;3, Allee Philadelphe De Gerde;33600;Pessac;44.7870737;-0.6520039 +0332346W;Lycée Professionnel Philippe Cousteau;Rue Arnaudin;33240;St Andre De Cubzac;44.9912798;-0.4501201 +0580020G;Lycée Professionnel Pierre Beregovoy;56 Rue Gambetta;58600;Fourchambault;47.019317;3.0831499 +0420021V;Lycée Professionnel Pierre Coton;Le Bourg;42510;Neronde;45.838012;4.238368 +0620189U;Lycée Professionnel Pierre De Coubertin;320 Boulevard Du 8 Mai;62225;Calais;50.958627;1.837843 +0770932V;Lycée Professionnel Pierre De Coubertin;Chaussee De Paris;77100;Meaux;48.9559126;2.869815 +0421691K;Lycée Professionnel Pierre Desgranges;32 Rue Des Bullieux;42166;Andrezieux Boutheon;45.5374319;4.2959439 +0170031S;Lycée Professionnel Pierre Doriole;221 Avenue De Perigny;17012;La Rochelle;46.1616962;-1.1243029 +0060027X;Lycée Professionnel Pierre Et Marie Curie;353 Av Du Doyen Jean Lepine;06500;Menton;43.7776606;7.4846068 +0500066A;Lycée Professionnel Pierre Et Marie Curie;377 Rue De L Exode;50010;St Lo;49.107496;-1.0893717 +0530040F;Lycée Professionnel Pierre Et Marie Curie;1 Rue Branly;53204;Chateau Gontier;47.8341556;-0.6994932 +0570051W;Lycée Professionnel Pierre Et Marie Curie;21 Rue Des Vosges;57804;Freyming Merlebach;49.153717;6.8188013 +0590015W;Lycée Professionnel Pierre Et Marie Curie;1 Rue Du Foyer;59620;Aulnoye Aymeries;50.2026966;3.835739 +0640079N;Lycée Professionnel Pierre Et Marie Curie;Avenue Pierre Angot;64150;Mourenx;43.3703832;-0.6251249 +0760013B;Lycée Professionnel Pierre Et Marie Curie;33 Rue Du Calvaire;76210;Bolbec;49.5698937;0.4817532 +0890053Z;Lycée Professionnel Pierre Et Marie Curie;1 Place Walesa;89094;Sens;48.20065;3.28268 +0132276D;Lycée Professionnel Pierre Latecoere;Avenue Des Bolles;13800;Istres;43.5039779;4.994962 +0752388N;Lycée Professionnel Pierre Lescot;35 Rue Des Bourdonnais;75001;Paris 01;48.8602998;2.3453363 +0050027C;Lycée Professionnel Pierre Mendes France;Place Des Aires;05400;Veynes;44.5309937;5.7990979 +0650035K;Lycée Professionnel Pierre Mendes France;Rue Du College;65501;Vic En Bigorre;43.388411;0.056435 +0801514L;Lycée Professionnel Pierre Mendes France;Route De Saint Denis;80201;Peronne;49.9361685;2.9435387 +0880013L;Lycée Professionnel Pierre Mendes France;100 Rue Du Shah De Perse;88141;Contrexeville;48.1816011;5.8950657 +0911578W;Lycée Professionnel Pierre Mendes France;Av De L Aunette;91133;Ris Orangis;48.6393295;2.4105804 +0620167V;Lycée Professionnel Pierre Mendes-france;Rue Antoine De Saint Exupery;62166;St Pol Sur Ternoise;51.024809;2.343421 +0620192X;Lycée Professionnel Pierre Mendes-france;Rue De Saint Omer;62701;Bruay La Buissiere;50.4874136;2.5382001 +0951090U;Lycée Professionnel Pierre Mendes-france;1 Rue De Goussainville;95400;Villiers Le Bel;49.0047221;2.4173569 +0390024P;Lycée Professionnel Pierre Vernotte;6 Route De St Laurent;39261;Moirans En Montagne;46.4348496;5.7248544 +0250013D;Lycée Professionnel Pierre-adrien Paris;8 Rue Mercator;25000;Besancon;47.2604676;5.9960551 +0160048R;Lycée Professionnel Pierre-andre Chabanne;28 Rue Bir Hakeim;16260;Chasseneuil Sur Bonnieure;45.8235254;0.453489 +0590005K;Lycée Professionnel Pierre-joseph Fontaine;2 Av Leo Lagrange;59416;Anzin;50.3696507;3.4916736 +0592610S;Lycée Professionnel Pierre-joseph Laurent;128 Rue Edmond Laudeau;59580;Aniche;50.3265557;3.2625615 +0101122R;Lycée Professionnel Pigier;40 Place Jean Jaures;10000;Troyes;48.2947188;4.0701812 +9720844K;Lycée Professionnel Place D Armes;Place D Armes;97232;Le Lamentin;14.6166147;-60.9887419 +0590098L;Lycée Professionnel Placide Courtoy;Rue Pierre Curie;59330;Hautmont;50.255989;3.905332 +0130054N;Lycée Professionnel Poinso-chapuis;49 Traverse Parangon;13272;Marseille 08;43.2427647;5.3806897 +9720005Y;Lycée Professionnel Pointe Des Negres;Rue Marie Therese Gertrude;97261;Fort De France;14.6040646;-61.084565 +0700882D;Lycée Professionnel Pontarcher;Place Jacques Brel;70014;Vesoul;47.6354117;6.1686975 +0240039M;Lycée Professionnel Porte D Aquitaine;15 Rue A Bonneau;24800;Thiviers;45.4243846;0.9267852 +0271319M;Lycée Professionnel Porte De Normandie;830 Chemin Des Poissonniers;27130;Verneuil Sur Avre;48.7405634;0.9156189 +0740031G;Lycée Professionnel Porte Des Alpes;26 Rue De La Curdy;74151;Rumilly;45.8644993;5.9406325 +0380101D;Lycée Professionnel Portes De L'oisans;Avenue Aristide Briand;38220;Vizille;45.067413;5.769686 +0240048X;Lycée Professionnel Pre De Cordy;Av. De La Dordogne;24200;Sarlat La Caneda;44.8754955;1.2126435 +0390056Z;Lycée Professionnel Pre Saint Sauveur;Cite Scolaire Pre St Sauveur;39201;St Claude;46.387405;5.8677659 +0430103D;Lycée Professionnel Privé;1 Place Neron;43120;Monistrol Sur Loire;45.2946516;4.1708988 +9711139L;Lycée Professionnel Privé 3ec;1108 Imm.capt Grammont;97142;Les Abymes;16.27395;-61.502615 +0700947Z;Lycée Professionnel Privé Agricole Sainte Marie;13 Rue Victor Hugo;70106;Gray;47.4436381;5.5914217 +0311200L;Lycée Professionnel Privé Airbus - Toulouse;57 Chemin Du Sang De Serp;31060;Toulouse;43.6158658;1.4282595 +0382093U;Lycée Professionnel Privé Alpes Sud-isere;Rond Point Du Villaret-susville;38350;Susville;44.9166667;5.7811111 +0030112F;Lycée Professionnel Privé Anna Rodier;39 Cours Jean Jaures;03008;Moulins;46.5669568;3.3349455 +0631050Z;Lycée Professionnel Privé Anna Rodier;54 Boulevard Jean Jaures;63000;Clermont Ferrand;45.7679299;3.0798386 +0570283Y;Lycée Professionnel Privé Anne De Mejanes;3 Rue Goussaud;57000;Metz;49.1235079;6.173823 +0420987V;Lycée Professionnel Privé Arago;28 Rue Arago;42300;Roanne;46.0292922;4.0690676 +0920979P;Lycée Professionnel Privé Atelier App.pte Mecan.;19 Rue Victor Hugo;92130;Issy Les Moulineaux;48.8264404;2.2758007 +0690652J;Lycée Professionnel Privé Ateliers Appren De L'industrie;148 Avenue Franklin Roosevelt;69120;Vaulx En Velin;45.7546702;4.9351651 +9710879D;Lycée Professionnel Privé Bel Air;Moudong Nord;97122;Baie Mahault;0.0;0.0 +0312059V;Lycée Professionnel Privé Billieres;18 Rue Du 14 Juillet;31100;Toulouse;43.5910132;1.414594 +0450764L;Lycée Professionnel Privé Blanche De Castille;;45303;Pithiviers;48.156984;2.192561 +9710083N;Lycée Professionnel Privé Boc-calmet;Rue Paul Lacave- Assainissement;97110;Pointe A Pitre;16.241111;-61.533056 +0381806G;Lycée Professionnel Privé Bordier;26 Rue Prosper Merimee;38100;Grenoble;45.1737355;5.7260681 +0292139T;Lycée Professionnel Privé Brest - Rive Droite - Javouhey;4 Rue Des Remparts;29238;Brest;48.3832192;-4.5023223 +0131484T;Lycée Professionnel Privé Brise Lames;Route De La Vierge;13500;Martigues;43.4170127;5.0437853 +0131463V;Lycée Professionnel Privé Cabucelle (la);16 Boulevard Denis Papin;13015;Marseille 15;43.3342857;5.3626747 +0312063Z;Lycée Professionnel Privé Castelnouvel;Castelnouvel;31490;Leguevin;43.594447;1.254584 +9741308D;Lycée Professionnel Privé Catholique De La Montagne;Chemin Piton Tresor Pk7;97417;St Denis;48.936181;2.357443 +0131474G;Lycée Professionnel Privé Caucadis;Boulevard Alfred Casile;13127;Vitrolles;43.4478245;5.2426452 +0941407U;Lycée Professionnel Privé Ce 3p;5 Rue Rene Robin;94200;Ivry Sur Seine;48.8137588;2.3781028 +0740152N;Lycée Professionnel Privé Cecam;Chemin De Beauregard;74490;St Jeoire;46.1429375;6.4595274 +0131415T;Lycée Professionnel Privé Celony;4 Bis Av Delattre De Tassigny;13090;Aix En Provence;43.5303056;5.4383758 +0241129X;Lycée Professionnel Privé Cent.tech.coif.esthetique;49 Rue Candillac;24100;Bergerac;44.8507187;0.4855329 +0300028T;Lycée Professionnel Privé Centre Cevenol;3 Quai Boissier De Sauvages;30100;Ales;44.12714;4.0767327 +0240092V;Lycée Professionnel Privé Chambre Com Et Industrie;Zone Industrielle Av H Deluc;24750;Boulazac;45.180548;0.768949 +0541998D;Lycée Professionnel Privé Charles De Foucauld;1 Rue Jeannot;54000;Nancy;48.6894923;6.1881706 +0672602P;Lycée Professionnel Privé Charles De Foucauld;Allee D'athenes;67306;Schiltigheim;48.6075759;7.7099709 +0133276R;Lycée Professionnel Privé Charles Peguy;102 Rue Sylvabelle;13006;Marseille 06;43.2882408;5.3748305 +0131445A;Lycée Professionnel Privé Charlotte Grawitz;20 Chemin Chateau Gombert;13013;Marseille 13;43.3350871;5.4206869 +0381758E;Lycée Professionnel Privé Charmilles;15 Rue Montesquieu;38100;Grenoble;45.1762181;5.7280364 +0932424E;Lycée Professionnel Privé Chne Or;17 Rue Du Clos Benard;93300;Aubervilliers;48.9114654;2.3861326 +0541370W;Lycée Professionnel Privé Claude Daunot;10 Boulevard Clemenceau;54052;Nancy;48.6779501;6.1805671 +0133305X;Lycée Professionnel Privé Clovis Hugues;1 Rue Fernand Dol;13100;Aix En Provence;43.5260677;5.452697 +0754165V;Lycée Professionnel Privé Coiffure;15-17 Rue Des Fillettes;75018;Paris 18;48.8946669;2.3633961 +0771190A;Lycée Professionnel Privé Coiffure Et Esthetique;6 Rue Jean Jaures;77670;St Mammes;48.3866661;2.8150085 +0440267B;Lycée Professionnel Privé Coiffure P.masson;12 Rue D Alger;44100;Nantes;47.210504;-1.5676183 +0601164P;Lycée Professionnel Privé Croiset;7 Avenue De Joinville;60500;Chantilly;49.1761667;2.4601513 +0470821M;Lycée Professionnel Privé Ctr.etu.fem.rur.l Ermitage;304 Avenue Amouroux;47000;Agen;44.2118115;0.6187838 +0442071M;Lycée Professionnel Privé Daniel Brottier;Chemin Du Couvent;44340;Bouguenais;47.187338;-1.5866796 +0762285W;Lycée Professionnel Privé Daniel Brottier;11 Impasse Du Chateau;76660;Smermesnil;49.8063368;1.4206513 +0691724Z;Lycée Professionnel Privé De Coiffure;22 Rue D'algerie;69001;Lyon 01;45.767637;4.8327698 +0691723Y;Lycée Professionnel Privé De Coiffure De Lyon;28 Rue Valentin Couturier;69004;Lyon 04;45.7763552;4.8274152 +9711080X;Lycée Professionnel Privé De Coiffure Et D'esthetique;Lot 76 Centre Commercial;97139;Les Abymes;16.27395;-61.502615 +0311190A;Lycée Professionnel Privé De Coiffure Skhole D'art;55 Avenue Louis Breguet;31400;Toulouse;43.5772349;1.4794166 +0820885S;Lycée Professionnel Privé De Coiffure Skhole D'art;3 Rue Bataille De Dunkerque;82000;Montauban;44.0068309;1.3461514 +0280687V;Lycée Professionnel Privé De Couasnon;37 Rue De Moronval;28100;Dreux;48.7329442;1.3867752 +0441791H;Lycée Professionnel Privé De L Erdre;13 Rue Du Gal Leclerc;44390;Nort Sur Erdre;47.4389938;-1.4992748 +0593020M;Lycée Professionnel Privé De La Coiffure;68 Rue Saint Etienne;59800;Lille;50.6361624;3.0595327 +0761353H;Lycée Professionnel Privé De La Coiffure A. Pourriere;24 Rue D'herbouville;76000;Rouen;49.4489961;1.0861707 +0331547C;Lycée Professionnel Privé De La Cote D Argent;46 Bis Avenue De La Liberation;33380;Biganos;44.6407598;-0.9743709 +0573270V;Lycée Professionnel Privé De La Salle;2 Rue St Maximin;57070;Metz;49.1048183;6.1935928 +0420982P;Lycée Professionnel Privé Des Collines;4 Rue Ferdinand Buisson;42800;Rive De Gier;45.5261265;4.6165582 +0561606S;Lycée Professionnel Privé Des Metiers N.dame De La Paix;Beg Er Lann;56275;Ploemeur;47.7506666;-3.3973939 +0061462G;Lycée Professionnel Privé Don Bosco;40 Place Don Bosco;06046;Nice;43.7086006;7.2813297 +0131466Y;Lycée Professionnel Privé Don Bosco;78 Rue Stanislas Torrents;13006;Marseille 06;43.2855609;5.3795529 +0530073S;Lycée Professionnel Privé Don Bosco;18 Boulevard Anatole France;53102;Mayenne;48.295993;-0.6158636 +0622211S;Lycée Professionnel Privé Du Calaisis;71 73 Rue Chantilly;62100;Calais;50.9435057;1.8553408 +0070113K;Lycée Professionnel Privé Du Sacre Coeur;7 Avenue De La Gare;07301;Tournon Sur Rhone;45.0637796;4.834587 +0720823D;Lycée Professionnel Privé Du Sacre Coeur De Pontlieu;30 Rue Des Sapins;72100;Le Mans;47.9798872;0.2105683 +0022109G;Lycée Professionnel Privé Du Val De Serre;10 Rue De La Halle;02270;Pouilly Sur Serre;49.6813163;3.587762 +0741285V;Lycée Professionnel Privé E.c.a.;2 Rue Des Carillons;74942;Annecy Le Vieux;45.914268;6.1419587 +0760143T;Lycée Professionnel Privé Ecole Industrielle;50 Rue Meridienne;76100;Rouen;49.4256007;1.0837566 +0131434N;Lycée Professionnel Privé Ecole Libre De Metiers;24 Rue Des Bons Enfants;13006;Marseille 06;43.2908998;5.388285 +0132193N;Lycée Professionnel Privé Edmond Rostand;114 Rue Edmond Rostand;13006;Marseille 06;43.2821817;5.3828274 +0440255N;Lycée Professionnel Privé Encia;6 Rue Crebillon;44003;Nantes;47.214125;-1.5596911 +0011258M;Lycée Professionnel Privé Ensemble Scolaire Lamartine;41 Rue Georges Girerd;01302;Belley;45.7612095;5.6848602 +0941915W;Lycée Professionnel Privé Espace Beaute Thalgo;16 Rue D'alsace Lorraine;94100;St Maur Des Fosses;48.8054972;2.4744603 +0754184R;Lycée Professionnel Privé Eugene Napoleon;254 Rue Du Fg Saint Antoine;75012;Paris 12;48.8489028;2.3916976 +9710827X;Lycée Professionnel Privé Faeec;Lot 76 Centre Commercial;97139;Les Abymes;16.27395;-61.502615 +0940823J;Lycée Professionnel Privé Foyer Des Ptt;36 Avenue Du President Wilson;94234;Cachan;48.7922449;2.3291571 +9830272D;Lycée Professionnel Privé Francois D Assise (ddec);Lot 25 Pie Era;98870;Bourail;44.5379358;1.6760691 +0381732B;Lycée Professionnel Privé Francois Verguin;Rte De Sablons;38550;Le Peage De Roussillon;45.3619259;4.7957204 +0280689X;Lycée Professionnel Privé Francoise D Aubigne;23 Rue Du Marechal Maunoury;28130;Maintenon;48.587838;1.5762147 +9830273E;Lycée Professionnel Privé Gabriel Rivat (ddec);Village;98824;Pouebo;0.0;0.0 +0920985W;Lycée Professionnel Privé Georges Guerin;5 Rue Deves;92200;Neuilly Sur Seine;48.8831271;2.2707066 +0610724F;Lycée Professionnel Privé Giel Don Bosco;Les Cours;61210;Giel Courteilles;48.7569657;-0.1951044 +0592973L;Lycée Professionnel Privé Gilbert Cesbron;34 Rue Richard Lenoir;59060;Roubaix;50.6978383;3.171796 +0631736V;Lycée Professionnel Privé Godefroy De Bouillon;14 Rue Godefroy De Bouillon;63037;Clermont Ferrand;45.781122;3.0929055 +0700077D;Lycée Professionnel Privé Gpe Scolaire De La Compassion;Route De Saint Sulpice;70110;Villersexel;47.551021;6.432211 +0941722L;Lycée Professionnel Privé Gregor Mendel;205 Rue De Fontenay;94300;Vincennes;48.8488616;2.4272778 +0280691Z;Lycée Professionnel Privé Guery;5 Rue Des Marais;28000;Chartres;48.4414704;1.4978421 +0592976P;Lycée Professionnel Privé Helene Boucher;Rue Roger Salengro;59490;Somain;50.3734891;3.2807582 +0133282X;Lycée Professionnel Privé Henri Leroy;1 Rue Des Ecoles;13230;Port St Louis Du Rhone;43.3867633;4.8031175 +0881623L;Lycée Professionnel Privé Horticulture Et Paysage;Roville Aux Chenes;88700;Roville Aux Chenes;48.358452;6.630084 +9710878C;Lycée Professionnel Privé I S F C A;9 Centre Commercial Le Galion;97139;Les Abymes;16.27395;-61.502615 +0442227G;Lycée Professionnel Privé Ifom;17 Bd Des Martyrs Nantais;44200;Nantes;47.2030171;-1.5454647 +0641664L;Lycée Professionnel Privé Immac.concept.beau Frene;Bd Edouard Herriot;64051;Pau;43.3050247;-0.3782249 +0520692V;Lycée Professionnel Privé Immaculee Conception;1 Bis Rue Du Mal De Lattre;52115;St Dizier;48.6385221;4.9486945 +0530904V;Lycée Professionnel Privé Immaculee Conception;15 Rue Crossardiere;53000;Laval;48.0734452;-0.7680567 +0593027V;Lycée Professionnel Privé Industries Lilloises;82 Rue Des Meuniers;59000;Lille;50.6221365;3.0581427 +0500132X;Lycée Professionnel Privé Ingenieur Cachin;4 Rue Ingenieur Cachin;50100;Cherbourg Octeville;49.6396131;-1.617479 +0691875N;Lycée Professionnel Privé Inst Techn Carrieres Carole;21 Quai Tilsitt;69002;Lyon 02;45.7558718;4.8263561 +0593094T;Lycée Professionnel Privé Institut Familial;16 Ter Rue Lamartine;59426;Armentieres;50.6866796;2.8758479 +0141866C;Lycée Professionnel Privé Institut Lemonnier;60 Rue D Herouville;14013;Caen;49.1935816;-0.3487181 +0501825M;Lycée Professionnel Privé Institut Saint Lo;18 Rue De L'oratoire;50180;Agneaux;49.1184709;-1.1060995 +0680154Y;Lycée Professionnel Privé Institut Sonnenberg;1 Rue Du Moulin;68130;Carspach;47.6651522;7.2235055 +0680161F;Lycée Professionnel Privé Institution Don Bosco;60 Rue D'ensisheim;68272;Wittenheim;47.8136036;7.3394336 +0681656F;Lycée Professionnel Privé Institution Saint Jean;3 Route De Bale;68025;Colmar;48.0119328;7.3843166 +0672305S;Lycée Professionnel Privé Institution Sainte Clotilde;19 Rue De Verdun;67083;Strasbourg;48.5874674;7.7724234 +0672299K;Lycée Professionnel Privé Institution Ste Philomene;19a Boulevard Hanauer;67504;Haguenau;48.8113152;7.7888563 +0570225K;Lycée Professionnel Privé Interentreprise;Rue Robert Schuman;57220;Boulay Moselle;49.185664;6.491576 +0572500H;Lycée Professionnel Privé Interentreprise;Rue Jacques Touba;57430;Sarralbe;48.991472;7.024928 +0690654L;Lycée Professionnel Privé Interfora;6 Rue Jean Mace;69190;St Fons;45.7059602;4.8532599 +0492082X;Lycée Professionnel Privé Itec;2 Rue Pilastre;49100;Angers;47.479365;-0.5445983 +0381899H;Lycée Professionnel Privé Itec Boisfleury;76 Grande Rue;38701;La Tronche;45.2047507;5.7377606 +0930934K;Lycée Professionnel Privé Itmc;Cent Aff Paris Nord Imm Ampere;93153;Le Blanc Mesnil;48.941345;2.46436 +0132790M;Lycée Professionnel Privé Jacques Raynaud;59 Traverse Susini;13013;Marseille 13;43.3355765;5.4182619 +0690660T;Lycée Professionnel Privé Japy;1 Place Louis Pradel;69203;Lyon 01;45.7680387;4.8375832 +0311883D;Lycée Professionnel Privé Jasmin Coiffure;4 Rue Des Teinturiers;31300;Toulouse;43.5969809;1.4357278 +0382893N;Lycée Professionnel Privé Jean Marie Vianney;22 Avenue Hector Berlioz;38260;La Cote St Andre;45.386288;5.26266 +9830270B;Lycée Professionnel Privé Jean Xxiii (ddec);Route Du Mont Mou;98890;Paita;0.0;0.0 +0421002L;Lycée Professionnel Privé Jean-baptiste D'allard;7 Rue Du Bief;42601;Montbrison;45.602668;4.0626362 +0693370N;Lycée Professionnel Privé Jean-baptiste De La Salle;1 Rue Neyret;69283;Lyon 01;45.7715543;4.8280213 +0541377D;Lycée Professionnel Privé Jean-baptiste Vatelot;6 Rue De La Republique;54203;Toul;48.6729066;5.8919085 +0133274N;Lycée Professionnel Privé Jeanne D Arc;5 Rue Saint Roch;13632;Arles;43.6743556;4.6432771 +0331569B;Lycée Professionnel Privé Jeanne D Arc;3 Place De La Fraternite;33230;St Medard De Guizieres;45.0154483;-0.0603156 +0610721C;Lycée Professionnel Privé Jeanne D Arc;16 Rue De La Vicomte;61203;Argentan;48.7455115;-0.0208491 +0740156T;Lycée Professionnel Privé Jeanne D Arc;18 Bis Avenue Jules Ferry;74200;Thonon Les Bains;46.3724399;6.4832781 +0880133S;Lycée Professionnel Privé Jeanne D Arc;1 Avenue Chanzy;88600;Bruyeres;48.2086223;6.7188568 +0950800D;Lycée Professionnel Privé Jeanne D Arc;20 Rue De La Liberte;95100;Argenteuil;48.9433576;2.2505002 +0950812S;Lycée Professionnel Privé Jeanne D Arc;2 Bis Bd Toussaint Lucas;95130;Franconville;48.9875913;2.2306862 +0080093H;Lycée Professionnel Privé Jeanne D'arc;27 Rue Bournizet;08400;Vouziers;49.3973761;4.6979472 +0120105Z;Lycée Professionnel Privé Jeanne D'arc;3 Place Du Mandarous;12104;Millau;44.0996885;3.0784006 +0121310J;Lycée Professionnel Privé Jeanne D'arc;23 Rue Lamartine;12402;St Affrique;43.956919;2.890886 +0220123P;Lycée Professionnel Privé Jeanne D'arc;4 Rue De La Bienfaisance;22300;Lannion;48.7287135;-3.4562299 +0381733C;Lycée Professionnel Privé Jeanne D'arc;2 Rue Jacques Prevert;38550;Le Peage De Roussillon;45.3712936;4.8011019 +0391001B;Lycée Professionnel Privé Jeanne D'arc;10 Rue Du Sauget;39303;Champagnole;46.7456228;5.9058307 +0511153B;Lycée Professionnel Privé Jeanne D'arc;94 Avenue De Laon;51100;Reims;49.2662257;4.0257119 +0730773U;Lycée Professionnel Privé Jeanne D'arc;3 Place De L'eglise;73203;Albertville;45.6780169;6.3908431 +0810108C;Lycée Professionnel Privé Jeanne D'arc;23 Rue De La Vanne;81200;Mazamet;43.4847739;2.3737673 +0881498A;Lycée Professionnel Privé Jeanne D'arc;6 Rue Du Canton;88202;Remiremont;48.0192476;6.5872824 +0881651S;Lycée Professionnel Privé Jeanne D'arc;1 Avenue De Herringen;88300;Neufchateau;48.3590156;5.6926631 +0271074W;Lycée Professionnel Privé Jeanne D'arc - Saint-anselme;11/13 R Leprevost De Beaumont;27300;Bernay;49.0942864;0.6045856 +0693372R;Lycée Professionnel Privé Jeanne De Lestonnac;132 Rue Vendome;69006;Lyon 06;45.7648477;4.8456527 +0930929E;Lycée Professionnel Privé Jeanne La Lorraine;3 Boulevard Du Nord;93340;Le Raincy;48.9017008;2.513264 +0100078F;Lycée Professionnel Privé Jeanne Mance;Rue Du Paradis;10000;Troyes;48.2903865;4.0658966 +9830299H;Lycée Professionnel Privé Joahana Vakie (ddec);Ecole De Nindiah;98816;Houailou;0.0;0.0 +0720825F;Lycée Professionnel Privé Joseph Roussel;50 Avenue Bollee;72000;Le Mans;48.0020204;0.2071975 +0450759F;Lycée Professionnel Privé L Abbaye;2 Rue De L Abbaye;45190;Beaugency;47.7765588;1.6341757 +0133275P;Lycée Professionnel Privé La Cadenelle;134 Bd Des Liberateurs;13012;Marseille 12;43.3020176;5.4572235 +0830128N;Lycée Professionnel Privé La Colette;Residence Ste Catherine Tour A;83000;Toulon;43.1200697;5.9383291 +0690697H;Lycée Professionnel Privé La Favorite Sainte Therese;107 Rue Cdt Charcot;69110;Ste Foy Les Lyon;45.7464605;4.7883369 +0740148J;Lycée Professionnel Privé La Fontaine;116 Rue De La Fontaine;74210;Faverges;45.7445312;6.2942685 +0060715V;Lycée Professionnel Privé La Providence;236 Route De Grenoble;06200;Nice;43.6819305;7.1990341 +0260082Y;Lycée Professionnel Privé La Providence;14 18 Rue Henri Chalamet;26000;Valence;44.9294183;4.8938111 +0311209W;Lycée Professionnel Privé La Providence;30 Rue Jean Moulin;31250;Revel;43.4582667;2.0065414 +0490886X;Lycée Professionnel Privé La Providence;33 Avenue Gustave Ferrie;49306;Cholet;47.0623002;-0.8582673 +0570248K;Lycée Professionnel Privé La Providence;4 Rue De Sarrelouis;57320;Bouzonville;49.2902615;6.5356148 +0570279U;Lycée Professionnel Privé La Providence;1 Rue Des Freres Mesguin;57260;Dieuze;48.8116359;6.7201268 +0331561T;Lycée Professionnel Privé La Ruche;1 Rue Poquelin Moliere;33000;Bordeaux;44.8401086;-0.576113 +0693332X;Lycée Professionnel Privé La Vidaude;Chemin De La Vidaude;69563;St Genis Laval;45.6925289;4.7800249 +0420992A;Lycée Professionnel Privé Lachaux;Rue Edouard Michot;42501;Le Chambon Feugerolles;45.3979878;4.3243091 +0020493A;Lycée Professionnel Privé Lacordaire;Route De Laon;02800;Charmes;49.655364;3.385869 +0501789Y;Lycée Professionnel Privé Le Bon Sauveur;Rue E De Surville;50001;St Lo;49.112089;-1.094787 +0381741L;Lycée Professionnel Privé Le Breda;L'epinette;38580;Allevard;45.404864;6.084012 +0640179X;Lycée Professionnel Privé Le Guichot;42 Rue D Espagne;64100;Bayonne;43.489412;-1.4771746 +0730772T;Lycée Professionnel Privé Le Margeriaz;28 Rue De Buisson Rond;73000;Barberaz;45.5665442;5.9414436 +0701016Z;Lycée Professionnel Privé Le Marteroy;7 Avenue Aristide Briand;70000;Vesoul;47.6288058;6.1668986 +0440282T;Lycée Professionnel Privé Le Masle;9 Rue Jean Pierre Dufrexou;44612;St Nazaire;47.2722287;-2.2058751 +0340923U;Lycée Professionnel Privé Le Parterre;15 Rue Courbezou;34600;Bedarieux;43.6146339;3.1582161 +0490903R;Lycée Professionnel Privé Le Pinier Neuf;;49601;Beaupreau;48.384469;0.1593171 +0131485U;Lycée Professionnel Privé Le Rocher;Montee De La Transhumance;13300;Salon De Provence;43.6449432;5.0949437 +0340992U;Lycée Professionnel Privé Le Sacre Coeur;46 Boulevard D Angleterre;34500;Beziers;43.3470472;3.2120912 +0592964B;Lycée Professionnel Privé Leonard De Vinci;10 Rue Notre Dame Des Victoires;59100;Roubaix;50.6950525;3.1805112 +0100063P;Lycée Professionnel Privé Leonie Aviat;3 Rue Etienne Pedron;10000;Troyes;48.3043057;4.0791779 +0490910Y;Lycée Professionnel Privé Les Ardilliers;1 Quai Du Jagueneau;49421;Saumur;47.2516631;-0.0584264 +0741287X;Lycée Professionnel Privé Les Bressis;85 Route Des Creuses;74600;Seynod;45.8932394;6.1001202 +0782100J;Lycée Professionnel Privé Les Chataigniers;11 Bis Avenue Jean Jaures;78000;Versailles;48.8174674;2.1442539 +0631408N;Lycée Professionnel Privé Les Cordeliers;Place Des Cordeliers;63100;Clermont Ferrand;45.7942923;3.1131005 +0740143D;Lycée Professionnel Privé Les Cordeliers;11 Rue M. Berthelot;74301;Cluses;46.0593129;6.5787129 +0061394H;Lycée Professionnel Privé Les Fauvettes;44 Avenue J De Noailles;06400;Cannes;43.5541346;6.9996242 +0381809K;Lycée Professionnel Privé Les Gorges;22 Rue Des Orphelines;38500;Voiron;45.3692772;5.5910909 +0090045A;Lycée Professionnel Privé Les Jacobins;Rue Du Rempart Du Touroncq;09100;Pamiers;43.114753;1.607953 +9710775R;Lycée Professionnel Privé Les Perseverants;28 Rue Lardenoy;97100;Basse Terre;15.9932345;-61.7242003 +0381805F;Lycée Professionnel Privé Les Portes De Chartreuse;387 Avenue De Stalingrad;38343;Voreppe;45.2947821;5.6336357 +0311971Z;Lycée Professionnel Privé Les Potiers;4 Rue Du Sachet;31400;Toulouse;43.5931107;1.4478264 +0382169B;Lycée Professionnel Privé Les Prairies;31 Rue Mainssieux;38500;Voiron;45.3630158;5.5928307 +0141868E;Lycée Professionnel Privé Les Rosiers;17 Chemin De Rocques;14100;Lisieux;49.154762;0.2306014 +9711064E;Lycée Professionnel Privé L'institut Des Ameriques;Forum De Grand-camp;97142;Les Abymes;16.27395;-61.502615 +0595723A;Lycée Professionnel Privé Louise De Marillac;2 Rue D'antin;59003;Lille;50.6268165;3.0474897 +0593066M;Lycée Professionnel Privé Lp Des Forges;13 Rue Du Couvent;59220;Denain;50.32239;3.382042 +0595918M;Lycée Professionnel Privé Lp E.p.i.d.;20 Rue De Lille;59140;Dunkerque;51.0277456;2.3724543 +0070100W;Lycée Professionnel Privé Marc Seguin-saint Charles;Route De Californie;07100;Annonay;45.2440877;4.6758762 +9830271C;Lycée Professionnel Privé Marcellin Champagnat (ddec);Rt Terr 1;98890;Paita;0.0;0.0 +0593015G;Lycée Professionnel Privé Maria Goretti;1/e Rue De Verlinghem;59832;Lambersart;50.653684;3.0247746 +0221867K;Lycée Professionnel Privé Marie Balavenne;47 Boulevard Laennec;22005;St Brieuc;48.5111408;-2.7744799 +0830116A;Lycée Professionnel Privé Marie France;220 Avenue Marcel Castie;83000;Toulon;43.1234854;5.941595 +0131449E;Lycée Professionnel Privé Marie Gasquet;38 R Electriciens St Barnabe;13012;Marseille 12;43.3041972;5.4172944 +0541364P;Lycée Professionnel Privé Marie Immaculee;33 Av Du General Leclerc;54000;Nancy;48.6821438;6.1840703 +0133277S;Lycée Professionnel Privé Modele Electronique;233 Bd St Marcel;13011;Marseille 11;43.2865274;5.4686969 +0740155S;Lycée Professionnel Privé Mont Blanc;148 Route De Doran;74700;Sallanches;45.9375916;6.6254618 +0820061W;Lycée Professionnel Privé Montauriol;1 Boulevard Montauriol;82000;Montauban;44.0100506;1.3594123 +0640184C;Lycée Professionnel Privé Montpensier;34 Rue Montpensier;64000;Pau;43.3010083;-0.3729012 +0261139X;Lycée Professionnel Privé Montplaisir;75 Rue Montplaisir;26000;Valence;44.9312156;4.9083899 +0940851P;Lycée Professionnel Privé Morin;22 Avenue De Verdun;94000;Creteil;48.7948841;2.462591 +0312064A;Lycée Professionnel Privé Myriam;9 Et 20 Rue Mage;31000;Toulouse;43.5977887;1.4472602 +0440262W;Lycée Professionnel Privé Nazareth;40 Rue Michel Grimault;44141;Chateaubriant;47.7156138;-1.3765267 +0290207T;Lycée Professionnel Privé Nd Du Kreisker;2 Rue Cadiou;29250;St Pol De Leon;48.6825255;-3.9870981 +0781582W;Lycée Professionnel Privé N-dame Du Grandchamp;97 Rue Royale;78000;Versailles;48.7933549;2.1239896 +0280684S;Lycée Professionnel Privé Notre Dame;Chateau Des Vaux;28240;St Maurice St Germain;48.494383;1.089488 +0350790U;Lycée Professionnel Privé Notre Dame;6 Rue De Vannes;35601;Redon;47.6473747;-2.0900067 +0350807M;Lycée Professionnel Privé Notre Dame;Rue De Dinan;35290;St Meen Le Grand;48.1926274;-2.188228 +0501941N;Lycée Professionnel Privé Notre Dame;43 Rue Sebline;50500;Carentan;49.3051863;-1.2471225 +0541357G;Lycée Professionnel Privé Notre Dame;1 Rue Du Bois Prieur;54350;Mont St Martin;49.5335424;5.764854 +0691465T;Lycée Professionnel Privé Notre Dame;1 Rue Honore Petetin;69700;Givors;45.5910944;4.7716109 +0783537W;Lycée Professionnel Privé Notre Dame;15 Rue De Strasbourg;78200;Mantes La Jolie;48.991379;1.711402 +0881530K;Lycée Professionnel Privé Notre Dame - Saint Joseph;23 Rue Thiers;88012;Epinal;48.1701687;6.4513835 +0141191U;Lycée Professionnel Privé Notre Dame De Fidelite;8 Rue Du Petit Clos Saint Marc;14074;Caen;49.2111144;-0.3733099 +0501787W;Lycée Professionnel Privé Notre Dame De La Providence;9 Rue Du Chanoine Beranger;50303;Avranches;48.686819;-1.3690481 +0572951Y;Lycée Professionnel Privé Notre Dame De La Providence;22 Place Notre Dame;57126;Thionville;49.3576246;6.1602375 +0880120C;Lycée Professionnel Privé Notre Dame De La Providence;14 Rue Pasteur;88109;St Die Des Vosges;48.2825237;6.9504488 +0601946P;Lycée Professionnel Privé Notre Dame De La Tilloye;1 Av De La Liberation;60204;Compiegne;49.4062037;2.8305249 +0141193W;Lycée Professionnel Privé Notre Dame De Nazareth;Avenue De La Basilique;14440;Douvres La Delivrande;49.2970159;-0.3732011 +0260078U;Lycée Professionnel Privé Notre Dame Des Champs;Rue Eugene Blain;26106;Romans Sur Isere;45.046612;5.042497 +0550700M;Lycée Professionnel Privé Notre Dame Des Vertus;30 Rue Du General De Gaulle;55500;Ligny En Barrois;48.6873861;5.3236757 +0470730N;Lycée Professionnel Privé Notre Dame La Compassion;1ter Rue De Langeot;47200;Marmande;44.4978288;0.1723321 +0480041J;Lycée Professionnel Privé Notre-dame;Quartier Fontanilles;48000;Mende;44.517611;3.501873 +0761347B;Lycée Professionnel Privé Notre-dame;5 Rue Hervieux;76500;Elbeuf;49.2889278;1.0017247 +0593030Y;Lycée Professionnel Privé Notre-dame Du Sacre Coeur;158 Avenue Saint Marcel;59120;Loos;50.6097613;3.0255216 +0754191Y;Lycée Professionnel Privé Notre-dame -st Vincent De Paul;49 Rue Bobillot;75013;Paris 13;48.828049;2.3529993 +0950804H;Lycée Professionnel Privé Notre-famille;2 Rue Des Patis;95520;Osny;49.0501663;2.086339 +0693406C;Lycée Professionnel Privé O R T;133 Rue Marius Berliet;69008;Lyon 08;45.7380862;4.8677943 +0141186N;Lycée Professionnel Privé Oasis;18 Rue De L Oratoire;14000;Caen;49.18111;-0.3599751 +0320652K;Lycée Professionnel Privé Oratoire Sainte-marie;50 Bis Rue Victor Hugo;32002;Auch;43.6505336;0.5776322 +0131424C;Lycée Professionnel Privé Ort Leon Bramson;9 Rue Des Forges;13010;Marseille 10;43.2791281;5.4104058 +0312191N;Lycée Professionnel Privé Ort Maurice Grynfogel;14 Rue Etienne Collongues;31770;Colomiers;43.601951;1.333155 +0430101B;Lycée Professionnel Privé Paradis;Rue Pont De La Chartreuse;43700;Brives Charensac;45.0529219;3.9138113 +0921932A;Lycée Professionnel Privé Passy Buzenval;50 Avenue Otis Mygatt;92508;Rueil Malmaison;48.8591268;2.1832515 +0300109F;Lycée Professionnel Privé Pasteur;3 Rue Pasteur;30110;La Grand Combe;44.2107935;4.0283226 +9720073X;Lycée Professionnel Privé Patronage Saint-louis;Route Du Lamentin Km 4;97200;Fort De France;14.609371;-61.07256 +9710066V;Lycée Professionnel Privé Pensionnat De Versailles;8 Rue Victor Hugues;97100;Basse Terre;16.1985639;-61.5901551 +9830294C;Lycée Professionnel Privé Pere Gueneau (ddec);24 Rte Terr 1 Village;98870;Bourail;0.0;0.0 +0131437S;Lycée Professionnel Privé Phocea R Attoyan (coiffure);1 Rue D Arcole;13006;Marseille 06;43.2894588;5.3774154 +0541363N;Lycée Professionnel Privé Pierre De Coubertin;5 R Du President Robert Schuman;54000;Nancy;48.6825501;6.1799118 +0050039R;Lycée Professionnel Privé Pierre Et Louis Poutrain;;05260;St Jean St Nicolas;44.6683333;6.2294444 +0771204R;Lycée Professionnel Privé Pigier Melun;30 Boulevard Victor Hugo;77000;Melun;48.5394984;2.6580575 +0070107D;Lycée Professionnel Privé Presentation De Marie;21 Avenue Notre Dame;07700;Bourg St Andeol;44.3730123;4.6450101 +0631048X;Lycée Professionnel Privé Rene Rambaud - Coiffure;27 Avenue Du Marechal Leclerc;63000;Clermont Ferrand;45.7843743;3.0868642 +0931573E;Lycée Professionnel Privé Robert Schuman;5 Avenue Du General De Gaulle;93440;Dugny;48.9494067;2.4150708 +0940820F;Lycée Professionnel Privé Robert Schuman;5-6 Rue De L'eglise;94340;Joinville Le Pont;48.8192804;2.4664616 +0381738H;Lycée Professionnel Privé Robin St Vincent De Paul;;38204;Vienne;46.3491859;-1.0850299 +0783330W;Lycée Professionnel Privé Roulleau;42 Rue De Tessancourt;78250;Meulan;49.0086789;1.9050102 +0801947G;Lycée Professionnel Privé Sacre Coeur;1 Rue De L Oratoire;80026;Amiens;49.8933658;2.3044821 +0801951L;Lycée Professionnel Privé Sacre Coeur;36 Boulevard Des Anglais;80200;Peronne;49.9264177;2.9342576 +0040502Z;Lycée Professionnel Privé Sacre Coeur (du);2 Avenue Des Thermes;04000;Digne Les Bains;44.0917238;6.2407469 +0940853S;Lycée Professionnel Privé Sacre-coeur;3 Boulevard De Stalingrad;94320;Thiais;48.7672928;2.4035625 +0631737W;Lycée Professionnel Privé Saint Alyre;20 Rue Sainte George;63037;Clermont Ferrand;45.7834986;3.0818986 +0070112J;Lycée Professionnel Privé Saint Andre;18 Rue Emile Combes;07400;Le Teil;44.5388909;4.6878848 +0572101Z;Lycée Professionnel Privé Saint Andre;1 Rue De L'eglise;57840;Ottange;49.4410443;6.0178869 +0251112Y;Lycée Professionnel Privé Saint Benigne;1 Rue Du Chanoine Prenel;25300;Pontarlier;46.9020101;6.3561888 +0021906L;Lycée Professionnel Privé Saint Charles;1 Rue Du Brouage;02300;Chauny;49.6147218;3.2121038 +0860752A;Lycée Professionnel Privé Saint Gabriel Notre Dame;27 Rue Du Vieux Palais;86108;Chatellerault;46.8156207;0.542157 +0891223W;Lycée Professionnel Privé Saint Jacques;6 Rue Du Faubourg St Jacques;89300;Joigny;47.9834057;3.3881557 +0950805J;Lycée Professionnel Privé Saint Jean;Rond Point De La Tour Du Mail;95117;Sannois;48.970782;2.256869 +0010099C;Lycée Professionnel Privé Saint Joseph;3 Rue Du Lycée;01000;Bourg En Bresse;0.0;0.0 +0010814E;Lycée Professionnel Privé Saint Joseph;101 Rue Henri Grobon;01705;Miribel;45.825157;4.9512247 +0022002R;Lycée Professionnel Privé Saint Joseph;2 Chaussee De Fontaine;02140;Fontaine Les Vervins;49.8365787;3.906547 +0180572Z;Lycée Professionnel Privé Saint Joseph;11 Rue Gourdon;18100;Vierzon;47.2244551;2.0669876 +0251592V;Lycée Professionnel Privé Saint Joseph;28 Av Fontaine Argent;25000;Besancon;47.2462201;6.0340665 +0382600V;Lycée Professionnel Privé Saint Joseph;1 Rue Lakanal;38506;Voiron;45.3678543;5.5870773 +0622192W;Lycée Professionnel Privé Saint Joseph;30 Rue Des Berceaux;62630;Etaples;50.5129606;1.6437015 +0690651H;Lycée Professionnel Privé Saint Joseph;327 Rue Garibaldi;69363;Lyon 07;45.7475187;4.8515373 +0741355W;Lycée Professionnel Privé Saint Joseph;St Martin Sur Arve;74703;Sallanches;45.912929;6.689997 +0831187P;Lycée Professionnel Privé Saint Joseph;2229 Route De Faveyrolles;83190;Ollioules;43.139436;5.8845344 +0900425Y;Lycée Professionnel Privé Saint Joseph;14 Rue De Badonvillers;90000;Belfort;47.6461321;6.8486574 +0680159D;Lycée Professionnel Privé Saint Joseph De Cluny;53 Avenue Roger Salengro;68100;Mulhouse;47.7509394;7.347119 +0570264C;Lycée Professionnel Privé Saint Joseph-la Providence;10 Rue De L Abbe Heydel;57801;Freyming Merlebach;49.1466767;6.8148536 +0572953A;Lycée Professionnel Privé Saint Joseph-la Providence;2 Avenue Passaga;57600;Forbach;49.1853591;6.9006303 +0261138W;Lycée Professionnel Privé Saint Louis;Clos Soubeyran;26402;Crest;0.0;0.0 +0593060F;Lycée Professionnel Privé Saint Louis;145 Avenue Marc Sangnier;59427;Armentieres;50.7001104;2.8845967 +0371328T;Lycée Professionnel Privé Saint Martin;47 Rue Nericault Destouches;37000;Tours;47.3920294;0.6835155 +0541344T;Lycée Professionnel Privé Saint Michel;Chartreuse De Bosserville;54510;Art Sur Meurthe;48.664136;6.243642 +0754175F;Lycée Professionnel Privé Saint Paul;81 Ter Rue Jean Pierre Timbaud;75011;Paris 11;48.867957;2.3777608 +0421978X;Lycée Professionnel Privé Saint Paul Forez;13 Rue Du College;42603;Montbrison;45.6084481;4.0659129 +0801945E;Lycée Professionnel Privé Saint Pierre;24 Place Clemenceau;80103;Abbeville;50.1087199;1.8347016 +0701001H;Lycée Professionnel Privé Saint Pierre Fourier;10 Q Rue Des Casernes;70103;Gray;47.4448206;5.5926946 +0801949J;Lycée Professionnel Privé Saint Riquier;50 Chaussee Jules Ferry;80094;Amiens;49.8829134;2.3258793 +0030105Y;Lycée Professionnel Privé Saint Vincent;150 Boulevard De Courtais;03100;Montlucon;46.3419497;2.6072664 +0740144E;Lycée Professionnel Privé Saint Vincent;55 Route De Bossey;74160;Collonges Sous Saleve;46.1413463;6.1456029 +0060776L;Lycée Professionnel Privé Saint Vincent De Paul;17 Rue Fodere;06300;Nice;43.6991063;7.2864757 +0370757X;Lycée Professionnel Privé Saint Vincent De Paul;30 Rue Delperier;37058;Tours;47.3925342;0.6769264 +0570241C;Lycée Professionnel Privé Saint Vincent De Paul;4 Rue Marie Douchet;57440;Algrange;49.3580552;6.0494087 +0930946Y;Lycée Professionnel Privé Saint Vincent De Paul;25 Rue Albert Walter;93200;St Denis;48.9380004;2.3591377 +0810100U;Lycée Professionnel Privé Saint-dominique;17 Lices Georges Pompidou;81000;Albi;43.9298245;2.1477169 +0420980M;Lycée Professionnel Privé Sainte Anne;24 Rue Du Pilat;42400;St Chamond;45.4720855;4.514178 +0421979Y;Lycée Professionnel Privé Sainte Anne;4 Rue Saint Alban;42300;Roanne;46.0363817;4.0633804 +0550983V;Lycée Professionnel Privé Sainte Anne;14 Rue Mautrote;55104;Verdun;49.1604635;5.3812032 +0730774V;Lycée Professionnel Privé Sainte Anne;39 Montee Saint Jean;73292;La Motte Servolex;45.5973189;5.8780976 +0700076C;Lycée Professionnel Privé Sainte Anne Saint Joseph;1 Rue De La Tannerie;70204;Lure;47.6877792;6.4943028 +0593079B;Lycée Professionnel Privé Sainte Bernadette;225 Rue Des Anciens D'afn;59572;Jeumont;50.296322;4.100791 +0570268G;Lycée Professionnel Privé Sainte Chretienne;1 Passage Du Pensionnat;57506;St Avold;49.1049716;6.7083946 +0131675A;Lycée Professionnel Privé Sainte Elisabeth;Lot N?35;13240;Septemes Les Vallons;43.3973237;5.3694594 +0251035P;Lycée Professionnel Privé Sainte Famille;33 Rue Du General Brulard;25000;Besancon;47.2275972;6.0026427 +0741286W;Lycée Professionnel Privé Sainte Famille;Avenue Des Voirons;74805;La Roche Sur Foron;46.0773767;6.2971227 +0730776X;Lycée Professionnel Privé Sainte Genevieve;2 Boulevard Du Theatre;73000;Chambery;45.5659213;5.9237499 +0551032Y;Lycée Professionnel Privé Sainte Jeanne D'arc;23 Rue Poincare;55200;Commercy;48.7606796;5.5902284 +0570294K;Lycée Professionnel Privé Sainte Marie;21 Avenue Du General De Gaulle;57400;Sarrebourg;48.7310531;7.05137 +0593063J;Lycée Professionnel Privé Sainte Marie;2 Rue Emile Hie;59270;Bailleul;50.7375223;2.73195 +0622195Z;Lycée Professionnel Privé Sainte Marie;31 Rue Du Paradis;62310;Fruges;50.5156254;2.1365448 +0390094R;Lycée Professionnel Privé Sainte Marie - Fenelon;84 Rue Saint Desire;39000;Lons Le Saunier;46.6706079;5.5489595 +0420996E;Lycée Professionnel Privé Sainte Marie La Grand'grange;Rue Mondragon;42400;St Chamond;45.483205;4.5102909 +0830114Y;Lycée Professionnel Privé Sainte Marthe;8 Rue Des 4 Freres Bernard;83390;Cuers;43.237875;6.0721999 +0671679L;Lycée Professionnel Privé Sainte Therese;4 Rue Des Allies;67970;Oermingen;49.0008662;7.128229 +0740151M;Lycée Professionnel Privé Sainte Therese;8 Avenue De L'aumone;74150;Rumilly;45.8644371;5.950467 +0141867D;Lycée Professionnel Privé Sainte Ursule;30 R De La Misericorde;14019;Caen;49.1819847;-0.3555065 +0271073V;Lycée Professionnel Privé Sainte-agnes;126 Rue D'albufera;27200;Vernon;49.090865;1.4801589 +0811070Y;Lycée Professionnel Privé Sainte-cecile;1 Avenue Du Breuil;81000;Albi;43.9432297;2.1483149 +0300106C;Lycée Professionnel Privé Sainte-marie;Impasse Des Recollets;30200;Bagnols Sur Ceze;44.1612433;4.6238415 +0311970Y;Lycée Professionnel Privé Sainte-marie De Nevers;10 Rue Du Perigord;31070;Toulouse;43.6072684;1.4432532 +0311219G;Lycée Professionnel Privé Sainte-marie Saint-sernin;19 Boulevard Armand Duportal;31000;Toulouse;43.6068673;1.4345863 +0761344Y;Lycée Professionnel Privé Sainte-therese;34 Rue Ferdinand Cartier;76150;Maromme;49.4855079;1.0441959 +0460050E;Lycée Professionnel Privé Saint-etienne;49 Rue Des Soubirous;46000;Cahors;44.4504769;1.4412933 +0110053Y;Lycée Professionnel Privé Saint-francois;2 Rue Des Amidonniers;11000;Carcassonne;43.2096831;2.3495761 +0761348C;Lycée Professionnel Privé Saint-hildevert;43 Bis Rue De Ferrieres;76220;Gournay En Bray;49.4821029;1.7281726 +0754239A;Lycée Professionnel Privé Saint-jean De Montmartre;31 Rue Caulaincourt;75018;Paris 18;48.8881336;2.3336903 +0121260E;Lycée Professionnel Privé Saint-joseph;Avenue Etienne Soulie;12200;Villefranche De Rouergue;44.357108;2.0390698 +0121309H;Lycée Professionnel Privé Saint-joseph;1 Rue Sarrus;12000;Rodez;44.3451427;2.5715885 +0141192V;Lycée Professionnel Privé Saint-joseph;145 Avenue De La Republique;14800;Deauville;49.3547504;0.0669966 +0312065B;Lycée Professionnel Privé Saint-joseph;85 Rue De Limayrac;31079;Toulouse;43.593452;1.4734636 +0810105Z;Lycée Professionnel Privé Saint-joseph;38 Avenue De Lavaur;81100;Castres;43.6075533;2.2350212 +0271081D;Lycée Professionnel Privé Saint-ouen;30 Rue Sadi Carnot;27500;Pont Audemer;49.3571648;0.5145026 +0141184L;Lycée Professionnel Privé Saint-pierre;50 Rue Canchy;14000;Caen;49.1741178;-0.3513773 +0820551D;Lycée Professionnel Privé Saint-roch;Malepeyre;82390;Durfort Lacapelette;44.19504;1.1837629 +0761349D;Lycée Professionnel Privé Saint-vincent De Paul;26 Boulevard Amiral Mouchez;76600;Le Havre;49.4849886;0.1351204 +0730779A;Lycée Professionnel Privé Savoisienne;85 Chemin Des Nuettes;73420;Drumettaz Clarafond;45.6649369;5.911833 +0601947R;Lycée Professionnel Privé Sevigne;20 Rue De La Sous Prefecture;60200;Compiegne;49.415931;2.8299869 +0771211Y;Lycée Professionnel Privé Sncf;35 Rue Du Chateau D'arcy;77390;Chaumes En Brie;48.666504;2.880979 +0690698J;Lycée Professionnel Privé Societe Enseig. Prof. Du Rhone;46 Rue Professeur Rochaix;69003;Lyon 03;45.7487694;4.8778351 +0131432L;Lycée Professionnel Privé St Andre (les Routiers);368 Boulevard Barnier;13016;Marseille 16;43.3606468;5.3425878 +0331585U;Lycée Professionnel Privé St Augustin;19 Rue Paul Courteault;33000;Bordeaux;44.8346037;-0.607579 +0440302P;Lycée Professionnel Privé St Donatien-immaculee;6 Rue Guillet De La Brosse;44013;Nantes;47.2296841;-1.5440911 +0133280V;Lycée Professionnel Privé St Eloi;9 Avenue Jules Isaac;13626;Aix En Provence;43.5348838;5.4513323 +0781856U;Lycée Professionnel Privé St Erembert;5 Rue Salomon Reinach;78100;St Germain En Laye;48.8958802;2.0996683 +0292158N;Lycée Professionnel Privé St Esprit;3 Rue Emile Souvestre;29403;Landivisiau;48.5121112;-4.0797023 +0911964R;Lycée Professionnel Privé St Eugene;10 Rue De La Pie Voleuse;91120;Palaiseau;48.7131857;2.2405189 +0781855T;Lycée Professionnel Privé St Francois D Assise;45 Avenue Du Manet;78180;Montigny Le Bretonneux;48.7664455;2.0213888 +0920963X;Lycée Professionnel Privé St Francois D Assise;39 Rue Boris Vilde;92260;Fontenay Aux Roses;48.7884925;2.2801094 +0332550T;Lycée Professionnel Privé St Genes;160 Rue St Genes;33081;Bordeaux;44.8249282;-0.5813679 +0560199M;Lycée Professionnel Privé St Georges;16 Rue Marechal Foch;56000;Vannes;47.6524299;-2.7669182 +0131433M;Lycée Professionnel Privé St Henri;37 Chemin De Bernex;13016;Marseille 16;43.366048;5.3394241 +0870919B;Lycée Professionnel Privé St Jean;26 Rue Eugene Varlin;87016;Limoges;45.8263601;1.2749309 +0840941S;Lycée Professionnel Privé St Jean Baptiste De La Salle;9 R Notre Dame Sept Douleurs;84008;Avignon;43.9489266;4.815644 +0101015Z;Lycée Professionnel Privé St Joseph;21 Rue Du Cloitre St Etienne;10042;Troyes;48.2970565;4.0816607 +0221865H;Lycée Professionnel Privé St Joseph;Rue Mouexigne;22404;Lamballe;48.4631626;-2.5132448 +0332535B;Lycée Professionnel Privé St Joseph;12 Avenue Du 8 Mai;33291;Blanquefort;44.9087878;-0.6456849 +0350788S;Lycée Professionnel Privé St Joseph;42 Route De St James;35300;Fougeres;48.3659336;-1.2085963 +0400883T;Lycée Professionnel Privé St Joseph;Le Bourg;40310;Gabarret;43.984986;0.011111 +0400916D;Lycée Professionnel Privé St Joseph;Le Berceau;40990;St Vincent De Paul;43.7469013;-1.0103303 +0511151Z;Lycée Professionnel Privé St Joseph;41 Rue Du General Fery;51037;Chalons En Champagne;48.9614846;4.3716507 +0541988T;Lycée Professionnel Privé St Joseph;413 Avenue De Boufflers;54524;Laxou;48.6934935;6.1381862 +0640181Z;Lycée Professionnel Privé St Joseph;1 Rte Des Missionnaires;64240;Hasparren;43.3811075;-1.3049408 +9710593T;Lycée Professionnel Privé St Joseph De Cluny;La Jaille;97122;Baie Mahault;16.2547641;-61.56198 +9720074Y;Lycée Professionnel Privé St Joseph De Cluny;22 Route De Cluny;97200;Fort De France;14.627187;-61.0738508 +9830269A;Lycée Professionnel Privé St Joseph De Cluny (ddec);45 Rue Alma Ctre Ville;98845;Noumea;-22.2697334;166.4407828 +0601896K;Lycée Professionnel Privé St Joseph Du Moncel;8 Place De L Eglise;60722;Pont Ste Maxence;49.3158812;2.6215379 +0131468A;Lycée Professionnel Privé St Louis;538 Chemin De La Madrague-ville;13015;Marseille 15;43.347103;5.3554272 +0291808H;Lycée Professionnel Privé St Marc;12 Rue De Kerfeunteun;29910;Tregunc;47.8537038;-3.8548436 +0721298V;Lycée Professionnel Privé St Martin;Chateau De Maison Rouge;72500;La Bruere Sur Loir;48.537019;-0.503046 +0331548D;Lycée Professionnel Privé St Michel;20 Av Gl De Gaulle;33290;Blanquefort;44.910084;-0.635244 +0560197K;Lycée Professionnel Privé St Michel;Fondation D'auteuil;56320;Priziac;48.1024272;-3.4316011 +0851516L;Lycée Professionnel Privé St Michel;32 Rue Du Calvaire;85290;St Laurent Sur Sevre;46.9593823;-0.8910565 +0131441W;Lycée Professionnel Privé St Michel (ann. St Ch. Camas);75 Rue Saint Savournin;13005;Marseille 05;43.2967646;5.3871316 +0920981S;Lycée Professionnel Privé St Philippe;1 Rue Du Pere Brottier;92190;Meudon;48.8066002;2.2467973 +0490897J;Lycée Professionnel Privé St Serge;13 Rue Des Fours A Chaux;49103;Angers;47.4840708;-0.5409198 +0440261V;Lycée Professionnel Privé St Thomas D'aquin;83 Rue Clemenceau;44153;Ancenis;47.3668587;-1.1786221 +0781581V;Lycée Professionnel Privé St Thomas De Villeneuve;15 Rue Des Louviers;78100;St Germain En Laye;48.8983954;2.0921371 +0290192B;Lycée Professionnel Privé St Tremeur;1 Rue Cazuguel;29833;Carhaix Plouguer;48.275584;-3.574188 +0132835L;Lycée Professionnel Privé St Vincent De Paul;30 Rue Stanislas Torrents;13006;Marseille 06;43.287572;5.3782713 +0240089S;Lycée Professionnel Privé St Vincent De Paul;23 Av G Pompidou;24000;Perigueux;45.1889317;0.7230677 +0332497K;Lycée Professionnel Privé St Vincent De Paul;47 A 51 Rue Des Sablieres;33074;Bordeaux;44.825364;-0.5753274 +0601162M;Lycée Professionnel Privé St Vincent De Paul;8 Bd Du General De Gaulle;60000;Beauvais;49.4291558;2.0897125 +0640189H;Lycée Professionnel Privé St Vincent De Paul;25 Rue Rene Cassin;64000;Pau;43.301636;-0.3672163 +0783325R;Lycée Professionnel Privé St Vincent De Paul;44 Avenue De Saint Cloud;78000;Versailles;48.8049728;2.1359398 +0641663K;Lycée Professionnel Privé Ste Anne;Notre Dame Du Refuge;64604;Anglet;43.496758;-1.52561 +0641408H;Lycée Professionnel Privé Ste Bernadette;1 Avenue De Gassion;64190;Audaux;43.3580963;-0.7937267 +0371326R;Lycée Professionnel Privé Ste Clotilde St Joseph;9 Rue Henri Dunan;37400;Amboise;47.4085229;0.9738971 +0292157M;Lycée Professionnel Privé Ste Elisabeth;9 Rue Lamennais;29171;Douarnenez;48.0905065;-4.3275018 +0641665M;Lycée Professionnel Privé Ste Elisabeth;45 Avenue Du Pic Du Midi;64800;Igon;43.1518007;-0.2248725 +0221866J;Lycée Professionnel Privé Ste Elisabeth-kersa;Chateau De Kersa;22620;Ploubazlanec;48.794307;-3.046996 +0332496J;Lycée Professionnel Privé Ste Famille -saintonge;12 Rue De Saintonge;33023;Bordeaux;44.8321381;-0.5798944 +0131459R;Lycée Professionnel Privé Ste Marie;2 Rue De Jouques;13100;Aix En Provence;43.5318267;5.4462333 +0511149X;Lycée Professionnel Privé Ste Marie;26 Rue Du Docteur Verron;51200;Epernay;49.0449891;3.9564484 +0492003L;Lycée Professionnel Privé Ste Marie-groupe Maine;3 Rue Du Margat;49105;Angers;47.4855856;-0.5607137 +0240094X;Lycée Professionnel Privé Ste Marthe - St Front;74 Avenue Pasteur;24100;Bergerac;44.8601889;0.4951121 +0440307V;Lycée Professionnel Privé Ste Therese;80 Rue D Anjou;44600;St Nazaire;47.2796821;-2.2092813 +0641705F;Lycée Professionnel Privé St-joseph;650 Rue Hiribehere;64480;Ustaritz;43.4045104;-1.462666 +0580075S;Lycée Professionnel Privé Therese Desreumaux;4 Rte De La Machine;58300;St Leger Des Vignes;46.838903;3.456643 +0501788X;Lycée Professionnel Privé Thomas Helye;37 Rue Emile Zola;50100;Cherbourg Octeville;49.6370758;-1.6291084 +0340920R;Lycée Professionnel Privé Turgot;94 Rue Pierre Flourens;34098;Montpellier;43.6377935;3.8324336 +0141197A;Lycée Professionnel Privé Victorine Magne;39 Avenue Du 6 Juin;14103;Lisieux;49.1409396;0.2117813 +0131677C;Lycée Professionnel Privé Vigie (la);Pl La Bastide La Maurelette;13015;Marseille 15;0.0;0.0 +0650067V;Lycée Professionnel Privé Vincent De Paul;16 Rue St Vincent De Paul;65000;Tarbes;43.2303093;0.079062 +0840082H;Lycée Professionnel Privé Vincent De Paul;1 Rue Chiron;84000;Avignon;43.9511699;4.8046831 +0620221D;Lycée Professionnel Professeur Clerc;34 Rue Andre Pantigny;62230;Outreau;50.703243;1.5894109 +0220186H;Lycée Professionnel Public;47 Rue Rene Le Magorec;22110;Rostrenen;48.2332958;-3.3089866 +0640066Z;Lycée Professionnel Ramiro Arrue;Quart Chantaco Rte D Ascain;64500;St Jean De Luz;43.3691803;-1.6437374 +0870119G;Lycée Professionnel Raoul Dautry;14 Rue Du Puy Imbert;87036;Limoges;45.8387379;1.2785581 +0900236T;Lycée Professionnel Raoul Follereau;3 Rue Louis Marchal;90016;Belfort;47.6383708;6.8462164 +0860029P;Lycée Professionnel Raoul Mortier;72 Avenue De L Europe;86501;Montmorillon;46.4257827;0.8591632 +0150008C;Lycée Professionnel Raymond Cortat;55 Avenue Jean Chanal;15005;Aurillac;44.947086;2.4625249 +9720468B;Lycée Professionnel Raymond Neris;Cite Scolaire Marin;97290;Le Marin;14.4904216;-60.9530711 +0121157T;Lycée Professionnel Raymond Savignac;Rue Agnes Savignac;12203;Villefranche De Rouergue;44.3563661;2.0381734 +0860823C;Lycée Professionnel Reaumur;4 Rue Salvador Allende;86036;Poitiers;46.5817037;0.370543 +0650029D;Lycée Professionnel Reffye;76 Avenue Joffre;65016;Tarbes;43.2394768;0.0631445 +0540067E;Lycée Professionnel Regional Du Toulois;440 Avenue Du General Bigeard;54201;Toul;48.6667052;5.8891311 +0280043V;Lycée Professionnel Remi Belleau;33 Rue Bretonnerie;28400;Nogent Le Rotrou;48.3189144;0.8109948 +0130057S;Lycée Professionnel Rene Caillie;173 Boulevard De St-loup;13011;Marseille 11;43.2817581;5.4283584 +0190034J;Lycée Professionnel Rene Cassin;Bd Du Marquisat;19012;Tulle;45.2632089;1.7723838 +0570124A;Lycée Professionnel Rene Cassin;2 Rue Rene Cassin;57052;Metz;49.1358349;6.1674007 +0594404S;Lycée Professionnel Rene Cassin;Rue Des Ecoles;59182;Montigny En Ostrevent;50.3695619;3.1875453 +0750588G;Lycée Professionnel Rene Cassin;185 Avenue De Versailles;75016;Paris 16;48.840068;2.2637559 +0900294F;Lycée Professionnel Rene Cassin;3 Rue Louis Marchal;90016;Belfort;47.6383708;6.8462164 +0840939P;Lycée Professionnel Rene Char;2 Rue Pierre A Renoir;84033;Avignon;43.9460637;4.8531827 +0850146X;Lycée Professionnel Rene Couzinet;Boulevard Jean Yole;85302;Challans;46.840774;-1.87671 +0730048F;Lycée Professionnel Rene Perrin;41 Rue Rene Perrin;73400;Ugine;45.7514631;6.4161324 +0310054R;Lycée Professionnel Renee Bonnet;1 Allee Du Lieutenant Lafay;31432;Toulouse;43.5550127;1.4681713 +9710709U;Lycée Professionnel Richeval;Quartier Richeval;97111;Morne A L Eau;0.0;0.0 +0271097W;Lycée Professionnel Risle-seine;Rue Des Deportes;27500;Pont Audemer;49.353527;0.520603 +0530013B;Lycée Professionnel Robert Buron;68 Rue Bellessort;53013;Laval;48.0694098;-0.7598676 +0601363F;Lycée Professionnel Robert Desnos;2 Rue Jules Michelet;60803;Crepy En Valois;49.2284134;2.8941975 +0760144U;Lycée Professionnel Robert Schuman;51 Avenue Du 8 Mai;76610;Le Havre;49.5161566;0.1664377 +0840042P;Lycée Professionnel Robert Schuman;138 Av De Tarascon;84084;Avignon;43.9250989;4.8091279 +0601787S;Lycée Professionnel Roberval;10 Rue Des Grez;60600;Breuil Le Vert;49.3641021;2.4171306 +9740004L;Lycée Professionnel Roches Maigres;25 Rue Leconte De Lisle;97899;St Louis;0.0;0.0 +0570016H;Lycée Professionnel Romain Rolland;30 Rue Des Peupliers;57150;Creutzwald;49.1995571;6.6988264 +0590078P;Lycée Professionnel Romain Rolland;11 Rue Du Pont De Pierre;59750;Feignies;50.2915826;3.9206938 +0801628K;Lycée Professionnel Romain Rolland;Rue Romain Rolland;80080;Amiens;49.9159787;2.2980057 +0171238D;Lycée Professionnel Rompsay;Avenue De Perigny;17025;La Rochelle;46.15927;-1.132608 +0290078C;Lycée Professionnel Roz Glas;1 Place Jean Zay;29391;Quimperle;47.8846911;-3.5532464 +0400783J;Lycée Professionnel Rural Ste Elisabeth Et;137 Route De La Vieille Cote;40180;St Pandelon;43.6721063;-1.0435113 +0891159B;Lycée Professionnel Saint Germain;2 Place St Germain;89000;Auxerre;47.8002699;3.5720023 +9720516D;Lycée Professionnel Saint James;Route De Fonds St Denis;97250;St Pierre;14.7419691;-61.133656 +9750003E;Lycée Professionnel Saint Pierre;Rue Marcel Bonin;97500;St Pierre;0.0;0.0 +0400057V;Lycée Professionnel Saint-exupery;Avenue Du Lycée;40161;Parentis En Born;0.0;0.0 +0620043K;Lycée Professionnel Salvador Allende;Rue De L'universite;62408;Bethune;50.5213285;2.6532171 +0501677B;Lycée Professionnel Sauxmarais;444 Rue De La Chasse Aux Loups;50110;Tourlaville;49.6380184;-1.591671 +7200011Z;Lycée Professionnel Scamaroni;Rue Du 4eme D M N;20600;Bastia;42.697283;9.450881 +0692518M;Lycée Professionnel Sermenaz;Rue De Bonn;69140;Rillieux La Pape;45.819655;4.897126 +0050009H;Lycée Professionnel Sevigne;6 Rue Jean Mace;05003;Gap;44.5614745;6.0739966 +0590217R;Lycée Professionnel Sevigne;151 Rue De La Malcense;59208;Tourcoing;50.730799;3.1650448 +0570100Z;Lycée Professionnel Simon Lazard;25 Rue J J Kieffer;57215;Sarreguemines;49.1209348;7.074776 +0790702J;Lycée Professionnel Simone Signoret;36 Rue De Malabry;79301;Bressuire;46.8407623;-0.4847921 +0930135S;Lycée Professionnel Simone Weil;121 Avenue Jean Lolive;93500;Pantin;48.8927394;2.4107582 +0650028C;Lycée Professionnel Sixte Vignon;12 Rue Du Taillade;65801;Aureilhan;43.2462704;0.0848529 +0410832G;Lycée Professionnel Sonia Delaunay;21 Bis Rue D Auvergne;41016;Blois;47.5948627;1.3360367 +0590111A;Lycée Professionnel Sonia Delaunay;121 Rue De La Mitterie;59461;Lille;50.6491583;2.9967355 +0630070J;Lycée Professionnel Sonia Delaunay;Cite Pontel 7 Ter Av J.jaures;63304;Thiers;45.857907;3.545177 +0572028V;Lycée Professionnel Sophie Germain;9 Impasse Colbert;57129;Thionville;49.366287;6.1478214 +0841078R;Lycée Professionnel Sorgues (de);Chemin De La Lucette;84700;Sorgues;43.9976096;4.8794552 +0520923W;Lycée Professionnel St Exupery;82 Rue Anatole France;52105;St Dizier;48.6430006;4.9626225 +0330082K;Lycée Professionnel Sud Gironde;1 Av Des Resistants;33212;Langon;44.54777;-0.252276 +0752109K;Lycée Professionnel Suzanne Valadon;7 Rue Ferdinand Flocon;75018;Paris 18;48.8916988;2.345863 +0711384U;Lycée Professionnel Theodore Monod;Rue De La Loge;71450;Blanzy;46.680842;4.3819194 +0921676X;Lycée Professionnel Theodore Monod;26 Avenue Leon Jouhaux;92160;Antony;48.735355;2.3064839 +0930133P;Lycée Professionnel Theodore Monod;187 Rue De Brement;93130;Noisy Le Sec;48.8872329;2.4666533 +0752845K;Lycée Professionnel Theophile Gautier;49 Rue De Charenton;75012;Paris 12;48.8511953;2.3727407 +0710014E;Lycée Professionnel Thomas Dumorey Metiers Batimen;3 Rue Jean Rostand;71321;Chalon Sur Saone;46.7776154;4.8396918 +0382031B;Lycée Professionnel Thomas Edison;Rue Normandie Niemen;38130;Echirolles;45.1467389;5.719765 +0790964U;Lycée Professionnel Thomas Jean Main;39 R Coteau St Hubert;79011;Niort;46.3419255;-0.4775602 +0330031E;Lycée Professionnel Toulouse Lautrec;115 Rue Joseph Abria;33000;Bordeaux;44.8339958;-0.5933109 +0810003N;Lycée Professionnel Toulouse-lautrec;15 Rue Charles Portal;81027;Albi;43.925348;2.1395873 +0251349F;Lycée Professionnel Toussaint Louverture;81 Route De Besancon;25304;Pontarlier;46.9141729;6.3458015 +0330142A;Lycée Professionnel Tregey Rive De Garonne;24 Rue De Tregey;33015;Bordeaux;44.8391974;-0.5478589 +0251079M;Lycée Professionnel Tristan Bernard;13 Rue Goya;25000;Besancon;47.2176939;5.9813344 +0290105G;Lycée Professionnel Tristan Corbiere;16 Rue De Kerveguen;29671;Morlaix;48.5705251;-3.8221176 +0590186G;Lycée Professionnel Turgot;2 Rue De La Paix;59053;Roubaix;50.6962493;3.1783973 +0951281B;Lycée Professionnel Turgot;3 Place Au Pain;95162;Montmorency;48.9853003;2.3212499 +0750778N;Lycée Professionnel Turquetil;18 Passage Turquetil;75011;Paris 11;48.8521798;2.3928741 +0030924N;Lycée Professionnel Val D'allier;15 Rue Du Beaupuy;03150;Varennes Sur Allier;46.3144612;3.4066414 +0940134K;Lycée Professionnel Val De Bievre;15 17 Rue D'arcueil;94257;Gentilly;48.8129955;2.3447508 +0630078T;Lycée Professionnel Val De Dore;68 Avenue Leo Lagrange;63300;Thiers;45.8468155;3.5278217 +0570234V;Lycée Professionnel Val De Fensch;204 Rue Victor Rimmel;57240;Knutange;49.3429114;6.0222329 +0590152V;Lycée Professionnel Val De Lys;Rue Jacqueminemars;59940;Estaires;50.6466217;2.7048506 +0760145V;Lycée Professionnel Val De Seine;Avenue Georges Braque;76124;Le Grand Quevilly;49.402714;1.04095 +0570324T;Lycée Professionnel Valentin Metzinger;Rue De Montreal;57500;St Avold;49.1002999;6.7197012 +0590126S;Lycée Professionnel Valentine Labbe;41 Rue Paul Doumer;59110;La Madeleine;50.6473736;3.0721697 +0921229L;Lycée Professionnel Valmy;130 Boulevard De Valmy;92700;Colombes;48.9306689;2.2551693 +0920166F;Lycée Professionnel Vassily Kandinsky;96 Boulevard Bineau;92200;Neuilly Sur Seine;48.888011;2.2757687 +0890819G;Lycée Professionnel Vauban;22 Rue Faidherbe;89010;Auxerre;47.8000287;3.565054 +0380098A;Lycée Professionnel Vaucanson;27 Rue Anatole France;38030;Grenoble;45.1722442;5.7083809 +0130170P;Lycée Professionnel Vauvenargues;60 Boulevard Carnot;13100;Aix En Provence;43.5284055;5.4549853 +0180823X;Lycée Professionnel Vauvert;Rue Marguerite Audoux;18000;Bourges;47.0776989;2.3787816 +0592832H;Lycée Professionnel Vertes Feuilles;95 Rue Georges Maertens;59871;St Andre Lez Lille;50.6327986;3.0613862 +0260037Z;Lycée Professionnel Victor Hugo;442 Avenue Victor Hugo;26021;Valence;44.9076529;4.8831446 +0840044S;Lycée Professionnel Victor Hugo;139 Av Victor Hugo;84208;Carpentras;44.0515235;5.0474084 +0370771M;Lycée Professionnel Victor Laloux;2 Rue Marcel Proust;37200;Tours;47.358625;0.7029341 +0140018U;Lycée Professionnel Victor Lepine;40 Rue Victor Lepine;14000;Caen;49.1711498;-0.3420176 +9740020D;Lycée Professionnel Victor Schoelcher;14 Rue Saint Philippe;97899;St Louis;45.7504748;4.8660377 +0250066L;Lycée Professionnel Viette;1 B Rue P Donzelot;25206;Montbeliard;47.4951765;6.8092234 +0130065A;Lycée Professionnel Viste (la);Traverse Bonnet;13015;Marseille 15;43.35932;5.3571954 +0921500F;Lycée Professionnel Voilin;26 Rue Lucien Voilin;92800;Puteaux;48.8828072;2.2370604 +0930846P;Lycée Professionnel Voillaume;136 Rue De Mitry;93604;Aulnay Sous Bois;48.9457777;2.5099926 +0301210C;Lycée Professionnel Voltaire;399 Rue Bellini;30903;Nimes;43.8266006;4.3381288 +0620258U;Lycée Professionnel Voltaire;Place Bertin Ledoux;62410;Wingles;50.494578;2.852404 +9740015Y;Lycée Professionnel Vue Belle;;97422;St Paul;49.164628;5.38442 +0670023L;Lycée Professionnel Xavier Nessel;123 Route De Strasbourg;67504;Haguenau;48.8001884;7.7656679 +0510037N;Lycée Professionnel Yser;Avenue De L'yser;51053;Reims;49.2484301;4.0585216 +0692682R;Lycée Rural Privé Jean Monnet;304 Boulevard De La Bardiere;69590;St Symphorien Sur Coise;45.6295165;4.4610934 +0441788E;Lycée Techn Et Professionnel Le Bois Tillac;Allee Du Bois Tillac;44640;Le Pellerin;47.2018844;-1.7712182 +0390970T;Lycée Technique Rural Privé;1 Allee De La Savine;39400;Morbier;46.5571966;6.0150673 +0291208F;Lycée Technol Agricole Privé;Le Nivot;29590;Loperec;48.312256;-4.011024 +0351954J;Lycée Technol Agricole Privé;L'abbaye;35160;Montfort Sur Meu;48.130223;-1.943634 +0560228U;Lycée Technol Agricole Privé Anne De Bretagne;5 Place Anne De Bretagne;56500;Locmine;47.8833082;-2.8324355 +0221593M;Lycée Technol Agricole Privé Centre De Formation D'armor;Le Chef Du Bois;22450;Pommerit Jaudy;48.742229;-3.244975 +0561408B;Lycée Technol Agricole Privé Des Pays De Vilaine;Route De Peillac;56220;St Jacut Les Pins;47.7154571;-2.1948712 +0221033D;Lycée Technol Agricole Privé Espa;La Ville Davy;22120;Quessoy;48.425678;-2.667812 +0311270M;Lycée Technol Agricole Privé La Cadene;Rue Buissonniere;31670;Labege;43.535875;1.521728 +0350918H;Lycée Technol Agricole Privé Les Vergers;4 Rue Des Murets;35120;Dol De Bretagne;48.5521611;-1.7541892 +0292000S;Lycée Technol Agricole Privé St Antoine;11 Rue St J-b De La Salle;29870;Lannilis;48.5722565;-4.5194361 +0221673Z;Lycée Technol Agricole Privé St Ilan;52 Rue De St Ilan;22360;Langueux;48.5048429;-2.7062202 +0291751W;Lycée Technol Agricole Privé St Joseph;12 Allee De Kerustum;29101;Quimper;47.9804929;-4.0927405 +0351953H;Lycée Technol Agricole Privé St Nicolas-la Providence;28 Rue De Rennes;35360;Montauban De Bretagne;48.1977763;-2.0426486 +0211503V;Lycée Technol.agricole Privé Anne-marie Javouhey;Rue Anne-marie Javouhey;21250;Chamblanc;47.0220768;5.1517811 +0601194X;Lycée Technol.agricole Privé Charles Quentin;1 Rue Sabatier;60350;Pierrefonds;49.3433941;2.9760698 +0530816Z;Lycée Technol.agricole Privé D Orion;2 Rue De La Liberation;53600;Evron;48.1565742;-0.4088119 +0441032H;Lycée Technol.agricole Privé Ecole D'agriculture;29 Rue De Rennes;44590;Derval;47.6713835;-1.6741115 +0491804V;Lycée Technol.agricole Privé Edmond Michelet;Le Pre Neuf;49330;Etriche;47.6505269;-0.444111 +0440981C;Lycée Technol.agricole Privé Gabriel Deshayes;4, Route De Redon;44530;St Gildas Des Bois;47.5189743;-2.0396361 +0320633P;Lycée Technol.agricole Privé Institut Saint-christophe;Aux Stournes;32140;Masseube;43.429726;0.579788 +0441795M;Lycée Technol.agricole Privé La Motte;17 Rue Abbe Perrin;44320;St Pere En Retz;47.2054129;-2.0427713 +0491809A;Lycée Technol.agricole Privé Les 3 Provinces;11 Boulevard Jeanne D Arc;49304;Cholet;47.0640598;-0.8673109 +0840797K;Lycée Technol.agricole Privé Les Chenes;524 Av Du Pont Des Fontaines;84200;Carpentras;44.0610607;5.0551521 +0850609A;Lycée Technol.agricole Privé Les Etablieres;Rte De Nantes;85015;La Roche Sur Yon;46.7055761;-1.4296139 +0261067U;Lycée Technol.agricole Privé Les Mandailles;;26330;Chateauneuf De Galaure;45.234516;4.960742 +0491027A;Lycée Technol.agricole Privé Pouille;;49136;Les Ponts De Ce;47.4268572;-0.5516965 +0491802T;Lycée Technol.agricole Privé Robert D Arbrissel;Place Urbain Ii;49120;Chemille;47.210608;-0.7294054 +0530818B;Lycée Technol.agricole Privé Rochefeuille;Route De Caen;53100;Mayenne;48.543782;-0.207489 +0801613U;Lycée Technol.agricole Privé Sainte Colette;Rue De L Enclos;80800;Corbie;49.9096772;2.510355 +0711440E;Lycée Technol.agricole Privé Ste Marguerite Marie;Jannots;71220;Verosvres;46.401991;4.453972 +0810833R;Lycée Technol.agricole Privé Touscayrats;Touscayrats;81110;Verdalle;43.5060659;2.180924 +0421674S;Lycée Technol.prive Agricole Le Puits De L'aune;Rue Louis Blanc;42110;Feurs;45.7383928;4.2364878 +0130161E;Lycée Technologique Adam De Craponne;218 Rue Chateau Redon;13651;Salon De Provence;43.6355195;5.0986535 +0131656E;Lycée Technologique Agricole Aix-valabre;Chemin Moulin Fort;13548;Gardanne;43.464305;5.453012 +0040512K;Lycée Technologique Agricole Digne-carmejane;Route D Espinouse;04510;Le Chaffaut St Jurson;44.047146;6.158502 +0840110N;Lycée Technologique Agricole Francois Petrarque;Site Agroparc;84911;Avignon;43.949317;4.805528 +0251481Z;Lycée Technologique Agricole Jeanne D'arc;22 Rue Jeanne D'arc;25300;Pontarlier;46.9039198;6.3575183 +0251479X;Lycée Technologique Agricole Les Fontenelles;15 Rue Du Couvent;25210;Les Fontenelles;47.1959275;6.7471417 +0840607D;Lycée Technologique Agricole Louis Giraud;Chemin De L Hermitage;84208;Carpentras;44.0836262;5.059517 +0750710P;Lycée Technologique Auguste Renoir;24 Rue Ganneron;75018;Paris 18;48.8870203;2.3283565 +0750697A;Lycée Technologique Batiment-saint-lambert;15 Rue Saint Lambert;75015;Paris 15;48.8377738;2.2915594 +0420937R;Lycée Technologique Benoit Fourneyron;24 Rue Virgile;42013;St Etienne;45.4176774;4.4197149 +0520028Y;Lycée Technologique Blaise Pascal;1 Avenue Marcel Paul;52100;St Dizier;48.6456201;4.9737363 +0500087Y;Lycée Technologique Claude Lehec;Rue Dauphine;50600;St Hilaire Du Harcouet;48.5715264;-1.0976365 +0190012K;Lycée Technologique Danton;Rue Danton;19311;Brive La Gaillarde;45.1590287;1.5216108 +9760220K;Lycée Technologique De Dzoumogne;;97650;Bandraboua;0.0;0.0 +0590223X;Lycée Technologique Du Hainaut;1 Avenue Villars;59322;Valenciennes;50.3582813;3.5107769 +0750672Y;Lycée Technologique Duperre Esaa;11 Rue Dupetit Thouars;75003;Paris 03;48.8652834;2.3620241 +0750685M;Lycée Technologique Ec Nat Chimie Physiq Biologie;11 Rue Pirandello;75013;Paris 13;48.8365925;2.3562968 +0750612H;Lycée Technologique Ec Nat Sup Des Arts Appliques;63 65 Rue Olivier De Serres;75015;Paris 15;48.833647;2.2948847 +0594391C;Lycée Technologique Ec Sup Arts Appliques Textile;539 Avenue Des Nations Unies;59056;Roubaix;50.6936638;3.1752265 +0750707L;Lycée Technologique École Nationale De Commerce;70 Boulevard Bessieres;75017;Paris 17;48.8971407;2.3208753 +0801327H;Lycée Technologique Edouard Branly;70 Boulevard De Saint Quentin;80098;Amiens;49.8791607;2.2965051 +0480009Z;Lycée Technologique Emile Peytavin;Avenue Du 11 Novembre;48001;Mende;44.5233613;3.4833187 +0750686N;Lycée Technologique Estienne Esaig;18 Boulevard Auguste Blanqui;75013;Paris 13;48.8308013;2.3524558 +0211033J;Lycée Technologique Gustave Eiffel;15 Avenue Champollion;21074;Dijon;47.3338611;5.0676398 +0590011S;Lycée Technologique Gustave Eiffel;96 Rue Jules Lebleu;59427;Armentieres;50.6880881;2.8733271 +0290070U;Lycée Technologique Jean Chaptal;35 Chemin Des Justices;29191;Quimper;47.9918404;-4.1193383 +0750674A;Lycée Technologique Jules Siegfried;12 Rue D'abbeville;75010;Paris 10;48.8788604;2.3501271 +0760056Y;Lycée Technologique Jules Siegfried;1 Rue Dume D'aplemont;76054;Le Havre;49.4974627;0.1241609 +0141687H;Lycée Technologique Jules Verne;12 Rue Lucien Bossoutrot;14126;Mondeville;49.176963;-0.319769 +0332836D;Lycée Technologique Latresne Constructions Aeronautiques;Chateau De Latresne;33360;Latresne;44.779974;-0.491589 +0380034F;Lycée Technologique Louise Michel;30 Rue Louise Michel;38037;Grenoble;45.1687882;5.7081825 +0171428K;Lycée Technologique Lycée De La Mer Et Du Littoral;Rue William Bertrand;17560;Bourcefranc Le Chapus;45.846735;-1.15707 +9840268U;Lycée Technologique Lycée Hotelier De Tahiti;Punaauia;98718;Punaauia;-17.5943083;-149.6116857 +0130051K;Lycée Technologique Marie Curie;16 Boulevard Jeanne D Arc;13392;Marseille 05;43.2937083;5.4004604 +0710012C;Lycée Technologique Nicephore Niepce;141 Avenue Boucicaut;71321;Chalon Sur Saone;46.7888739;4.8359262 +0330029C;Lycée Technologique Nicolas Bremontier;152 Cours De L Yser;33077;Bordeaux;44.823338;-0.5706489 +0130011S;Lycée Technologique Pasquet;54 Bd Marcellin Berthelot;13200;Arles;43.6724263;4.6297406 +7200583W;Lycée Technologique Paul Vincensini;Rue Du 4eme D M M;20600;Bastia;42.697283;9.450881 +0031041R;Lycée Technologique Privé Anna Rodier;39 Cours Jean Jaures;03008;Moulins;46.5669568;3.3349455 +0573683U;Lycée Technologique Privé Anne De Mejanes;3 Rue Goussaud;57000;Metz;49.1235079;6.173823 +0170124T;Lycée Technologique Privé Aris Formation Pigier;16 Rue Leonce Vieljeux;17000;La Rochelle;46.1578836;-1.1553283 +9710993C;Lycée Technologique Privé Bel Air;Moudong Nord;97122;Baie Mahault;0.0;0.0 +0331591A;Lycée Technologique Privé Bel Orme;67 Rue De Bel Orme;33000;Bordeaux;44.8497536;-0.5941378 +0133286B;Lycée Technologique Privé Belsunce;13 Rue Fauchier;13002;Marseille 02;43.3044451;5.3717817 +0690646C;Lycée Technologique Privé Carrel;7 Rue Pierre Robin;69362;Lyon 07;45.7493853;4.8581918 +0133424B;Lycée Technologique Privé Caucadis;Bd Alfred Casile;13127;Vitrolles;43.4478245;5.2426452 +0133425C;Lycée Technologique Privé Celony;4 Bis Av De Lattre De Tassigny;13090;Aix En Provence;43.5303056;5.4383758 +0131402D;Lycée Technologique Privé Charles Peguy;102 Rue Sylvabelle;13006;Marseille 06;43.2882408;5.3748305 +0131681G;Lycée Technologique Privé Chimie Et Biologie La Forbine;4 Boulevard De La Forbine;13011;Marseille 11;43.2868081;5.4665119 +0542448T;Lycée Technologique Privé Claude Daunot;10 Boulevard Clemenceau;54052;Nancy;48.6779501;6.1805671 +0592969G;Lycée Technologique Privé Dampierre;85 Avenue De Denain;59326;Valenciennes;50.3543272;3.4992095 +0521045D;Lycée Technologique Privé De L'assomption;50 Rue Du Mal De Lattre;52108;St Dizier;48.6381589;4.9511085 +0593071T;Lycée Technologique Privé Deforest De Lewarde;151 Rue Jean De Gouy;59504;Douai;50.3715086;3.0819657 +0133396W;Lycée Technologique Privé Don Bosco;78 Rue Stanislas Torrents;13006;Marseille 06;43.2855609;5.3795529 +0691659D;Lycée Technologique Privé Don Bosco;12 Et 24 Montee Saint Laurent;69322;Lyon 05;45.7510345;4.8167844 +0070098U;Lycée Technologique Privé Du Sacre Coeur;7 Avenue De La Gare;07301;Tournon Sur Rhone;45.0637796;4.834587 +0753537M;Lycée Technologique Privé Ec Sup Tech Biologie Appliquee;56 Rue Planchat;75020;Paris 20;48.8560952;2.3964535 +0750330B;Lycée Technologique Privé Ec Technique Sup Laboratoire;93 95 R Du Dessous Des Berges;75013;Paris 13;48.8281956;2.3731118 +0593078A;Lycée Technologique Privé Fondation Depoorter;7-9 Rue Depoorter;59524;Hazebrouck;50.7201268;2.5342537 +0132280H;Lycée Technologique Privé Henri Leroy;1 Rue Des Ecoles;13230;Port St Louis Du Rhone;43.3867633;4.8031175 +0690631L;Lycée Technologique Privé Icof;8 Avenue Debrousse;69005;Lyon 05;45.7505097;4.8141393 +0870097H;Lycée Technologique Privé Inst D Econ Sociale Familiale;5 Rue De La Cite;87000;Limoges;45.8290178;1.2642372 +0141175B;Lycée Technologique Privé Institut Lemonnier;60 Rue D Herouville;14013;Caen;49.1935816;-0.3487181 +0751883P;Lycée Technologique Privé Institut Privé;119 Boulevard Diderot;75012;Paris 12;48.8479674;2.3912553 +0681885E;Lycée Technologique Privé Institut Sonnenberg;1 Rue Du Moulin;68130;Carspach;47.6651522;7.2235055 +0380018N;Lycée Technologique Privé Iser - Bordier;26 Rue Prosper Merimee;38100;Grenoble;45.1737355;5.7260681 +0381900J;Lycée Technologique Privé Itec Boisfleury Europe;76 Grande Rue;38701;La Tronche;45.2047507;5.7377606 +0741307U;Lycée Technologique Privé Jeanne D Arc;18 Bis Avenue Jules Ferry;74200;Thonon Les Bains;46.3724399;6.4832781 +0592966D;Lycée Technologique Privé Jehanne D'arc;106 Rue De Lille;59202;Tourcoing;50.7228019;3.1512774 +0690634P;Lycée Technologique Privé La Mache;75 Boulevard Jean Xxiii;69373;Lyon 08;45.7364603;4.8695511 +0754037F;Lycée Technologique Privé La Plaine Monceau;9 Boulevard De Courcelles;75008;Paris 08;48.881052;2.3158423 +0061852F;Lycée Technologique Privé La Providence;236 Route De Grenoble;06200;Nice;43.6819305;7.1990341 +0592967E;Lycée Technologique Privé La Sagesse;40 Rue De Mons;59306;Valenciennes;50.3598706;3.5288319 +0593061G;Lycée Technologique Privé La Sagesse;7 Rue Du Temple;59405;Cambrai;50.1755939;3.2277333 +0500127S;Lycée Technologique Privé Le Bon Sauveur;Rue E De Surville;50001;St Lo;49.112089;-1.094787 +0133554T;Lycée Technologique Privé Le Rocher;42 Boulevard Ledru Rollin;13300;Salon De Provence;43.6441863;5.0948018 +0741101V;Lycée Technologique Privé Les Bressis;85 Route Des Creuses;74600;Seynod;45.8932394;6.1001202 +0060735S;Lycée Technologique Privé Les Fauvettes;44 Avenue Jean De Noailles;06400;Cannes;43.5541346;6.9996242 +0382943T;Lycée Technologique Privé Les Portes De Chartreuse;387 Avenue De Stalingrad;38343;Voreppe;45.2947821;5.6336357 +0350787R;Lycée Technologique Privé Les Rimains;Esplanade De La Gare;35409;St Malo;48.5706771;-1.9687989 +0141418R;Lycée Technologique Privé Les Rosiers;17 Chemin De Rocques;14100;Lisieux;49.154762;0.2306014 +0754016H;Lycée Technologique Privé L'initiative;24 Rue Bouret;75019;Paris 19;48.8814352;2.3745457 +0121428M;Lycée Technologique Privé Louis Querbes;29 Rue Maurice Bompard;12000;Rodez;44.3485775;2.5792732 +0641534V;Lycée Technologique Privé Ltp Nay Baudreix;Route De Lys;64800;Baudreix;43.20577;-0.2589659 +0071359P;Lycée Technologique Privé Marc Seguin-saint Charles;Route De Californie;07100;Annonay;45.2440877;4.6758762 +0220116G;Lycée Technologique Privé Marie Balavenne;47 Boulevard Laennec;22005;St Brieuc;48.5111408;-2.7744799 +0831207L;Lycée Technologique Privé Marie France;220 Avenue Marcel Castie;83000;Toulon;43.1234854;5.941595 +0133474F;Lycée Technologique Privé Marie Gasquet;38 R Electriciens St Barnabe;13012;Marseille 12;43.3041972;5.4172944 +0131398Z;Lycée Technologique Privé Maximilien De Sully;52 Rue Auguste Blanqui;13006;Marseille 06;43.2922507;5.3874655 +0131436R;Lycée Technologique Privé Modele Electronique;233 Bd St Marcel;13396;Marseille 11;43.2865274;5.4686969 +0754048T;Lycée Technologique Privé Morin;182 Rue De Vaugirard;75015;Paris 15;48.8421148;2.3096576 +0311201M;Lycée Technologique Privé Myriam;9 Et 20 Rue Mage;31000;Toulouse;43.5977887;1.4472602 +0440259T;Lycée Technologique Privé Nd D'esperance;15 Rue Du Bois Savary;44615;St Nazaire;47.2747567;-2.2080565 +0570210U;Lycée Technologique Privé Notre Dame;2 Rue De Metz;57245;Peltre;49.0684672;6.2497106 +0690553B;Lycée Technologique Privé Notre Dame;72 Rue Des Jardiniers;69657;Villefranche Sur Saone;45.9885367;4.7206723 +0142084P;Lycée Technologique Privé Notre Dame De Fidelite;8 Rue Du Petit Clos Saint Marc;14074;Caen;49.2111144;-0.3733099 +0142108R;Lycée Technologique Privé Notre Dame De Nazareth;Avenue De La Basilique;14440;Douvres La Delivrande;49.2970159;-0.3732011 +0593006X;Lycée Technologique Privé Notre-dame D'annay;15 Place Du Concert;59000;Lille;50.6422985;3.0607077 +0690642Y;Lycée Technologique Privé O R T;133 Rue Marius Berliet;69008;Lyon 08;45.7380862;4.8677943 +0931796X;Lycée Technologique Privé Ort;39-45 Rue Raspail;93107;Montreuil;48.8528323;2.424645 +0133334D;Lycée Technologique Privé Ort Leon Bramson;9 Rue Des Forges;13010;Marseille 10;43.2791281;5.4104058 +9711022J;Lycée Technologique Privé Pensionnat De Versailles;8 Rue Victor Hugues;97100;Basse Terre;16.1985639;-61.5901551 +0542161F;Lycée Technologique Privé Pierre De Coubertin;5 R Du President Robert Schuman;54000;Nancy;48.6825501;6.1799118 +0650065T;Lycée Technologique Privé Pradeau-la Sede;14 Rue Mesclin;65912;Tarbes;43.2366268;0.0682376 +0070099V;Lycée Technologique Privé Saint Andre;18 Rue Emile Combes;07400;Le Teil;44.5388909;4.6878848 +0573519R;Lycée Technologique Privé Saint Andre;1 Rue De L'eglise;57840;Ottange;49.4410443;6.0178869 +0622199D;Lycée Technologique Privé Saint Denis;8 Place Saint Jean;62504;St Omer;50.750051;2.2606387 +0593077Z;Lycée Technologique Privé Saint Joseph;10 Rue De La Paix;59529;Hazebrouck;50.718103;2.5331551 +0622198C;Lycée Technologique Privé Saint Joseph;26 Route De Calais;62280;St Martin Boulogne;50.7367968;1.6223612 +0741414K;Lycée Technologique Privé Saint Joseph;St Martin Sur Arve;74703;Sallanches;45.912929;6.689997 +0573073F;Lycée Technologique Privé Saint Joseph-la Providence;10 Rue De L'abbe Heydel;57801;Freyming Merlebach;49.146198;6.811347 +0260072M;Lycée Technologique Privé Saint Louis;Clos Soubeyran;26402;Crest;0.0;0.0 +0592963A;Lycée Technologique Privé Saint Martin;54 Rue De Lille;59062;Roubaix;50.6867645;3.1712288 +0542295B;Lycée Technologique Privé Saint Michel;Charteuse De Bosserville;54510;Art Sur Meurthe;48.664136;6.243642 +0593104D;Lycée Technologique Privé Saint Pierre;10 Rue Du General Goutierre;59612;Fourmies;50.0092398;4.0503645 +0573366Z;Lycée Technologique Privé Saint Vincent De Paul;4 Rue Marie Douchet;57440;Algrange;49.3580552;6.0494087 +0573172N;Lycée Technologique Privé Sainte Chrétienne;1 Passage Du Pensionnat;57506;St Avold;49.1049716;6.7083946 +0141178E;Lycée Technologique Privé Sainte Ursule;1 Place Wurzburg;14067;Caen;49.2015078;-0.3799957 +0810095N;Lycée Technologique Privé Sainte-Cécile;1 Avenue Du Breuil;81000;Albi;43.9432297;2.1483149 +0811175M;Lycée Technologique Privé Saint-Joseph;38 Avenue De Lavaur;81100;Castres;43.6075533;2.2350212 +0650921Y;Lycée Technologique Privé Saint-Pierre;24 Avenue D'azereix;65000;Tarbes;43.2289169;0.0578127 +0755121J;Lycée Technologique Privé Sinai;9 Rue Jacques Ibert;75017;Paris 17;48.8890075;2.2909761 +0371439N;Lycée Technologique Privé St Francois Inst N-d La Riche;30 Rue Delperier;37058;Tours;47.3925342;0.6769264 +0132922F;Lycée Technologique Privé St Jean;76 Av Georges Borel;13300;Salon De Provence;43.6406902;5.0821442 +0840940R;Lycée Technologique Privé St Jean Baptiste De La Salle;9 Rue Notre Dame Sept Douleurs;84008;Avignon;43.9489266;4.815644 +0593005W;Lycée Technologique Privé St Jean-baptiste De La Salle;2 Rue Jean Le Vasseur;59000;Lille;50.6138111;3.0423599 +0100059K;Lycée Technologique Privé St Joseph;21 Rue Cloitre St Etienne;10042;Troyes;48.2970565;4.0816607 +0783323N;Lycée Technologique Privé St Thomas De Villeneuve;15 Rue Des Louviers;78100;St Germain En Laye;48.8983954;2.0921371 +0601950U;Lycée Technologique Privé St Vincent De Paul;8 Bd Du General De Gaulle;60000;Beauvais;49.4291558;2.0897125 +0641441U;Lycée Technologique Privé Ste Anne;Notre Dame Du Refuge;64604;Anglet;43.496758;-1.52561 +0911981J;Lycée Technologique Privé Ste Jeanne D Arc;36 Av Du 8 Mai 1945;91120;Palaiseau;48.7096979;2.2428188 +0133395V;Lycée Technologique Privé Ste Marie;2 Rue De Jouques;13100;Aix En Provence;43.5318267;5.4462333 +0490887Y;Lycée Technologique Privé Ste Marie;43 Rue St Bonaventure;49307;Cholet;47.0594586;-0.8778263 +0593034C;Lycée Technologique Privé Theophile Legrand;16 Rue Bertrand;59720;Louvroil;50.2659008;3.9615428 +0492019D;Lycée Technologique Privé Urbain Mongazon;1 Rue Du Colombier;49036;Angers;47.4592971;-0.5294038 +0593058D;Lycée Technologique Privé Vauban;1 Place Vauban;59140;Dunkerque;51.0294231;2.3781519 +0130049H;Lycée Technologique Rempart (rue Du);1 Rue Du Rempart;13007;Marseille 07;43.2889391;5.3652071 +0670086E;Lycée Technologique Rene Cassin;4 Rue Schoch;67046;Strasbourg;48.5829505;7.7702809 +0332468D;Lycée Technologique St Louis;Rue Capitaine Kleber Dupuy;33028;Bordeaux;44.837789;-0.57918 +0511219Y;Lycée Viticole De La Champagne;Route D'oger;51190;Avize;48.9669337;4.0084888 +0241111C;Lycéee Prof Agricole;Domaine De La Brie;24240;Monbazillac;44.793529;0.492161 +0301433V;Maison Famil Rurale Horticole Le Grand Mas;Route De Nimes;30700;Uzes;43.9965584;4.4132526 +0520934H;Maison Familiale Rurale Des Recollets;Avenue Du 21eme R.i.;52202;Langres;47.849961;5.3290179 +0753494R;Museum D'histoire Naturelle;57 Rue Cuvier;75231;Paris 05;48.8441222;2.3556153 +0753496T;Observatoire De Paris;61 Avenue De L'observatoire;75014;Paris 14;48.8369989;2.3365396 +0190058K;Ogec Notre Dame Jeanne D'arc Lycée Bossuet;11 Rue Bossuet;19107;Brive La Gaillarde;45.1582613;1.52277 +0750030A;Palais De La Decouverte;Avenue Franklin D Roosevelt;75008;Paris 08;48.8705294;2.3101262 +0450762J;Sarl Inst. Formation Prof.;31 Av Louis Maurice Chautemps;45200;Montargis;48.0069208;2.7464731 +0382974B;Ste Grenobloise De Formation Sup2i;9 Rue General Ferrie;38100;Grenoble;45.1796694;5.723777 +9710585J;Université Antilles Guyane;Bd Legitimus Bp 771;97159;Pointe A Pitre;16.241111;-61.533056 +0131842G;Université Aix-marseille 1;3, Place Victor-hugo;13331;Marseille 03;43.3042684;5.3782868 +0131843H;Université Aix-marseille 2;58 Bd Charles Livon;13284;Marseille 07;43.2920635;5.3590116 +0132364Z;Université Aix-marseille 3;3, Avenue Robert Schuman;13628;Aix En Provence;43.5192855;5.4406011 +0331764N;Université Bordeaux 1;351, Cours De La Liberation;33405;Talence;44.808849;-0.5914683 +0331765P;Université Bordeaux 2;146, Rue Leo Saignat;33076;Bordeaux;44.8264547;-0.6017476 +0331766R;Université Bordeaux 3;Esplanade Montaigne Dom Univ;33405;Talence;44.802614;-0.588054 +0332929E;Universite Bordeaux 4;Avenue Leon Duguit;33608;Pessac;44.7988471;-0.6154882 +0631262E;Université Clermont-ferrand 1;49 Boulevard Gergovia Bp 32;63001;Clermont Ferrand;45.777222;3.087025 +0631525R;Université Clermont-ferrand 2;34 Avenue Carnot Bp 185;63006;Clermont Ferrand;45.777222;3.087025 +0801344B;Université D'amiens;Chemin Du Thil;80025;Amiens;49.8762421;2.2668361 +0490970N;Université D'angers;Rue Des Arenes Bp 3532;49035;Angers;47.478419;-0.563166 +0623957P;Université D'arras;9 Rue Du Temple Bp 665;62030;Arras;50.291002;2.777535 +0840685N;Université D'avignon;35 Rue Joseph Vernet Bp 218;84010;Avignon;43.949317;4.805528 +0251215K;Université De Besancon;3 Rue Goudimel;25030;Besancon;47.2402992;6.0225572 +0290346U;Université De Brest;Rue Des Archives Bp 137;29269;Brest;48.390394;-4.486076 +0561718N;Universite De Bretagne Sud;12 Avenue Saint Symphorien;56000;Vannes;47.6623582;-2.7552643 +0141408E;Université De Caen;Esplanade De La Paix;14032;Caen;49.1881893;-0.3638111 +0951793H;Université De Cergy-pontoise;33 Bd Du Port Bp 220;95011;Cergy;49.035617;2.060325 +0730858L;Université De Chambery;27 Rue Marcoz Bp 1104;73011;Chambery;45.564601;5.917781 +0601223D;Université De Compiègne;Rue Roger Couttolenc Bp 649;60206;Compiegne;49.417816;2.826145 +7200664J;Université De Corse;7 Av J Nicoli Bp 52;20250;Corte;42.309409;9.149022 +0211237F;Université De Dijon;Campus Univ Montmuzard Bp 138;21004;Dijon;47.322047;5.04148 +0595964M;Université De Dunkerque;9 Quai De La Citadelle Bp 1022;59375;Dunkerque;51.03456;2.3752019 +9830445S;Universite De La Nouvelle-caledonie;145 Av James Cook;98847;Noumea;-22.2686289;166.4242812 +9840349G;Universite De La Polynesie Francaise;Punaauia;98714;Papeete;-17.5753232;-149.6059799 +9740478B;Université De La Réunion;15 Avenue Rene Cassin;97489;St Denis;47.827397;1.9235839 +0171463Y;Université De La Rochelle;23 Avenue Albert Einstein;17071;La Rochelle;46.148256;-1.154995 +0870669E;Université De Limoges;Hotel Burgy 13 Rue De Geneve;87065;Limoges;45.8340567;1.2442706 +0772502B;Universite De Marne La Vallee;5 Bld Descartes A Champs/marne;77454;Champs Sur Marne;48.8400977;2.5862493 +0932056E;Université De Marne La Vallée;2 Rue De La Butte Verte;93160;Noisy Le Grand;48.8423725;2.5796219 +0572081C;Université De Metz;Ile Du Saulcy Bp 794;57012;Metz;49.119666;6.176905 +0681166Y;Université De Mulhouse;2, Rue Des Freres Lumiere;68093;Mulhouse;47.73208;7.315734 +0440984F;Université De Nantes;Quai Tourville Bp 1026;44035;Nantes;47.218371;-1.553621 +0060931E;Université De Nice;28, Avenue Valrose;06034;Nice;43.7158479;7.2657113 +0640251A;Université De Pau Et Pays De L'adour;Av De L Universite Bp 576;64012;Pau;43.2951;-0.370797 +0660437S;Université De Perpignan;52 Avenue De Villeneuve;66860;Perpignan;42.6809965;2.9025928 +0860856N;Université De Poitiers;15 Rue De L Hotel Dieu;86034;Poitiers;46.5857078;0.3458139 +0511296G;Université De Reims;Villa Douce 9 Bd De La Paix;51097;Reims;49.2543882;4.0406026 +0761904G;Université De Rouen;1, Rue Thomas Becket;76821;Mont St Aignan;49.4603349;1.0673087 +0421095M;Université De Saint-etienne;34 Rue Francis Baulier;42023;St Etienne;45.4294967;4.391713 +0830766G;Université De Toulon;Avenue De L Universite Bp 132;83957;La Garde;43.128777;6.017199 +0370800U;Université De Tours;3, Rue Des Tanneurs;37041;Tours;47.3960763;0.6815944 +0101060Y;Université De Troyes;13 Bd Henri Barbusse Bp 2060;10010;Troyes;48.2965919;4.074735 +0593279U;Université De Valenciennes;Le Mont Houy Bp 311;59304;Valenciennes;50.357113;3.518332 +0781944P;Université De Versailles;23, Rue Du Refuge;78035;Versailles;48.8040427;2.1440967 +0450855K;Université D'orléans;Chateau De La Source;45067;Orleans;47.8507118;1.9337794 +0762762P;Université Du Havre;25 Rue Philippe Lebon Bp 1123;76063;Le Havre;49.49437;0.107929 +0720916E;Université Du Mans;Av Messiaen Bp 535;72017;Le Mans;48.00611;0.199556 +0911975C;Université Evry;23 Boulevard Des Coquibus;91025;Evry;48.6226142;2.4426967 +9840303G;Université Francaise Du Pacifique;B P 4635 Papeete;98800;Noumea;-22.2758;166.458 +0381838S;Université Grenoble 1;621 Av Centrale Bp53x;38041;Grenoble;45.188529;5.724524 +0381839T;Université Grenoble 2;Domaine Universitaire Bp 47x;38402;St Martin D Heres;45.165133;5.767761 +0381840U;Université Grenoble 3;Dom Univ Ave Centrale Bp 25;38040;Grenoble;45.188529;5.724524 +0593559Y;Université Lille 1;Dom Universitaire Scientifique;59655;Villeneuve D Ascq;50.623334;3.145 +0593560Z;Université Lille 2;42, Rue Paul Duez;59800;Lille;50.6312819;3.0756487 +0593561A;Université Lille Iii;Dom Univ Lit Et Jur Bp 149;59653;Villeneuve D Ascq;50.623334;3.145 +0691774D;Université Lyon 1;43 Bd Du 11 Nov 1918 Bp 761;69622;Villeurbanne;45.771944;4.8901709 +0691775E;Université Lyon 2;86, Rue Pasteur;69365;Lyon 07;45.7495518;4.8374252 +0692437Z;Université Lyon 3;1 Rue Universite Bp 0638;69239;Lyon 02;0.0;0.0 +0341087X;Université Montpellier 1;5,bd Henri Iv;34006;Montpellier;43.6149374;3.8736413 +0341088Y;Université Montpellier 2;Place Eugene Bataillon;34095;Montpellier;43.631505;3.860602 +0341089Z;Université Montpellier 3;B P 5043;34032;Montpellier;43.6340946;3.8704922 +0541507V;Université Nancy 1;24 30 R Lionnois Bp 3069;54013;Nancy;48.692054;6.184417 +0541508W;Université Nancy 2;25, Rue Baron Louis Bp 454;54001;Nancy;48.692054;6.184417 +0751717J;Université Paris 1;12 Place Du Pantheon;75231;Paris 05;48.8468209;2.3448981 +0921204J;Université Paris 10;200 Avenue De La Republique;92001;Nanterre;48.9047283;2.2088302 +0911101C;Université Paris 11;15, Rue Georges Clemenceau;91405;Orsay;48.6981864;2.181768 +0941111X;Université Paris 12;61, Avenue Du General De Gaulle;94010;Creteil;48.788165;2.445979 +0931238R;Université Paris 13;Avenue Jean-baptiste Clement;93430;Villetaneuse;48.955902;2.3388238 +0751718K;Université Paris 2;12, Place Du Pantheon;75231;Paris 05;48.8468209;2.3448981 +0751719L;Université Paris 3;17, Place De La Sorbonne;75230;Paris 05;48.8485744;2.3421878 +0751720M;Université Paris 4;1, Rue Victor Cousin;75230;Paris 05;48.8478098;2.3424414 +0751721N;Université Paris 5;12 Rue De L Ecole De Medecine;75270;Paris 06;48.8512077;2.3407847 +0751722P;Université Paris 6;4, Place Jussieu;75252;Paris 05;48.8465589;2.3544484 +0751723R;Université Paris 7;2,place Jussieu;75251;Paris 05;48.8460464;2.355228 +0931827F;Université Paris 8;2 Rue De La Liberte;93526;St Denis;48.9452421;2.3639438 +0750736T;Université Paris 9;Place Du Marechal De Tassigny;75775;Paris 16;48.8712128;2.2744029 +0350936C;Université Rennes 1;2 Rue Du Thabor Bp 1134;35014;Rennes;48.113475;-1.675708 +0350937D;Université Rennes 2;6, Avenue Gaston Berger;35043;Rennes;48.1187652;-1.7048071 +0671712X;Université Strasbourg 1;4 Rue Blaise Pascal Bp1032 F;67070;Strasbourg;48.583148;7.747882 +0671713Y;Université Strasbourg 2;22, Rue Descartes;67084;Strasbourg;48.579007;7.7651262 +0671778U;Université Strasbourg 3;1 Place D Athenes Bp 66;67045;Strasbourg;48.583148;7.747882 +0470940S;Universite Thematique Agen;Agropole Route De Condom;47931;Agen;44.203142;0.616363 +0311382J;Université Toulouse 1;Place Anatole France;31042;Toulouse;43.6060249;1.4384679 +0311383K;Université Toulouse 2;5, Allee Antonio Machado;31058;Toulouse;43.5776992;1.4040191 +0311384L;Université Toulouse 3;118, Route De Narbonne;31062;Toulouse;43.5631577;1.4628392 diff --git a/config/etc/README b/config/etc/README new file mode 100644 index 00000000..26aea491 --- /dev/null +++ b/config/etc/README @@ -0,0 +1,9 @@ + +* Scripts utiles pour l'installation de ScoDoc + +- scodoc : script de demarrage a placer dans /etc/rc.* + +- scodoc-site-ssl: configuration de site (https) pour Apache, a placer dans + /etc/apache2/sites-available/ + puis editer pour remplacer le nom du site et indiquer vos certificats crypto + puis activer par a2ensite scodoc-site-ssl diff --git a/config/etc/firehol.conf b/config/etc/firehol.conf new file mode 100644 index 00000000..2f5d964a --- /dev/null +++ b/config/etc/firehol.conf @@ -0,0 +1,19 @@ +# +# Exemple de configuration pare-feu firehol minimal pour un serveur ScoDoc +# +# Suppose que l'interface reseau Internet est XXX_INTERFACE_XXX +# +# E. Viennet, juin 2008, jul 2017 +# + +version 5 + +FIREHOL_LOG_MODE="ULOG" + +interface XXX_INTERFACE_XXX internet + protection strong + client all accept # plutot gentil, a adapter a vos besoins (attention a ntp et aux mises a jour) + server ssh accept + server https accept + # pas de log des paquets venant d'Internet... + server any nolog drop diff --git a/config/etc/scodoc b/config/etc/scodoc new file mode 100755 index 00000000..cf7a10ff --- /dev/null +++ b/config/etc/scodoc @@ -0,0 +1,75 @@ +#!/bin/sh +# +# zope This shell script takes care of starting and stopping +# zope under apache (proxy) +# Emmanuel Viennet @ LIPN, June 2002, Sept 2011 (LSB-compliance for Debian 6) +# +# chkconfig: - 90 10 +# description: zope is a web server + + +### BEGIN INIT INFO +# Provides: ScoDoc +# Required-Start: $local_fs $remote_fs $network $syslog +# Required-Stop: $local_fs $remote_fs $network $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Start/stop ScoDoc server +### END INIT INFO + +# Source function library. +#. /etc/init.d/functions + + +ZOPE_DIR=/opt/scodoc/instance + +# [ -x $ZOPE_DIR/bin/python ] || exit 0 + +RETVAL=0 +prog="Zope" + +start() { + # Start Zope persistent process + # Start daemons. + echo -n $"Starting $prog: " + echo $ZOPE_DIR/bin/zopectl start + $ZOPE_DIR/bin/zopectl start 2> /dev/null + RETVAL=$? + echo +# [ $RETVAL -eq 0 ] && touch /var/lock/subsys/zope + return $RETVAL +} + +stop() { + # Stop daemons. + echo -n 'Stopping zope daemon: ' + $ZOPE_DIR/bin/zopectl stop + RETVAL=$? + echo +# [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/zope + return $RETVAL +} + +# See how we were called. +case "$1" in + start) + start + ;; + stop) + stop + ;; +# status) +# status $ZOPE_DIR/bin/python +# RETVAL=$? +# ;; + restart|reload) + stop + start + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|stop|restart}" + exit 1 +esac + +exit $RETVAL diff --git a/config/etc/scodoc-site-ssl-apache2.4.orig b/config/etc/scodoc-site-ssl-apache2.4.orig new file mode 100644 index 00000000..ba1a612c --- /dev/null +++ b/config/etc/scodoc-site-ssl-apache2.4.orig @@ -0,0 +1,56 @@ + +# Fichier config Apache basique pour ScoDoc +# E. Viennet, juin 2008, sept 2009, mar 2017 (Apache 2.4) + +# Apache fonctionne ici en proxy https devant zope (lui meme sur 8080). + +# A EDITER !!! +# 1- REMPLACER YOUR.FULL.HOST.NAME par votre nom de site (www.monscodoc.com) +# 2- indiquer vos certificats ssl (.key, .pem) +# 3- eventuellement, le certificat de votre authorite de certif. (CA). +# 4- eventuellement, revoir les fichiers de logs (penser au logrotate !) + + +NameVirtualHost *:443 + + ServerAdmin webmaster + ServerName YOUR.FULL.HOST.NAME + SSLEngine on + + SSLCertificateFile /etc/apache2/scodoc-ssl/apache.pem + # SSLCertificateKeyFile /etc/apache2/ssl/iutv.univ-paris13.fr.key + + # Votre authorite de certification: + # SSLCACertificateFile /etc/apache2/scodoc-ssl/ct_root.pem + # SSLCACertificateFile /etc/apache2/scodoc-ssl/sureserverEDU.pem + + DocumentRoot /var/www/ + + ErrorLog /var/log/apache2/ssl-error.log + + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + LogLevel warn + + CustomLog /var/log/apache2/ssl-access.log combined + + RewriteEngine on + # "RewriteLogLevel" n'est plus disponible dans apache2.4, il faut utiliser l'option "rewrite" dans l'instruction "LogLevel" + #LogLevel warn rewrite:trace3 + + # ScoDoc static content, served directly: + # RewriteCond %{HTTP:Authorization} ^(.*) + RewriteRule ^/ScoDoc/static/(.*) /opt/scodoc/Products/ScoDoc/static/$1 [L] + + # Le reste est pour Zope: + RewriteRule ^/(.*) http://localhost:8080/VirtualHostBase/https/YOUR.FULL.HOST.NAME:443/$1 [L,P] + + # Log avec utilisateur authentifie par ScoDoc + LogFormat "%h %l %{X-ScoDoc-User}o %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" scodoc + CustomLog "/var/log/apache2/scodoc_access.log" scodoc + + + + +Require all granted + diff --git a/config/etc/scodoc-site-ssl.orig b/config/etc/scodoc-site-ssl.orig new file mode 100644 index 00000000..0f628f23 --- /dev/null +++ b/config/etc/scodoc-site-ssl.orig @@ -0,0 +1,52 @@ + +# Fichier config Apache basique pour ScoDoc +# E. Viennet, juin 2008, sept 2009 + +# Apache fonctionne ici en proxy https devant zope (lui meme sur 8080). + +# A EDITER !!! +# 1- REMPLACER YOUR.FULL.HOST.NAME par votre nom de site (www.monscodoc.com) +# 2- indiquer vos certificats ssl (.key, .pem) +# 3- eventuellement, le certificat de votre authorite de certif. (CA). +# 4- eventuellement, revoir les fichiers de logs (penser au logrotate !) + + +NameVirtualHost *:443 + + ServerAdmin webmaster + ServerName YOUR.FULL.HOST.NAME + SSLEngine on + + SSLCertificateFile /etc/apache2/scodoc-ssl/apache.pem + # SSLCertificateKeyFile /etc/apache2/ssl/iutv.univ-paris13.fr.key + + # Votre authorite de certification: + # SSLCACertificateFile /etc/apache2/scodoc-ssl/ct_root.pem + # SSLCACertificateFile /etc/apache2/scodoc-ssl/sureserverEDU.pem + + DocumentRoot /var/www/ + + ErrorLog /var/log/apache2/ssl-error.log + + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + LogLevel warn + + CustomLog /var/log/apache2/ssl-access.log combined + + RewriteEngine on + RewriteLog /var/log/rewrite.ssl + RewriteLogLevel 0 + + # ScoDoc static content, served directly: + RewriteCond %{HTTP:Authorization} ^(.*) + RewriteRule ^/ScoDoc/static/(.*) /opt/scodoc/instance/Products/ScoDoc/static/$1 [L] + + # Le reste est pour Zope: + RewriteRule ^/(.*) http://localhost:8080/VirtualHostBase/https/YOUR.FULL.HOST.NAME:443/$1 [L,P] + + # Log avec utilisateur authentifie par ScoDoc + LogFormat "%h %l %{X-ScoDoc-User}o %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" scodoc + CustomLog "/var/log/apache2/scodoc_access.log" scodoc + + diff --git a/config/etc/scodoc-site.orig b/config/etc/scodoc-site.orig new file mode 100644 index 00000000..6829d876 --- /dev/null +++ b/config/etc/scodoc-site.orig @@ -0,0 +1,22 @@ +# +# Config Apache minimale http: redirige tout vers https +# (voir scodoc-site-ssl) +# +NameVirtualHost * + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/ + + AllowOverride None + + + RewriteEngine on + + ReWriteCond %{SERVER_PORT} !^443$ + RewriteRule ^/(.*) https://%{HTTP_HOST}/$1 [NC,R,L] + + ErrorLog /var/log/apache2/error.log + LogLevel warn + CustomLog /var/log/apache2/access.log combined + ServerSignature On + diff --git a/config/etc/scodoc-updater.service b/config/etc/scodoc-updater.service new file mode 100644 index 00000000..dd0ca9b9 --- /dev/null +++ b/config/etc/scodoc-updater.service @@ -0,0 +1,18 @@ +# +# Systemd service to upgrade ScoDoc +# +# Put this file in /etc/systemd/system, along with scodoc-updater.timer +# and then run +# systemctl enable scodoc-updater.timer +# systemctl start scodoc-updater.timer +# +# E. Viennet jun 2019 +# + +[Unit] +Description=Update ScoDoc Software + +[Service] +Type=oneshot +ExecStart=/opt/scodoc/Products/ScoDoc/config/upgrade.sh + diff --git a/config/etc/scodoc-updater.timer b/config/etc/scodoc-updater.timer new file mode 100644 index 00000000..3a7cddff --- /dev/null +++ b/config/etc/scodoc-updater.timer @@ -0,0 +1,18 @@ +# +# Systemd service to upgrade ScoDoc +# +# Put this file in /etc/systemd/system, along with scodoc-updater.service +# +# E. Viennet jun 2019 +# + +[Unit] +Description=Upgrade ScoDoc and Linux each week on Saturday night (sunday, 2:30 am) + +[Timer] +OnCalendar=Sun *-*-* 02:30:00 +# Add random delay, to avoid overloading ScoDoc upgrade server +RandomizedDelaySec=30min + +[Install] +WantedBy=timers.target diff --git a/config/initialize_database.sh b/config/initialize_database.sh new file mode 100755 index 00000000..d57849a0 --- /dev/null +++ b/config/initialize_database.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Initialize database (create tables) for a ScoDoc instance +# This script must be executed as www-data user +# +# $db_name and $DEPT passed as environment variables + +source config.sh +source utils.sh + +if [ $(id -nu) != $POSTGRES_USER ] +then + echo "$0: script must be runned as user $POSTGRES_USER" + exit 1 +fi + +echo 'Initializing tables in database ' $db_name +$PSQL -U $POSTGRES_USER -p $POSTGRES_PORT $db_name -f $SCODOC_DIR/misc/createtables.sql + + +# Set DeptName in preferences: +echo "insert into sco_prefs (name, value) values ('DeptName', '"${DEPT}\'\) | $PSQL -U $POSTGRES_USER -p $POSTGRES_PORT $db_name \ No newline at end of file diff --git a/config/install_cal_modules.sh b/config/install_cal_modules.sh new file mode 100755 index 00000000..1e5f4898 --- /dev/null +++ b/config/install_cal_modules.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Install module(s) for calendars +# (if already installed, do nothing) + + +# Test if installed +# (NB: don't launch python, to be faster and also to avoid import bug zope vs pytz) + +if [ -e /opt/zope213/lib/python2.7/site-packages/icalendar ] || [ /usr/lib/python2.7/dist-packages/icalendar ] +then + exit 0 # already installed +else + echo "Installing icalendar" + /opt/zope213/bin/pip install icalendar +fi + + + diff --git a/config/install_debian10.sh b/config/install_debian10.sh new file mode 100755 index 00000000..7e9579ee --- /dev/null +++ b/config/install_debian10.sh @@ -0,0 +1,259 @@ +#!/bin/bash + +# +# ScoDoc: install third-party software necessary for our installation +# starting for a minimal Debian (Stretch, 9.0) install. +# +# E. Viennet, Jun 2008, Apr 2009, Sept 2011, Sept 2013, Nov 2013, Mar 2017, Jul 2017, Jun 2019, Oct 2019 +# + +source config.sh +source utils.sh + +check_uid_root $0 + +PYTHON=/opt/zope213/bin/python + +# ------------ Safety checks +if [ ${debian_version} != "10" ] +then + echo "Version du systeme Linux Debian incompatible" + exit 1 +fi + +if [ $(arch) != "x86_64" ] +then + echo "Version du systeme Linux Debian incompatible (pas X86 64 bits)" + exit 1 +fi + +# ------------ Permissions & directories +# source dir should be writable by scodoc to write bytecode files +chgrp www-data $SCODOC_DIR $SCODOC_DIR/ZopeProducts/* +chmod g+w $SCODOC_DIR $SCODOC_DIR/ZopeProducts/* + +chgrp -R www-data "${SCODOC_DIR}"/static/photos +chmod -R g+w "${SCODOC_DIR}"/static/photos + +if [ ! -e "${SCODOC_VERSION_DIR}" ]; then + mkdir "${SCODOC_VERSION_DIR}" + chown www-data.www-data "${SCODOC_VERSION_DIR}" +fi + +# ------------ LOCALES +echo +echo '---- Configuration des locales...' +echo + +if [ ! -e /etc/locale.gen ] +then + touch /etc/locale.gen +fi + + +for locname in en_US.UTF-8 en_US.ISO-8859-15 en_US.ISO-8859-1 +do + outname=$(echo ${locname//-/} | tr '[A-Z]' '[a-z]') + if [ $(locale -a | egrep -i ^${outname}$ | wc -l) -lt 1 ] + then + echo adding $locname + echo "$locname ${locname##*.}" >> /etc/locale.gen + fi +done + +/usr/sbin/locale-gen --keep-existing + + +if [ "$LANG" != "en_US.UTF-8" ] +then + # ceci est necessaire a cause de postgresql 8.3 qui + # cree son cluster lors de l'install avec la locale par defaut ! + echo "Attention: changement de la locale par defaut" + mv /etc/default/locale /etc/default/locale.orig + echo "LANG=\"en_US.UTF-8\"" > /etc/default/locale + export LANG=en_US.UTF-8 +fi +echo 'Done.' + +# ------------ FIX pour passage Debian 7 -> Debian >= 8 +chsh -s /bin/sh www-data + +# ------------ AJOUT DES PAQUETS NECESSAIRES +apt-get update +apt-get -y install subversion curl cracklib-runtime +apt-get -y install apache2 ssl-cert +apt-get -y install postgresql +apt-get -y install graphviz + +# ------------ INSTALL DES EXTENSIONS PYTHON (2.7) + +apt-get -y install python-docutils +apt-get -y install python-jaxml +apt-get -y install python-psycopg2 +apt-get -y install python-pyrss2gen +apt-get -y install python-pil python-reportlab +apt-get -y install python-cracklib # was python-crack +apt-get -y install python-icalendar +apt-get -y install python-requests + +apt-get -y install python-egenix-mxtools python-egenix-mxdatetime + + +# ------------ +SVNVERSION=$(cd ..; svnversion) +SVERSION=$(curl --silent http://scodoc.iutv.univ-paris13.fr/scodoc-installmgr/version?mode=install\&svn=$SVNVERSION) +echo $SVERSION > "${SCODOC_VERSION_DIR}/scodoc.sn" + + +# ------------ POSTFIX +echo +echo "ScoDoc a besoin de pouvoir envoyer des messages par mail." +echo -n "Voulez vous configurer la messagerie (tres recommande) ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + apt-get -y install postfix +fi + +# ------------ CONFIG FIREWALL (non teste en Debian 10) +echo +echo "Le firewall aide a proteger votre serveur d'intrusions indesirables." +echo -n "Voulez vous configurer un firewall minimal (ufw) ? (y/n) [n] " +read ans +if [ "$(norm_ans "$ans")" = 'Y' ] +then + echo 'Installation du firewall IP ufw (voir documentation Debian)' + echo ' on autorise les connexions ssh et https' + apt-get -y install ufw + ufw default deny incoming + ufw default allow outgoing + ufw allow ssh + ufw allow https + yes | ufw enable +fi + +# Nota: after this point, the network _may_ be unreachable +# (if firewall config is wrong) + +# ------------ CONFIG APACHE +a2enmod ssl +a2enmod proxy +a2enmod proxy_http +a2enmod rewrite + +echo +echo "La configuration du serveur web va modifier votre installation Apache pour supporter ScoDoc." +echo -n "Voulez vous configurer le serveur web Apache maintenant (tres conseille) ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo "Configuration d'Apache" + server_name="" + while [ -z $server_name ] + do + echo "Le nom de votre serveur doit normalement etre connu dans le DNS." + echo -n "Nom complet de votre serveur (exemple: notes.univ.fr): " + read server_name + done + # --- CERTIFICATS AUTO-SIGNES + echo + echo "Il est possible d'utiliser des certificats cryptographiques" + echo "auto-signes, qui ne seront pas reconnus comme de confiance" + echo "par les navigateurs, mais offrent une certaine securite." + echo -n 'Voulez vous generer des certificats ssl auto-signes ? (y/n) [y] ' + read ans + if [ "$(norm_ans "$ans")" != 'N' ] + then + # attention: utilise dans scodoc-site-ssl.orig + ssl_dir=/etc/apache2/scodoc-ssl + if [ ! -e $ssl_dir ] + then + mkdir $ssl_dir + fi + /usr/sbin/make-ssl-cert /usr/share/ssl-cert/ssleay.cnf $ssl_dir/apache.pem + cert_status=$? + else + cert_status=-1 + fi + # --- + echo 'generation de /etc/apache2/sites-available/scodoc-site-ssl' + cat $SCODOC_DIR/config/etc/scodoc-site-ssl-apache2.4.orig | sed -e "s:YOUR\.FULL\.HOST\.NAME:$server_name:g" > /etc/apache2/sites-available/scodoc-site-ssl.conf + echo 'activation du site...' + a2ensite scodoc-site-ssl + + echo 'Remplacement du site Apache par defaut (sic ! old saved as .bak)' + fn=/etc/apache2/sites-available/000-default.conf + if [ -e $fn ] + then + mv $fn $fn.bak + fi + cp $SCODOC_DIR/config/etc/scodoc-site.orig $fn + + if [ -z "$(grep Listen /etc/apache2/ports.conf | grep 443)" ] + then + echo 'adding port 443' + echo 'Listen 443' >> /etc/apache2/ports.conf + fi + + echo 'configuring Apache proxy' + mv /etc/apache2/mods-available/proxy.conf /etc/apache2/mods-available/proxy.conf.bak + cat > /etc/apache2/mods-available/proxy.conf < +# Proxy config for ScoDoc default installation +ProxyRequests Off + + Order deny,allow + Allow from all + + +EOF + +fi + +systemctl restart apache2 + +# ------------ CONFIG SERVICE SCODOC +echo +echo "Installer le service scodoc permet de lancer automatiquement le serveur au demarrage." +echo -n "Voulez vous installer le service scodoc ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo 'Installation du demarrage automatique de ScoDoc' + cp $SCODOC_DIR/config/etc/scodoc /etc/init.d/ + update-rc.d scodoc defaults +fi + + +# ------------ CONFIG MISE A JOUR HEBDOMADAIRE +echo +echo -n "Mises a jour hebdomadaires (tres recommande) ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + cp $SCODOC_DIR/config/etc/scodoc-updater.service /etc/systemd/system + cp $SCODOC_DIR/config/etc/scodoc-updater.timer /etc/systemd/system + systemctl enable scodoc-updater.timer + systemctl start scodoc-updater.timer +fi + +# ------------ THE END +echo +echo "Installation terminee." +echo +echo "Vous pouvez maintenant creer la base d'utilisateurs avec ./create_user_db.sh" +echo "puis creer un departement avec ./create_dept.sh" +echo "Ou bien restaurer vos donnees a partir d'une ancienne installation a l'aide du script restore_scodoc_data.sh" +echo "(voir https://trac.lipn.univ-paris13.fr/projects/scodoc/wiki/MigrationDonneesScoDoc)" +echo + + +if [ "${cert_status}" != 0 ] +then + echo "Attention: le serveur Web Apache n'a pas de certificat." + echo "Il est probable qu'il ne fonctionne pas." + echo "Installez vos certificats ou generez provisoirement des certificats autosignes" + echo "avec la commande: /usr/sbin/make-ssl-cert /usr/share/ssl-cert/ssleay.cnf $ssl_dir/apache.pem" + echo +fi + diff --git a/config/install_debian7.sh b/config/install_debian7.sh new file mode 100755 index 00000000..aed314e3 --- /dev/null +++ b/config/install_debian7.sh @@ -0,0 +1,256 @@ +#!/bin/bash + +# +# ScoDoc: install third-party software necessary for our installation +# starting for a minimal Debian (Wheezy, 7.0) install. +# +# E. Viennet, Jun 2008, Apr 2009, Sept 2011, Sept 2013, Nov 2013 +# + +source config.sh +source utils.sh + +check_uid_root $0 + +PYTHON=/opt/zope213/bin/python + +# ------------ Safety checks +if [ ${debian_version} != "7" ] +then + echo "Version du systeme Linux Debian incompatible" + exit 1 +fi + +if [ $(arch) != "x86_64" ] +then + echo "Version du systeme Linux Debian incompatible (pas X86 64 bits)" + exit 1 +fi + +# ------------ Permissions +# source dir should be writable by scodoc to write bytecode files +chgrp www-data $SCODOC_DIR $SCODOC_DIR/ZopeProducts/* +chmod g+w $SCODOC_DIR $SCODOC_DIR/ZopeProducts/* + +chgrp -R www-data "${SCODOC_DIR}"/static/photos +chmod -R g+w "${SCODOC_DIR}"/static/photos + +# ------------ LOCALES +echo +echo '---- Configuration des locales...' +echo + +if [ ! -e /etc/locale.gen ] +then +touch /etc/locale.gen +fi + + +for locname in en_US.UTF-8 en_US.ISO-8859-15 en_US.ISO-8859-1 +do + outname=$(echo ${locname//-/} | tr '[A-Z]' '[a-z]') + if [ $(locale -a | egrep -i ^${outname}$ | wc -l) -lt 1 ] + then + echo adding $locname + echo "$locname ${locname##*.}" >> /etc/locale.gen + fi +done + +/usr/sbin/locale-gen --keep-existing + + +if [ "$LANG" != "en_US.UTF-8" ] +then + # ceci est necessaire a cause de postgresql 8.3 qui + # cree son cluster lors de l'install avec la locale par defaut ! + echo "Attention: changement de la locale par defaut" + mv /etc/default/locale /etc/default/locale.orig + echo "LANG=\"en_US.UTF-8\"" > /etc/default/locale + export LANG=en_US.UTF-8 +fi +echo 'Done.' + + +# ------------ AJOUT DES PAQUETS NECESSAIRES +apt-get update +apt-get -y dist-upgrade +apt-get -y install subversion curl cracklib-runtime firehol +apt-get -y install apache2 ssl-cert +apt-get -y install postgresql-9.1 postgresql-client-9.1 +apt-get -y install graphviz + +# ------------ INSTALL DES EXTENSIONS PYTHON (2.7) + +apt-get -y install python-jaxml +apt-get -y install python-psycopg2 +apt-get -y install python-pyrss2gen +apt-get -y install python-imaging python-reportlab +apt-get -y install python-cracklib # was python-crack +apt-get -y install python-pyparsing +apt-get -y install python-beautifulsoup + +apt-get -y install python-egenix-mxtools python-egenix-mxdatetime + +# Installe la version standard de pydot (XXX A TESTER) +# apt-get -y install python-pydot +# python-pydot is currently bugged in Debian 5: install our 0.9.10 +# Le probleme: pydot v > 1 a change l'API : resultat de get_node est une liste. Resolu par sco_utils.pydot_get_node +# pydot 1.0.25 bug avec python 2.4 (get_node_list() renvoie toujours []) +# 1.0.3 idem (voir misc/testpydot.py) +echo '\nInstallation de pydot\n' +apt-get -y remove python-pydot +(cd /tmp; tar xfz $SCODOC_DIR/config/softs/pydot-0.9.10.tar.gz) +(cd /tmp/pydot-0.9.10; $PYTHON setup.py install) + + +# UNUSED BY ScoDoc 7: +# SOFTS="$SCODOC_DIR/config/softs" + +# ------------ Upgrade from svn +(cd "${SCODOC_DIR}"; svn update) + +# ------------ +SVNVERSION=$(cd ..; svnversion) +SVERSION=$(curl --silent http://scodoc.iutv.univ-paris13.fr/scodoc-installmgr/version?mode=install\&svn=$SVNVERSION) +echo $SVERSION > "${SCODOC_VERSION_DIR}/scodoc.sn" + + +# ------------ PYEXCELERATOR +echo +echo 'Installation de pyExcelerator' +echo + +(cd /tmp; tar xfz $SCODOC_DIR/config/softs/pyExcelerator-0.6.3a.patched.tgz) +(cd /tmp/pyExcelerator-0.6.3a.patched; $PYTHON setup.py install) + +echo 'Done.' + +# ------------ POSTFIX +echo +echo "ScoDoc a besoin de pouvoir envoyer des messages par mail." +echo -n "Voulez vous configurer la messagerie (tres recommande) ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + apt-get -y install postfix +fi + +# ------------ CONFIG FIREWALL +echo +echo "Le firewall aide a proteger votre serveur d'intrusions indesirables." +echo -n "Voulez vous installer un firewall minimal ? (y/n) [n] " +read ans +if [ "$(norm_ans "$ans")" = 'Y' ] +then + echo 'Installation du firewall IP (voir /etc/firehol/firehol.conf)' + echo "Attention: suppose que l'interface reseau vers Internet est eth0" + echo " si ce n'est pas le cas, editer /etc/firehol/firehol.conf" + echo " et relancer: /etc/init.d/firehol restart" + echo + cp $SCODOC_DIR/config/etc/firehol.conf /etc/firehol/ + mv /etc/default/firehol /etc/default/firehol.orig + cat /etc/default/firehol.orig | sed 's/START_FIREHOL=NO/START_FIREHOL=YES/' > /tmp/firehol && mv /tmp/firehol /etc/default/firehol + # mise a jour RESERVED_IPS (le script fournit en Deb 7 get-iana est buggy) + echo yes | $SCODOC_DIR/config/softs/get-iana.sh +fi + +# Nota: after this point, the network may be unreachable +# (if firewall config is wrong) + +# ------------ CONFIG APACHE +a2enmod ssl +a2enmod proxy +a2enmod proxy_http +a2enmod rewrite + +echo +echo "La configuration du serveur web va modifier votre installation Apache pour supporter ScoDoc." +echo -n "Voulez vous configurer le serveur web Apache maintenant ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo "Configuration d'Apache" + server_name="" + while [ -z $server_name ] + do + echo "Le nom de votre serveur doit normalement etre connu dans le DNS." + echo -n "Nom complet de votre serveur (exemple: notes.univ.fr): " + read server_name + done + # --- CERTIFICATS AUTO-SIGNES + echo + echo "Il est possible d'utiliser des certificats cryptographiques" + echo "auto-signes, qui ne seront pas reconnus comme de confiance" + echo "par les navigateurs, mais offrent une certaine securite." + echo -n 'Voulez vous generer des certificats ssl auto-signes ? (y/n) [y] ' + read ans + if [ "$(norm_ans "$ans")" != 'N' ] + then + # attention: utilise dans scodoc-site-ssl.orig + ssl_dir=/etc/apache2/scodoc-ssl + if [ ! -e $ssl_dir ] + then + mkdir $ssl_dir + fi + /usr/sbin/make-ssl-cert /usr/share/ssl-cert/ssleay.cnf $ssl_dir/apache.pem + fi + # --- + echo 'generation de /etc/apache2/sites-available/scodoc-site-ssl' + cat $SCODOC_DIR/config/etc/scodoc-site-ssl.orig | sed -e "s:YOUR\.FULL\.HOST\.NAME:$server_name:g" > /etc/apache2/sites-available/scodoc-site-ssl + echo 'activation du site...' + a2ensite scodoc-site-ssl + + echo 'Remplacement du site Apache par defaut (sic ! old saved as .bak)' + fn=/etc/apache2/sites-available/default + if [ -e $fn ] + then + mv $fn $fn.bak + fi + cp $SCODOC_DIR/config/etc/scodoc-site.orig $fn + + if [ -z "$(grep Listen /etc/apache2/ports.conf | grep 443)" ] + then + echo 'adding port 443' + echo 'Listen 443' >> /etc/apache2/ports.conf + fi + + echo 'configuring Apache proxy' + mv /etc/apache2/mods-available/proxy.conf /etc/apache2/mods-available/proxy.conf.bak + cat > /etc/apache2/mods-available/proxy.conf < +# Proxy config for ScoDoc default installation +ProxyRequests Off + + Order deny,allow + Allow from all + + +EOF + + /etc/init.d/apache2 restart +fi + + +# ------------ CONFIG SERVICE SCODOC +echo +echo "Installer le service scodoc permet de lancer automatiquement le serveur au demarrage." +echo -n "Voulez vous installer le service scodoc ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo 'Installation du demarrage automatique de ScoDoc' + cp $SCODOC_DIR/config/etc/scodoc /etc/init.d/ + insserv scodoc +fi + +# ------------ THE END +echo +echo "Installation terminee." +echo +echo "Vous pouvez maintenant creer la base d'utilisateurs avec ./create_user_db.sh" +echo "puis creer un departement avec ./create_dept.sh" +echo "Ou bien restaurer vos donnees a partir d'une ancienne installation a l'aide du script restore_scodoc_data.sh" +echo "(voir https://trac.lipn.univ-paris13.fr/projects/scodoc/wiki/MigrationVersScoDocSept)" +echo + + diff --git a/config/install_debian8.sh b/config/install_debian8.sh new file mode 100755 index 00000000..2741ceed --- /dev/null +++ b/config/install_debian8.sh @@ -0,0 +1,275 @@ +#!/bin/bash + +# +# ScoDoc: install third-party software necessary for our installation +# starting for a minimal Debian (Jessie, 8.0) install. +# +# E. Viennet, Jun 2008, Apr 2009, Sept 2011, Sept 2013, Nov 2013, Mar 2017 +# + +source config.sh +source utils.sh + +check_uid_root $0 + +PYTHON=/opt/zope213/bin/python + +# ------------ Safety checks +if [ ${debian_version} != "8" ] +then + echo "Version du systeme Linux Debian incompatible" + exit 1 +fi + +if [ $(arch) != "x86_64" ] +then + echo "Version du systeme Linux Debian incompatible (pas X86 64 bits)" + exit 1 +fi + +# ------------ Permissions +# source dir should be writable by scodoc to write bytecode files +chgrp www-data $SCODOC_DIR $SCODOC_DIR/ZopeProducts/* +chmod g+w $SCODOC_DIR $SCODOC_DIR/ZopeProducts/* + +chgrp -R www-data "${SCODOC_DIR}"/static/photos +chmod -R g+w "${SCODOC_DIR}"/static/photos + +# ------------ LOCALES +echo +echo '---- Configuration des locales...' +echo + +if [ ! -e /etc/locale.gen ] +then +touch /etc/locale.gen +fi + + +for locname in en_US.UTF-8 en_US.ISO-8859-15 en_US.ISO-8859-1 +do + outname=$(echo ${locname//-/} | tr '[A-Z]' '[a-z]') + if [ $(locale -a | egrep -i ^${outname}$ | wc -l) -lt 1 ] + then + echo adding $locname + echo "$locname ${locname##*.}" >> /etc/locale.gen + fi +done + +/usr/sbin/locale-gen --keep-existing + + +if [ "$LANG" != "en_US.UTF-8" ] +then + # ceci est necessaire a cause de postgresql 8.3 qui + # cree son cluster lors de l'install avec la locale par defaut ! + echo "Attention: changement de la locale par defaut" + mv /etc/default/locale /etc/default/locale.orig + echo "LANG=\"en_US.UTF-8\"" > /etc/default/locale + export LANG=en_US.UTF-8 +fi +echo 'Done.' + +# ------------ FIX pour passage Debien 7 -> Debian 8 +chsh -s /bin/sh www-data + +# ------------ AJOUT DES PAQUETS NECESSAIRES +apt-get update +apt-get -y install subversion curl cracklib-runtime firehol +apt-get -y install apache2 ssl-cert +apt-get -y install postgresql-9.4 postgresql-client-9.4 +apt-get -y install graphviz + +# ------------ INSTALL DES EXTENSIONS PYTHON (2.7) + +apt-get -y install python-jaxml +apt-get -y install python-psycopg2 +apt-get -y install python-pyrss2gen +apt-get -y install python-imaging python-reportlab +apt-get -y install python-cracklib # was python-crack +#apt-get -y install python-pyparsing +apt-get -y install python-beautifulsoup + +apt-get -y install python-egenix-mxtools python-egenix-mxdatetime + +# pydot est un probleme: todo: rececrire l'affichae du graphe sans pydot +# ou contribuer une versions ans bug de pydot +# ou encore simplement eviter d'utiliser graph_from_edges ? +# +# python-pydot is currently bugged in Debian 5, 6, 7, 8: install our 0.9.10 +# Le probleme: pydot v > 1 a change l'API : resultat de get_node est une liste. Resolu par sco_utils.pydot_get_node +# pydot 1.0.25 bug avec python 2.4 (get_node_list() renvoie toujours []) +# 1.0.3 idem (voir misc/testpydot.py) +# 1.2.4 / python2.7: bug graph_from_edges toujours là +# voir https://bugs.launchpad.net/ubuntu/+source/pydot/+bug/490015 +echo '\nInstallation de pydot\n' + +apt-get -y remove python-pydot +# Version ancienne de pyparsing requise par pydot +apt-get -y remove python-pyparsing +/opt/zope213/bin/pip install pyparsing==1.5.7 +(cd /tmp; tar xfz $SCODOC_DIR/config/softs/pydot-0.9.10.tar.gz) +(cd /tmp/pydot-0.9.10; $PYTHON setup.py install) + + +# UNUSED BY ScoDoc 7: +# SOFTS="$SCODOC_DIR/config/softs" + +# ------------ +SVNVERSION=$(cd ..; svnversion) +SVERSION=$(curl --silent http://scodoc.iutv.univ-paris13.fr/scodoc-installmgr/version?mode=install\&svn=$SVNVERSION) +echo $SVERSION > "${SCODOC_VERSION_DIR}/scodoc.sn" + + +# ------------ PYEXCELERATOR +# inutile en Debian 8 car dans l'image ScoDoc +#echo +#echo 'Installation de pyExcelerator' +#echo +#(cd /tmp; tar xfz $SCODOC_DIR/config/softs/pyExcelerator-0.6.3a.patched.tgz) +#(cd /tmp/pyExcelerator-0.6.3a.patched; $PYTHON setup.py install) +#echo 'Done.' + +# ------------ POSTFIX +echo +echo "ScoDoc a besoin de pouvoir envoyer des messages par mail." +echo -n "Voulez vous configurer la messagerie (tres recommande) ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + apt-get -y install postfix +fi + +# ------------ CONFIG FIREWALL +echo +echo "Le firewall aide a proteger votre serveur d'intrusions indesirables." +echo -n "Voulez vous installer un firewall minimal ? (y/n) [n] " +read ans +if [ "$(norm_ans "$ans")" = 'Y' ] +then + network_interface="eth0" + echo 'Installation du firewall IP (voir /etc/firehol/firehol.conf)' + echo "Attention: suppose que l'interface reseau vers Internet est eth0" + echo " si ce n'est pas le cas, editer /etc/firehol/firehol.conf" + echo " et relancer: /etc/init.d/firehol restart" + echo + cat $SCODOC_DIR/config/etc/firehol.conf | sed "s/XXX_INTERFACE_XXX/${network_interface}/" > /etc/firehol/firehol.conf + mv /etc/default/firehol /etc/default/firehol.orig + cat /etc/default/firehol.orig | sed 's/START_FIREHOL=NO/START_FIREHOL=YES/' > /tmp/firehol && mv /tmp/firehol /etc/default/firehol + # mise a jour RESERVED_IPS (le script fournit en Deb 7 get-iana est buggy) + echo yes | $SCODOC_DIR/config/softs/get-iana.sh +fi + +# Nota: after this point, the network may be unreachable +# (if firewall config is wrong) + +# ------------ CONFIG APACHE +a2enmod ssl +a2enmod proxy +a2enmod proxy_http +a2enmod rewrite + +echo +echo "La configuration du serveur web va modifier votre installation Apache pour supporter ScoDoc." +echo -n "Voulez vous configurer le serveur web Apache maintenant (tres conseille) ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo "Configuration d'Apache" + server_name="" + while [ -z $server_name ] + do + echo "Le nom de votre serveur doit normalement etre connu dans le DNS." + echo -n "Nom complet de votre serveur (exemple: notes.univ.fr): " + read server_name + done + # --- CERTIFICATS AUTO-SIGNES + echo + echo "Il est possible d'utiliser des certificats cryptographiques" + echo "auto-signes, qui ne seront pas reconnus comme de confiance" + echo "par les navigateurs, mais offrent une certaine securite." + echo -n 'Voulez vous generer des certificats ssl auto-signes ? (y/n) [y] ' + read ans + if [ "$(norm_ans "$ans")" != 'N' ] + then + # attention: utilise dans scodoc-site-ssl.orig + ssl_dir=/etc/apache2/scodoc-ssl + if [ ! -e $ssl_dir ] + then + mkdir $ssl_dir + fi + /usr/sbin/make-ssl-cert /usr/share/ssl-cert/ssleay.cnf $ssl_dir/apache.pem + cert_status=$? + else + cert_status=-1 + fi + # --- + echo 'generation de /etc/apache2/sites-available/scodoc-site-ssl' + cat $SCODOC_DIR/config/etc/scodoc-site-ssl-apache2.4.orig | sed -e "s:YOUR\.FULL\.HOST\.NAME:$server_name:g" > /etc/apache2/sites-available/scodoc-site-ssl.conf + echo 'activation du site...' + a2ensite scodoc-site-ssl + + echo 'Remplacement du site Apache par defaut (sic ! old saved as .bak)' + fn=/etc/apache2/sites-available/000-default.conf + if [ -e $fn ] + then + mv $fn $fn.bak + fi + cp $SCODOC_DIR/config/etc/scodoc-site.orig $fn + + if [ -z "$(grep Listen /etc/apache2/ports.conf | grep 443)" ] + then + echo 'adding port 443' + echo 'Listen 443' >> /etc/apache2/ports.conf + fi + + echo 'configuring Apache proxy' + mv /etc/apache2/mods-available/proxy.conf /etc/apache2/mods-available/proxy.conf.bak + cat > /etc/apache2/mods-available/proxy.conf < +# Proxy config for ScoDoc default installation +ProxyRequests Off + + Order deny,allow + Allow from all + + +EOF + +fi + +service apache2 restart + + +# ------------ CONFIG SERVICE SCODOC +echo +echo "Installer le service scodoc permet de lancer automatiquement le serveur au demarrage." +echo -n "Voulez vous installer le service scodoc ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo 'Installation du demarrage automatique de ScoDoc' + cp $SCODOC_DIR/config/etc/scodoc /etc/init.d/ + insserv scodoc +fi + +# ------------ THE END +echo +echo "Installation terminee." +echo +echo "Vous pouvez maintenant creer la base d'utilisateurs avec ./create_user_db.sh" +echo "puis creer un departement avec ./create_dept.sh" +echo "Ou bien restaurer vos donnees a partir d'une ancienne installation a l'aide du script restore_scodoc_data.sh" +echo "(voir https://trac.lipn.univ-paris13.fr/projects/scodoc/wiki/MigrationDonneesScoDoc)" +echo + +if [ "${cert_status}" != 0 ] +then + echo "Attention: le serveur Web Apache n'a pas de certificat." + echo "Il est probable qu'il ne fonctionne pas." + echo "Installez vos certificats ou generez provisoirement des certificats autosignes" + echo "avec la commande: /usr/sbin/make-ssl-cert /usr/share/ssl-cert/ssleay.cnf $ssl_dir/apache.pem" + echo +fi + + diff --git a/config/install_debian9.sh b/config/install_debian9.sh new file mode 100755 index 00000000..0e54bd14 --- /dev/null +++ b/config/install_debian9.sh @@ -0,0 +1,302 @@ +#!/bin/bash + +# +# ScoDoc: install third-party software necessary for our installation +# starting for a minimal Debian (Stretch, 9.0) install. +# +# E. Viennet, Jun 2008, Apr 2009, Sept 2011, Sept 2013, Nov 2013, Mar 2017, Jul 2017, Jun 2019 +# + +source config.sh +source utils.sh + +check_uid_root $0 + +PYTHON=/opt/zope213/bin/python + +# ------------ Safety checks +if [ ${debian_version} != "9" ] +then + echo "Version du systeme Linux Debian incompatible" + exit 1 +fi + +if [ $(arch) != "x86_64" ] +then + echo "Version du systeme Linux Debian incompatible (pas X86 64 bits)" + exit 1 +fi + +# ------------ Permissions & directories +# source dir should be writable by scodoc to write bytecode files +chgrp www-data $SCODOC_DIR $SCODOC_DIR/ZopeProducts/* +chmod g+w $SCODOC_DIR $SCODOC_DIR/ZopeProducts/* + +chgrp -R www-data "${SCODOC_DIR}"/static/photos +chmod -R g+w "${SCODOC_DIR}"/static/photos + +if [ ! -e "${SCODOC_VERSION_DIR}" ]; then + mkdir "${SCODOC_VERSION_DIR}" + chown www-data.www-data "${SCODOC_VERSION_DIR}" +fi + +# ------------ LOCALES +echo +echo '---- Configuration des locales...' +echo + +if [ ! -e /etc/locale.gen ] +then +touch /etc/locale.gen +fi + + +for locname in en_US.UTF-8 en_US.ISO-8859-15 en_US.ISO-8859-1 +do + outname=$(echo ${locname//-/} | tr '[A-Z]' '[a-z]') + if [ $(locale -a | egrep -i ^${outname}$ | wc -l) -lt 1 ] + then + echo adding $locname + echo "$locname ${locname##*.}" >> /etc/locale.gen + fi +done + +/usr/sbin/locale-gen --keep-existing + + +if [ "$LANG" != "en_US.UTF-8" ] +then + # ceci est necessaire a cause de postgresql 8.3 qui + # cree son cluster lors de l'install avec la locale par defaut ! + echo "Attention: changement de la locale par defaut" + mv /etc/default/locale /etc/default/locale.orig + echo "LANG=\"en_US.UTF-8\"" > /etc/default/locale + export LANG=en_US.UTF-8 +fi +echo 'Done.' + +# ------------ FIX pour passage Debian 7 -> Debian 8 +chsh -s /bin/sh www-data + +# ------------ AJOUT DES PAQUETS NECESSAIRES +apt-get update +apt-get -y install subversion curl cracklib-runtime +apt-get -y install apache2 ssl-cert +apt-get -y install firehol +apt-get -y install insserv +apt-get -y install postgresql-9.6 postgresql-client-9.6 +apt-get -y install graphviz + +# ------------ INSTALL DES EXTENSIONS PYTHON (2.7) + +apt-get -y install python-docutils +apt-get -y install python-jaxml +apt-get -y install python-psycopg2 +apt-get -y install python-pyrss2gen +apt-get -y install python-imaging python-reportlab +apt-get -y install python-cracklib # was python-crack +#apt-get -y install python-pyparsing +apt-get -y install python-beautifulsoup + +apt-get -y install python-egenix-mxtools python-egenix-mxdatetime + +# pydot est un probleme: todo: rececrire l'affichage du graphe sans pydot +# ou contribuer une versions sans bug de pydot +# ou encore simplement eviter d'utiliser graph_from_edges ? +# +# python-pydot is currently bugged in Debian 5, 6, 7, 8: install our 0.9.10 +# Le probleme: pydot v > 1 a change l'API : resultat de get_node est une liste. Resolu par sco_utils.pydot_get_node +# pydot 1.0.25 bug avec python 2.4 (get_node_list() renvoie toujours []) +# 1.0.3 idem (voir misc/testpydot.py) +# 1.2.4 / python2.7: bug graph_from_edges toujours là +# voir https://bugs.launchpad.net/ubuntu/+source/pydot/+bug/490015 +echo '\nInstallation de pydot\n' + +apt-get -y remove python-pydot +# Version ancienne de pyparsing requise par pydot +apt-get -y remove python-pyparsing +/opt/zope213/bin/pip install pyparsing==1.5.7 +(cd /tmp; tar xfz $SCODOC_DIR/config/softs/pydot-0.9.10.tar.gz) +(cd /tmp/pydot-0.9.10; $PYTHON setup.py install) + +# Fix dateutil: +/opt/zope213/bin/pip install python-dateutil --upgrade + +# UNUSED BY ScoDoc 7: +# SOFTS="$SCODOC_DIR/config/softs" + +# ------------ +SVNVERSION=$(cd ..; svnversion) +SVERSION=$(curl --silent http://scodoc.iutv.univ-paris13.fr/scodoc-installmgr/version?mode=install\&svn=$SVNVERSION) +echo $SVERSION > "${SCODOC_VERSION_DIR}/scodoc.sn" + + +# ------------ PYEXCELERATOR +# inutile en Debian 8 car dans l'image ScoDoc +#echo +#echo 'Installation de pyExcelerator' +#echo +#(cd /tmp; tar xfz $SCODOC_DIR/config/softs/pyExcelerator-0.6.3a.patched.tgz) +#(cd /tmp/pyExcelerator-0.6.3a.patched; $PYTHON setup.py install) +#echo 'Done.' + +# ------------ POSTFIX +echo +echo "ScoDoc a besoin de pouvoir envoyer des messages par mail." +echo -n "Voulez vous configurer la messagerie (tres recommande) ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + apt-get -y install postfix +fi + +# ------------ CONFIG FIREWALL +echo +echo "Le firewall aide a proteger votre serveur d'intrusions indesirables." +echo -n "Voulez vous installer un firewall minimal (firehol) ? (y/n) [n] " +read ans +if [ "$(norm_ans "$ans")" = 'Y' ] +then + echo 'Installation du firewall IP (voir /etc/firehol/firehol.conf)' + echo "Debian 9 n'utilise plus eth0 mais des noms fixes, comme enp0s3" + echo "Vos interfaces reseau actuelles sont:" + ip a + echo -n "Nom de votre interface reseau vers Internet ? " + read network_interface + echo "Attention: configure l'interface reseau vers Internet ${network_interface}" + echo " si ce n'est pas le cas, editer /etc/firehol/firehol.conf" + echo " et relancer: /etc/init.d/firehol restart" + echo + cat $SCODOC_DIR/config/etc/firehol.conf | sed "s/XXX_INTERFACE_XXX/${network_interface}/" > /etc/firehol/firehol.conf + mv /etc/default/firehol /etc/default/firehol.orig + cat /etc/default/firehol.orig | sed 's/START_FIREHOL=NO/START_FIREHOL=YES/' > /tmp/firehol && mv /tmp/firehol /etc/default/firehol + # mise a jour RESERVED_IPS (le script fournit en Deb 7 get-iana est buggy) + echo yes | $SCODOC_DIR/config/softs/get-iana.sh +fi + +# Nota: after this point, the network may be unreachable +# (if firewall config is wrong) + +# ------------ CONFIG APACHE +a2enmod ssl +a2enmod proxy +a2enmod proxy_http +a2enmod rewrite + +echo +echo "La configuration du serveur web va modifier votre installation Apache pour supporter ScoDoc." +echo -n "Voulez vous configurer le serveur web Apache maintenant (tres conseille) ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo "Configuration d'Apache" + server_name="" + while [ -z $server_name ] + do + echo "Le nom de votre serveur doit normalement etre connu dans le DNS." + echo -n "Nom complet de votre serveur (exemple: notes.univ.fr): " + read server_name + done + # --- CERTIFICATS AUTO-SIGNES + echo + echo "Il est possible d'utiliser des certificats cryptographiques" + echo "auto-signes, qui ne seront pas reconnus comme de confiance" + echo "par les navigateurs, mais offrent une certaine securite." + echo -n 'Voulez vous generer des certificats ssl auto-signes ? (y/n) [y] ' + read ans + if [ "$(norm_ans "$ans")" != 'N' ] + then + # attention: utilise dans scodoc-site-ssl.orig + ssl_dir=/etc/apache2/scodoc-ssl + if [ ! -e $ssl_dir ] + then + mkdir $ssl_dir + fi + /usr/sbin/make-ssl-cert /usr/share/ssl-cert/ssleay.cnf $ssl_dir/apache.pem + cert_status=$? + else + cert_status=-1 + fi + # --- + echo 'generation de /etc/apache2/sites-available/scodoc-site-ssl' + cat $SCODOC_DIR/config/etc/scodoc-site-ssl-apache2.4.orig | sed -e "s:YOUR\.FULL\.HOST\.NAME:$server_name:g" > /etc/apache2/sites-available/scodoc-site-ssl.conf + echo 'activation du site...' + a2ensite scodoc-site-ssl + + echo 'Remplacement du site Apache par defaut (sic ! old saved as .bak)' + fn=/etc/apache2/sites-available/000-default.conf + if [ -e $fn ] + then + mv $fn $fn.bak + fi + cp $SCODOC_DIR/config/etc/scodoc-site.orig $fn + + if [ -z "$(grep Listen /etc/apache2/ports.conf | grep 443)" ] + then + echo 'adding port 443' + echo 'Listen 443' >> /etc/apache2/ports.conf + fi + + echo 'configuring Apache proxy' + mv /etc/apache2/mods-available/proxy.conf /etc/apache2/mods-available/proxy.conf.bak + cat > /etc/apache2/mods-available/proxy.conf < +# Proxy config for ScoDoc default installation +ProxyRequests Off + + Order deny,allow + Allow from all + + +EOF + +fi + +service apache2 restart + + +# ------------ CONFIG SERVICE SCODOC +echo +echo "Installer le service scodoc permet de lancer automatiquement le serveur au demarrage." +echo -n "Voulez vous installer le service scodoc ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + echo 'Installation du demarrage automatique de ScoDoc' + cp $SCODOC_DIR/config/etc/scodoc /etc/init.d/ + insserv scodoc +fi + + +# ------------ CONFIG MISE A JOUR HEBDOMADAIRE +echo +echo -n "Mises a jour hebdomadaires (tres recommande) ? (y/n) [y] " +read ans +if [ "$(norm_ans "$ans")" != 'N' ] +then + cp $SCODOC_DIR/config/etc/scodoc-updater.service /etc/systemd/system + cp $SCODOC_DIR/config/etc/scodoc-updater.timer /etc/systemd/system + systemctl enable scodoc-updater.timer + systemctl start scodoc-updater.timer +fi + +# ------------ THE END +echo +echo "Installation terminee." +echo +echo "Vous pouvez maintenant creer la base d'utilisateurs avec ./create_user_db.sh" +echo "puis creer un departement avec ./create_dept.sh" +echo "Ou bien restaurer vos donnees a partir d'une ancienne installation a l'aide du script restore_scodoc_data.sh" +echo "(voir https://trac.lipn.univ-paris13.fr/projects/scodoc/wiki/MigrationDonneesScoDoc)" +echo + + +if [ "${cert_status}" != 0 ] +then + echo "Attention: le serveur Web Apache n'a pas de certificat." + echo "Il est probable qu'il ne fonctionne pas." + echo "Installez vos certificats ou generez provisoirement des certificats autosignes" + echo "avec la commande: /usr/sbin/make-ssl-cert /usr/share/ssl-cert/ssleay.cnf $ssl_dir/apache.pem" + echo +fi + diff --git a/config/postupgrade-db.py b/config/postupgrade-db.py new file mode 100755 index 00000000..b6d930d7 --- /dev/null +++ b/config/postupgrade-db.py @@ -0,0 +1,650 @@ +#!/opt/zope213/bin/python +# -*- coding: utf-8 -*- + +""" +ScoDoc post-upgrade script: databases housekeeping + +This script is runned by upgrade.sh after each SVN update. + +Runned as "www-data" with Zope shutted down and postgresql up. + + +Useful to update database schema (eg add new tables or columns to +existing scodoc instances). + +E. Viennet, june 2008, sept 2013 +""" + +from scodocutils import * + +for dept in get_depts(): + log('\nChecking database for dept %s' % dept) + cnx_string = None + try: + cnx_string = get_dept_cnx_str(dept) + cnx = psycopg2.connect( cnx_string ) + except: + log('\n*** Error: departement %s not upgraded ! ***\n' % dept) + log('connexion string was "%s"' % cnx_string) + traceback.print_exc() + continue + cnx.set_session(autocommit=False) + cursor = cnx.cursor() + # Apply upgrades: + + # SVN 564 -> 565 + # add resp_can_edit to notes_formsemestre: + check_field(cnx, 'notes_formsemestre', 'resp_can_edit', + ['alter table notes_formsemestre add column resp_can_edit int default 0', + 'update notes_formsemestre set resp_can_edit=0']) + + # SVN 580 -> 581 + # add resp_can_change_ens to notes_formsemestre: + check_field(cnx, 'notes_formsemestre', 'resp_can_change_ens', + ['alter table notes_formsemestre add column resp_can_change_ens int default 1', + 'update notes_formsemestre set resp_can_change_ens=1']) + + # Fix bug ayant empeche la creation de la table + check_table( cnx, 'admissions', [ + """CREATE TABLE admissions ( + adm_id text DEFAULT notes_newid_etud('ADM'::text) NOT NULL, + etudid text NOT NULL, + annee integer, + bac text, + specialite text, + annee_bac integer, + math real, + physique real, + anglais real, + francais real, + rang integer, -- dans les voeux du candidat (inconnu avec APB) + qualite real, + rapporteur text, + decision text, + score real, + commentaire text, + nomlycee text, + villelycee text, + codepostallycee text, + codelycee text, + debouche text, -- situation APRES etre passe par chez nous (texte libre) + type_admission text, -- 'APB', 'APC-PC', 'CEF', 'Direct', '?' (autre) + boursier_prec integer default NULL, -- etait boursier dans le cycle precedent (lycee) ? + classement integer default NULL, -- classement par le jury d'admission (1 à N), global (pas celui d'APB si il y a des groupes) + apb_groupe text, -- code du groupe APB + apb_classement_gr integer default NULL -- classement (1..Ngr) par le jury dans le groupe APB +) WITH OIDS; +"""] ) + + # SVN 651 + # Nouvelles donnees d'admission + check_field(cnx, 'admissions', 'codelycee', + ['alter table admissions add column codelycee text', + ]) + check_field(cnx, 'admissions', 'codepostallycee', + ['alter table admissions add column codepostallycee text', + ]) + + # New preferences system + check_field(cnx, 'sco_prefs', 'formsemestre_id', + ["alter table sco_prefs add column pref_id text DEFAULT notes_newid('PREF'::text) UNIQUE NOT NULL", + "update sco_prefs set pref_id=oid", + "alter table sco_prefs add column formsemestre_id text default NULL", + "alter table sco_prefs drop CONSTRAINT sco_prefs_pkey", + "alter table sco_prefs add unique( name, formsemestre_id)", + # copie anciennes prefs: + "insert into sco_prefs (name, value, formsemestre_id) select 'left_margin', left_margin, formsemestre_id from notes_formsemestre_pagebulletin", + "insert into sco_prefs (name, value, formsemestre_id) select 'top_margin', top_margin, formsemestre_id from notes_formsemestre_pagebulletin", + "insert into sco_prefs (name, value, formsemestre_id) select 'right_margin', right_margin, formsemestre_id from notes_formsemestre_pagebulletin", + "insert into sco_prefs (name, value, formsemestre_id) select 'bottom_margin', bottom_margin, formsemestre_id from notes_formsemestre_pagebulletin", + "insert into sco_prefs (name, value, formsemestre_id) select 'bul_title', title, formsemestre_id from notes_formsemestre_pagebulletin", + "insert into sco_prefs (name, value, formsemestre_id) select 'bul_intro_mail', intro_mail, formsemestre_id from notes_formsemestre_pagebulletin", + "drop table notes_formsemestre_pagebulletin", + # anciens champs de formsemestre: + "insert into sco_prefs (name, value, formsemestre_id) select 'bul_show_abs', gestion_absence, formsemestre_id from notes_formsemestre", + "alter table notes_formsemestre drop column gestion_absence", + + "insert into sco_prefs (name, value, formsemestre_id) select 'bul_show_decision', bul_show_decision, formsemestre_id from notes_formsemestre", + "alter table notes_formsemestre drop column bul_show_decision", + + "insert into sco_prefs (name, value, formsemestre_id) select 'bul_show_uevalid', bul_show_uevalid, formsemestre_id from notes_formsemestre", + "alter table notes_formsemestre drop column bul_show_uevalid", + + "insert into sco_prefs (name, value, formsemestre_id) select 'bul_show_codemodules', bul_show_codemodules, formsemestre_id from notes_formsemestre", + "alter table notes_formsemestre drop column bul_show_codemodules", + + "insert into sco_prefs (name, value, formsemestre_id) select 'bul_show_rangs', bul_show_rangs, formsemestre_id from notes_formsemestre", + "alter table notes_formsemestre drop column bul_show_rangs", + + "insert into sco_prefs (name, value, formsemestre_id) select 'bul_show_ue_rangs', bul_show_ue_rangs, formsemestre_id from notes_formsemestre", + "alter table notes_formsemestre drop column bul_show_ue_rangs", + + "insert into sco_prefs (name, value, formsemestre_id) select 'bul_show_mod_rangs', bul_show_mod_rangs, formsemestre_id from notes_formsemestre", + "alter table notes_formsemestre drop column bul_show_mod_rangs", + ]) + # fixed previous bug (misspelled pref) + cursor.execute("update sco_prefs set name = 'bul_show_codemodules' where name = 'bul_showcodemodules'") + + # billets d'absences + if not sequence_exists(cnx, 'notes_idgen_billets'): + log('creating sequence notes_idgen_billets') + cursor.execute('CREATE SEQUENCE notes_idgen_billets;') + + if not function_exists(cnx, 'notes_newid_billet'): + log('creating function notes_newid_billet') + cursor.execute("""CREATE FUNCTION notes_newid_billet( text ) returns text as ' + select $1 || to_char( nextval(''notes_idgen_billets''), ''FM999999999'' ) + as result; + ' language SQL;""") + + check_table( cnx, 'billet_absence', [ + """CREATE TABLE billet_absence ( + billet_id text DEFAULT notes_newid_billet('B'::text) NOT NULL, + etudid text NOT NULL, + abs_begin timestamp with time zone, + abs_end timestamp with time zone, + description text, -- "raison" de l'absence + etat integer default 0 -- 0 new, 1 processed +) WITH OIDS; +"""] ) + + # description absence + check_field(cnx, 'absences', 'description', + ['alter table absences add column description text' + ]) + check_field(cnx, 'absences', 'entry_date', + ['alter table absences add column entry_date timestamp with time zone DEFAULT now()' + ]) + check_field(cnx, 'billet_absence', 'entry_date', + ['alter table billet_absence add column entry_date timestamp with time zone DEFAULT now()' + ]) + # Nouvelles preferences pour bulletins PDF: migre bul_show_chiefDept + cursor.execute("update sco_prefs set name = 'bul_show_sig_right' where name = 'bul_show_chiefDept'") + # cursor.execute("insert into sco_prefs (name, value, formsemestre_id) select 'bul_show_sig_left', value, formsemestre_id from sco_prefs where name = 'bul_show_sig_right'") + # date et lieu naissance (pour IFAG Sofia) + check_field(cnx, 'identite', 'date_naissance', + ['alter table identite add column date_naissance date', + "update identite set date_naissance=to_date(to_char( annee_naissance, 'FM9999') || '-01-01', 'YYYY-MM-DD')", + 'alter table identite drop column annee_naissance' + ]) + check_field(cnx, 'identite', 'lieu_naissance', + ['alter table identite add column lieu_naissance text' + ]) + # justification billets: + check_field(cnx, 'billet_absence', 'justified', + [ 'alter table billet_absence add column justified integer default 0', + 'update billet_absence set justified=0' + ]) + + # ----------------------- New groups + # 1- Create new tables + check_table( cnx, 'partition', [ + """CREATE TABLE partition( + partition_id text default notes_newid2('P') PRIMARY KEY, + formsemestre_id text REFERENCES notes_formsemestre(formsemestre_id), + partition_name text, -- "TD", "TP", ... + compute_ranks integer default 1, -- calcul rang etudiants dans les groupes + numero SERIAL, -- ordre de presentation + UNIQUE(formsemestre_id,partition_name) +) WITH OIDS; +"""] ) + check_table( cnx, 'group_descr', [ + """CREATE TABLE group_descr ( + group_id text default notes_newid2('G') PRIMARY KEY, + partition_id text REFERENCES partition(partition_id), + group_name text, -- "A", "C2", ... + UNIQUE(partition_id, group_name) +) WITH OIDS; +"""] ) + check_table( cnx, 'group_membership', [ + """CREATE TABLE group_membership( + group_membership_id text default notes_newid2('GM') PRIMARY KEY, + etudid text REFERENCES identite(etudid), + group_id text REFERENCES group_descr(group_id), + UNIQUE(etudid, group_id) +) WITH OIDS; +"""] ) + + # 2- For each sem, create 1 to 4 partitions: all, TD (if any), TP, TA + # Here we have to deal with plain SQL, nasty... + if field_exists(cnx, 'notes_formsemestre_inscription', 'groupetd'): + # Some very old stduents didn't have addresses: it's now mandatory + cursor.execute("insert into adresse (etudid) select etudid from identite i except select etudid from adresse") + # + cursor.execute("SELECT formsemestre_id from notes_formsemestre") + formsemestre_ids = [ x[0] for x in cursor.fetchall() ] + for formsemestre_id in formsemestre_ids: + # create "all" partition (with empty name) + cursor.execute("INSERT into partition (formsemestre_id, compute_ranks) VALUES (%(formsemestre_id)s, 1)", {'formsemestre_id' : formsemestre_id } ) + cursor.execute("select partition_id from partition where oid=%(oid)s", { 'oid' : cursor.lastoid() }) + partition_id = cursor.fetchone()[0] + # create group "all" (without name) + cursor.execute("INSERT into group_descr (partition_id) VALUES (%(pid)s)", { 'pid' : partition_id } ) + cursor.execute("SELECT group_id from group_descr where oid=%(oid)s", { 'oid' : cursor.lastoid() }) + group_id = cursor.fetchone()[0] + # inscrit etudiants: + cursor.execute("INSERT into group_membership (etudid, group_id) SELECT etudid, %(group_id)s from notes_formsemestre_inscription where formsemestre_id=%(formsemestre_id)s", { 'group_id' : group_id, 'formsemestre_id' : formsemestre_id } ) + + # create TD, TP, TA + cursor.execute("SELECT distinct(groupetd) from notes_formsemestre_inscription where formsemestre_id=%(formsemestre_id)s", { 'formsemestre_id' : formsemestre_id } ) + groupetds = [ x[0] for x in cursor.fetchall() if x[0] ] + if len(groupetds) > 1 or (len(groupetds)==1 and groupetds[0] != 'A'): + # TD : create partition + cursor.execute("SELECT * from notes_formsemestre where formsemestre_id=%(formsemestre_id)s", { 'formsemestre_id' : formsemestre_id } ) + nomgroupetd = cursor.dictfetchone()['nomgroupetd'] + if not nomgroupetd: # pas de nom ??? on invente un nom stupide et unique + nomgroupetd = 'TD_'+str(time.time()).replace('.','')[-3:] + cursor.execute("INSERT into partition (formsemestre_id, partition_name) VALUES (%(formsemestre_id)s,%(nomgroupetd)s)", { 'formsemestre_id' : formsemestre_id, 'nomgroupetd' : nomgroupetd } ) + cursor.execute("select partition_id from partition where oid=%(oid)s", { 'oid' : cursor.lastoid() }) + partition_id = cursor.fetchone()[0] + # create groups + for groupetd in groupetds: + cursor.execute("INSERT into group_descr (partition_id, group_name) VALUES (%(pid)s, %(group_name)s)", { 'pid' : partition_id, 'group_name' : groupetd } ) + cursor.execute("SELECT group_id from group_descr where oid=%(oid)s", { 'oid' : cursor.lastoid() }) + group_id = cursor.fetchone()[0] + # inscrit les etudiants + cursor.execute("INSERT into group_membership (etudid, group_id) SELECT etudid, %(group_id)s from notes_formsemestre_inscription where formsemestre_id=%(formsemestre_id)s and groupetd=%(groupetd)s", { 'group_id' : group_id, 'formsemestre_id' : formsemestre_id, 'groupetd' : groupetd } ) + # TA + cursor.execute("SELECT distinct(groupeanglais) from notes_formsemestre_inscription where formsemestre_id=%(formsemestre_id)s", { 'formsemestre_id' : formsemestre_id } ) + groupetds = [ x[0] for x in cursor.fetchall() if x[0] ] + if len(groupetds) > 0: + # TA : create partition + cursor.execute("SELECT * from notes_formsemestre where formsemestre_id=%(formsemestre_id)s", { 'formsemestre_id' : formsemestre_id } ) + nomgroupetd = cursor.dictfetchone()['nomgroupeta'] + if not nomgroupetd: # pas de nom ??? on invente un nom stupide et unique + nomgroupetd = 'TA_'+str(time.time()).replace('.','')[-3:] + cursor.execute("INSERT into partition (formsemestre_id, partition_name) VALUES (%(formsemestre_id)s,%(nomgroupeta)s)", { 'formsemestre_id' : formsemestre_id, 'nomgroupeta' : nomgroupetd } ) + cursor.execute("select partition_id from partition where oid=%(oid)s", { 'oid' : cursor.lastoid() }) + partition_id = cursor.fetchone()[0] + # create groups + for groupetd in groupetds: + cursor.execute("INSERT into group_descr (partition_id, group_name) VALUES (%(pid)s, %(group_name)s)", { 'pid' : partition_id, 'group_name' : groupetd } ) + cursor.execute("SELECT group_id from group_descr where oid=%(oid)s", { 'oid' : cursor.lastoid() }) + group_id = cursor.fetchone()[0] + # inscrit les etudiants + cursor.execute("INSERT into group_membership (etudid, group_id) SELECT etudid, %(group_id)s from notes_formsemestre_inscription where formsemestre_id=%(formsemestre_id)s and groupeanglais=%(groupetd)s", { 'group_id' : group_id, 'formsemestre_id' : formsemestre_id, 'groupetd' : groupetd } ) + + # TP + cursor.execute("SELECT distinct(groupetp) from notes_formsemestre_inscription where formsemestre_id=%(formsemestre_id)s", { 'formsemestre_id' : formsemestre_id } ) + groupetds = [ x[0] for x in cursor.fetchall() if x[0] ] + if len(groupetds) > 0: + # TP : create partition + cursor.execute("SELECT * from notes_formsemestre where formsemestre_id=%(formsemestre_id)s", { 'formsemestre_id' : formsemestre_id } ) + nomgroupetd = cursor.dictfetchone()['nomgroupetp'] + if not nomgroupetd: # pas de nom ??? on invente un nom stupide et unique + nomgroupetd = 'TP_'+str(time.time()).replace('.','')[-3:] + cursor.execute("INSERT into partition (formsemestre_id, partition_name) VALUES (%(formsemestre_id)s,%(nomgroupeta)s)", { 'formsemestre_id' : formsemestre_id, 'nomgroupeta' : nomgroupetd } ) + cursor.execute("select partition_id from partition where oid=%(oid)s", { 'oid' : cursor.lastoid() }) + partition_id = cursor.fetchone()[0] + # create groups + for groupetd in groupetds: + cursor.execute("INSERT into group_descr (partition_id, group_name) VALUES (%(pid)s, %(group_name)s)", { 'pid' : partition_id, 'group_name' : groupetd } ) + cursor.execute("SELECT group_id from group_descr where oid=%(oid)s", { 'oid' : cursor.lastoid() }) + group_id = cursor.fetchone()[0] + # inscrit les etudiants + cursor.execute("INSERT into group_membership (etudid, group_id) SELECT etudid, %(group_id)s from notes_formsemestre_inscription where formsemestre_id=%(formsemestre_id)s and groupetp=%(groupetd)s", { 'group_id' : group_id, 'formsemestre_id' : formsemestre_id, 'groupetd' : groupetd } ) + + # 3- Suppress obsolete fields + cursor.execute( """alter table notes_formsemestre drop column nomgroupetd""" ) + cursor.execute( """alter table notes_formsemestre drop column nomgroupetp""" ) + cursor.execute( """alter table notes_formsemestre drop column nomgroupeta""" ) + + cursor.execute( """alter table notes_formsemestre_inscription drop column groupetd""" ) + cursor.execute( """alter table notes_formsemestre_inscription drop column groupetp""" ) + cursor.execute( """alter table notes_formsemestre_inscription drop column groupeanglais""" ) + # ----------------------- /New groups + + # Add moy_ue to validations: + check_field(cnx, 'scolar_formsemestre_validation', 'moy_ue', + ['alter table scolar_formsemestre_validation add column moy_ue real', + ]) + # Add photo_filename + check_field(cnx, 'identite', 'photo_filename', + ['alter table identite add column photo_filename text', + ]) + # Add module's ECTS + check_field(cnx, 'notes_modules', 'ects', + ['alter table notes_modules add column ects real', + ]) + # Add "statut" to identite (default to NULL) + check_field(cnx, 'identite', 'statut', + ['alter table identite add column statut text', + ]) + # Add user-defined expressions + check_field(cnx, 'notes_moduleimpl', 'computation_expr', + ['alter table notes_moduleimpl add column computation_expr text']) + # Add semestre_id to scolar_formsemestre_validation + check_field(cnx, 'scolar_formsemestre_validation', 'semestre_id', + ['alter table scolar_formsemestre_validation add column semestre_id int']) + + # Add + check_table( cnx, 'absences_notifications', [ """ + CREATE TABLE absences_notifications ( + etudid text NOT NULL, + notification_date timestamp with time zone DEFAULT now(), + email text NOT NULL, + nbabs integer, + nbabsjust integer + ) WITH OIDS; + """] ) + # rename old preference "send_mail_absence_to_chef" + cursor.execute("update sco_prefs set name = 'abs_notify_chief' where name = 'send_mail_absence_to_chef'") + + check_table( cnx, 'notes_formsemestre_ue_computation_expr', [ """ + CREATE TABLE notes_formsemestre_ue_computation_expr ( + notes_formsemestre_ue_computation_expr_id text default notes_newid('UEXPR') PRIMARY KEY, + formsemestre_id text REFERENCES notes_formsemestre(formsemestre_id), + ue_id text REFERENCES notes_ue(ue_id), + computation_expr text, -- formule de calcul moyenne + UNIQUE(formsemestre_id, ue_id) + ) WITH OIDS; + """] ) + + + # add moduleimpl_id to absences: + check_field(cnx, 'absences', 'moduleimpl_id', + ['alter table absences add column moduleimpl_id text']) + + # add type_parcours + check_field(cnx, 'notes_formations', 'type_parcours', + ['alter table notes_formations add column type_parcours int DEFAULT 0', + 'update notes_formations set type_parcours=0 where type_parcours is NULL' + ]) + + # add etape_apo2 + check_field(cnx, 'notes_formsemestre', 'etape_apo2', + ['alter table notes_formsemestre add column etape_apo2 text']) + # add etape_apo3 + check_field(cnx, 'notes_formsemestre', 'etape_apo3', + ['alter table notes_formsemestre add column etape_apo3 text']) + # add etape_apo4 + check_field(cnx, 'notes_formsemestre', 'etape_apo4', + ['alter table notes_formsemestre add column etape_apo4 text']) + # add publish_incomplete + check_field(cnx, 'notes_evaluation', 'publish_incomplete', + ['alter table notes_evaluation add column publish_incomplete int DEFAULT 0', + 'update notes_evaluation set publish_incomplete=0 where publish_incomplete is NULL' + ]) + + # add ens_can_create_eval to notes_formsemestre: + check_field(cnx, 'notes_formsemestre', 'ens_can_edit_eval', + ['alter table notes_formsemestre add column ens_can_edit_eval int default 0', + 'update notes_formsemestre set ens_can_edit_eval=0']) + + # add evaluation_type + check_field(cnx, 'notes_evaluation', 'evaluation_type', + ['alter table notes_evaluation add column evaluation_type int DEFAULT 0', + 'update notes_evaluation set evaluation_type=0 where evaluation_type is NULL' + ]) + + # add partition rank on bulletins + check_field(cnx, 'partition', 'bul_show_rank', + ['alter table partition add column bul_show_rank int DEFAULT 0', + 'update partition set bul_show_rank=0 where bul_show_rank is NULL']) + # add formsemestre to abs notifications + check_field(cnx, 'absences_notifications', 'formsemestre_id', + ['alter table absences_notifications add column formsemestre_id text DEFAULT NULL', + ]) + # Add "debouche" to admission + check_field(cnx, 'admissions', 'debouche', + ['alter table admissions add column debouche text DEFAULT NULL', + # et en profite pour corrige le From par defaut des mails: + "update sco_prefs set value='noreply@univ-paris13.fr' where name='email_from_addr' and value='noreply'" + ]) + # Increase semestre indices + for i in range(5,9): + cursor.execute("SELECT * from notes_semestres where semestre_id=%(i)s", { 'i' : i } ) + r = cursor.fetchall() + if not r: + log("adding semestre_id %s" % i) + cursor.execute("INSERT INTO notes_semestres (semestre_id) VALUES (%(i)s)", { 'i' : i } ) + # ECTS associes aux UE: + check_field(cnx, 'notes_ue', 'ects', + ['alter table notes_ue add column ects float DEFAULT NULL', + ]) + # Numeros des evaluations: + check_field(cnx, 'notes_evaluation', 'numero', + ['alter table notes_evaluation add column numero int DEFAULT 0', + ]) + # add nom_usuel to identite + check_field(cnx, 'identite', 'nom_usuel', + ['alter table identite add column nom_usuel text DEFAULT NULL', + ]) + # add type_admission + check_field(cnx, 'admissions', 'type_admission', + ['alter table admissions add column type_admission text DEFAULT NULL', + ]) + check_field(cnx, 'admissions', 'boursier_prec', + ['alter table admissions add column boursier_prec integer default NULL', + ]) + # add modalites formation + check_table( cnx, 'notes_form_modalites', [ + """CREATE TABLE notes_form_modalites ( + form_modalite_id text default notes_newid('Md') PRIMARY KEY, + modalite text, -- la clef dans notes_formsemestre + titre text, -- le nom complet de la modalite pour les documents scodoc + numero SERIAL -- integer, ordre de presentation + );""", + """INSERT INTO notes_form_modalites (modalite, titre) VALUES ('', 'Autres formations');""", + """INSERT INTO notes_form_modalites (modalite, titre) VALUES ('FI', 'Formation Initiale');""", + """INSERT INTO notes_form_modalites (modalite, titre) VALUES ('FC', 'Formation Continue');""", + """INSERT INTO notes_form_modalites (modalite, titre) VALUES ('FAP', 'Apprentissage');""", + """INSERT INTO notes_form_modalites (modalite, titre) VALUES ('DEC', 'Formation Décalées');""", + """INSERT INTO notes_form_modalites (modalite, titre) VALUES ('LIC', 'Licence');""" + ] ) + # Add code_specialite + check_field( cnx, 'notes_formations', 'code_specialite', + [ 'alter table notes_formations add column code_specialite text default NULL', + ]) + # Fix modules without codes + cursor.execute("UPDATE notes_modules SET code = 'M_' || coalesce(upper(substring(titre from 1 for 2)), '') || '_' || coalesce(semestre_id,'0') where code is NULL;"); + + # Add ue.is_external + check_field( cnx, 'notes_ue', 'is_external', + [ 'alter table notes_ue add column is_external integer default 0', + ]) + check_field( cnx, 'scolar_formsemestre_validation', 'is_external', + [ 'alter table scolar_formsemestre_validation add column is_external integer default 0', + ]) + # Add codes apogee + check_field( cnx, 'notes_ue', 'code_apogee', + [ 'alter table notes_ue add column code_apogee text UNIQUE', + ]) + check_field( cnx, 'notes_modules', 'code_apogee', + [ 'alter table notes_modules add column code_apogee text UNIQUE', + ]) + check_field( cnx, 'notes_formsemestre', 'elt_sem_apo', + [ 'alter table notes_formsemestre add column elt_sem_apo text', + ]) + check_field( cnx, 'notes_formsemestre', 'elt_annee_apo', + [ 'alter table notes_formsemestre add column elt_annee_apo text', + ]) + # Classement admission + check_field(cnx, 'admissions', 'classement', + ['alter table admissions add column classement integer default NULL', + ]) + # Supprime contraintes erronées sur codes Apogee: + if list_constraint( cnx, constraint_name='notes_ue_code_apogee_key' ): + log('dropping buggy constraint on notes_ue_code_apogee') + cursor.execute("alter table notes_ue drop CONSTRAINT notes_ue_code_apogee_key;") + if list_constraint( cnx, constraint_name='notes_modules_code_apogee_key' ): + log('dropping buggy constraint on notes_modules_code_apogee') + cursor.execute("alter table notes_modules drop CONSTRAINT notes_modules_code_apogee_key;") + + # SemSet: + check_table( cnx, 'notes_semset', [ + """CREATE TABLE notes_semset ( + semset_id text default notes_newid('NSS') PRIMARY KEY, + title text, + annee_scolaire int default NULL, -- 2016 + sem_id int default NULL -- 0, 1, 2 + ) WITH OIDS;""", ] ) + check_field(cnx, 'notes_semset', 'annee_scolaire', + ['alter table notes_semset add column annee_scolaire integer default NULL', + ]) + check_table( cnx, 'notes_semset_formsemestre', [ + """CREATE TABLE notes_semset_formsemestre ( + formsemestre_id text REFERENCES notes_formsemestre(formsemestre_id) ON DELETE CASCADE, + semset_id text REFERENCES notes_semset (semset_id) ON DELETE CASCADE, + PRIMARY KEY (formsemestre_id, semset_id) + ) WITH OIDS;""", ] ) + + # ModuleTags + check_table( cnx, 'notes_tags', [ + """CREATE TABLE notes_tags ( + tag_id text default notes_newid('TAG') PRIMARY KEY, + title text UNIQUE NOT NULL + ) WITH OIDS;""", ] ) + check_table( cnx, 'notes_modules_tags', [ + """CREATE TABLE notes_modules_tags ( + tag_id text REFERENCES notes_tags(tag_id) ON DELETE CASCADE, + module_id text REFERENCES notes_modules(module_id) ON DELETE CASCADE, + PRIMARY KEY (tag_id, module_id) + ) WITH OIDS;""", ] ) + + # add show_in_lists on partition + check_field(cnx, 'partition', 'show_in_lists', + ['alter table partition add column show_in_lists integer DEFAULT 1', + 'update partition set show_in_lists=1 where show_in_lists is NULL']) + # table codes etapes apogee semestre + check_table( cnx, 'notes_formsemestre_etapes', [ + """CREATE TABLE notes_formsemestre_etapes ( + formsemestre_id text REFERENCES notes_formsemestre(formsemestre_id) ON DELETE CASCADE, + etape_apo text NOT NULL +) WITH OIDS;""", + """INSERT into notes_formsemestre_etapes (formsemestre_id, etape_apo) SELECT formsemestre_id, etape_apo FROM notes_formsemestre WHERE etape_apo is not NULL;""", + """INSERT into notes_formsemestre_etapes (formsemestre_id, etape_apo) SELECT formsemestre_id, etape_apo2 FROM notes_formsemestre WHERE etape_apo2 is not NULL;""", + """INSERT into notes_formsemestre_etapes (formsemestre_id, etape_apo) SELECT formsemestre_id, etape_apo3 FROM notes_formsemestre WHERE etape_apo3 is not NULL;""", + """INSERT into notes_formsemestre_etapes (formsemestre_id, etape_apo) SELECT formsemestre_id, etape_apo4 FROM notes_formsemestre WHERE etape_apo4 is not NULL;""", + """ALTER table notes_formsemestre DROP column etape_apo;""", + """ALTER table notes_formsemestre DROP column etape_apo2;""", + """ALTER table notes_formsemestre DROP column etape_apo3;""", + """ALTER table notes_formsemestre DROP column etape_apo4;""", + ] ) + # Admission APB: groupe et classement dans groupe + check_field(cnx, 'admissions', 'apb_groupe', + ['alter table admissions add column apb_groupe text default NULL', + ]) + check_field(cnx, 'admissions', 'apb_classement_gr', + ['alter table admissions add column apb_classement_gr integer default NULL', + ]) + # Adresse mail perso + check_field(cnx, 'adresse', 'emailperso', + ['alter table adresse add column emailperso text', + ]) + # Ajout de modalites supplementaires + cursor.execute("SELECT modalite from notes_form_modalites where modalite= 'CP'") + if not len(cursor.fetchall()): + log('adding modalite "CP"') + cursor.execute("INSERT INTO notes_form_modalites (modalite, titre) VALUES ('CP', 'Contrats de Professionnalisation');") + # Un index oublié sur notes_notes: + if 'notes_notes_evaluation_id_idx' not in list_table_index(cnx, 'notes_notes'): + log('creating index on notes_notes') + cursor.execute("CREATE INDEX notes_notes_evaluation_id_idx ON notes_notes (evaluation_id)") + + # boursier (ajout API nov 2017) + check_field(cnx, 'identite', 'boursier', + ['alter table identite add column boursier text' + ]) + # Suivi des anciens etudiants (debouche) + # cree table suivi et recopie ancien champs debouche de la table admission + check_table( cnx, 'itemsuivi', [ + """CREATE TABLE itemsuivi ( + itemsuivi_id text DEFAULT notes_newid('SUI'::text) PRIMARY KEY, + etudid text NOT NULL, + item_date date DEFAULT now(), + situation text + ) WITH OIDS;""", + + """INSERT INTO itemsuivi (etudid, situation) + SELECT etudid, debouche FROM admissions WHERE debouche is not null; + """ + ] ) + check_table( cnx, 'itemsuivi_tags', [ + """CREATE TABLE itemsuivi_tags ( + tag_id text default notes_newid('TG') PRIMARY KEY, + title text UNIQUE NOT NULL + ) WITH OIDS;""", + ] ) + check_table( cnx, 'itemsuivi_tags_assoc', [ + """CREATE TABLE itemsuivi_tags_assoc ( + tag_id text REFERENCES itemsuivi_tags(tag_id) ON DELETE CASCADE, + itemsuivi_id text REFERENCES itemsuivi(itemsuivi_id) ON DELETE CASCADE, + PRIMARY KEY (tag_id, itemsuivi_id) + ) WITH OIDS;""", + ] ) + + # Types de modules (pour malus) + check_field(cnx, 'notes_modules', 'module_type', + ['alter table notes_modules add column module_type int', + ]) + + # Responsables de semestres + check_table( cnx, 'notes_formsemestre_responsables', [ + """CREATE TABLE notes_formsemestre_responsables ( + formsemestre_id text REFERENCES notes_formsemestre(formsemestre_id) ON DELETE CASCADE, + responsable_id text NOT NULL, + UNIQUE(formsemestre_id, responsable_id) + ) WITH OIDS;""", + """INSERT into notes_formsemestre_responsables (formsemestre_id, responsable_id) SELECT formsemestre_id, responsable_id FROM notes_formsemestre WHERE responsable_id is not NULL;""", + """ALTER table notes_formsemestre DROP column responsable_id;""", + ]) + # Fonction pour anonymisation: + if not function_exists(cnx, 'random_text_md5'): + log('creating function random_text_md5') + # inspirée par https://www.simononsoftware.com/random-string-in-postgresql/ + cursor.execute("""CREATE FUNCTION random_text_md5( integer ) returns text + LANGUAGE SQL + AS $$ + select upper( substring( (SELECT string_agg(md5(random()::TEXT), '') + FROM generate_series( + 1, + CEIL($1 / 32.)::integer) + ), 1, $1) ); + $$;""") + # departement naissance (ajout fev 2020) + check_field( + cnx, 'identite', 'dept_naissance', + ['alter table identite add column dept_naissance text']) + # Modalite semestres exterieurs + cursor.execute("SELECT modalite from notes_form_modalites where modalite= 'EXT'") + if not len(cursor.fetchall()): + log('adding modalite "EXT"') + cursor.execute("INSERT INTO notes_form_modalites (modalite, titre) VALUES ('EXT', 'Extérieur');") + # Coefficients d'UE + check_field(cnx, 'notes_ue', 'coefficient', + ["alter table notes_ue add column coefficient float", + # Initialise les coefficients égaux aux ECTS: + "update notes_ue set coefficient=ects", + # Force pref locale sur semestres existants: + """INSERT INTO sco_prefs (name, value, formsemestre_id) + SELECT DISTINCT 'use_ue_coefs', '0', formsemestre_id FROM notes_formsemestre + ON CONFLICT DO NOTHING + """ + ]) + # Add here actions to performs after upgrades: + cnx.commit() + cnx.close() + + +# Base utilisateurs: +log('\nChecking users database') +cnx = psycopg2.connect( get_users_cnx_str() ) +cursor = cnx.cursor() +check_field(cnx, 'sco_users', 'passwd_temp', + ['alter table sco_users add column passwd_temp int default 0', + 'update sco_users set passwd_temp=0' ]) +check_field(cnx, 'sco_users', 'status', + ["alter table sco_users add column status text default NULL"]) +check_field(cnx, 'sco_users', 'date_expiration', + ["alter table sco_users add column date_expiration date", + "update sco_users set status=NULL where status=''" # fix a bug in previous update... + ]) +check_field(cnx, 'sco_users', 'login_edt', + ["alter table sco_users add column login_edt text default NULL", + ]) +cnx.commit() +cnx.close() + +# The end. +sys.exit(0) diff --git a/config/postupgrade.py b/config/postupgrade.py new file mode 100755 index 00000000..1137357a --- /dev/null +++ b/config/postupgrade.py @@ -0,0 +1,60 @@ +#!/opt/zope213/bin/python + +""" +ScoDoc post-upgrade script. + +This script is launched by upgrade.sh after each SVN update. + +Run as "root" with Zope shutted down and postgresql up, +_before_ upgrading the database. + +E. Viennet, June 2008 +Mar 2017: suppress upgrade of very old Apache configs +Aug 2020: move photos to .../var/scodoc/ +""" +import os +import sys +import glob +import shutil +from scodocutils import log, SCODOC_DIR, SCODOC_VAR_DIR, SCODOC_LOGOS_DIR + +if os.getuid() != 0: + log('postupgrade.py: must be run as root') + sys.exit(1) + +# --- +# Migrate photos (2020-08-16, svn 1908) +old_photo_dir = os.path.join(SCODOC_DIR, "static", "photos") +photo_dirs = glob.glob( old_photo_dir + "/F*") +if photo_dirs: + log("Moving photos to new directory...") + shutil.move(old_photo_dir, SCODOC_VAR_DIR) + +# Migrate depts (2020-08-17, svn 1909) + +old_depts_dir = os.path.join(SCODOC_DIR, "config", "depts") +cfg_files = glob.glob( old_depts_dir + "/*.cfg") +depts_dir = os.path.join(SCODOC_VAR_DIR, "config/depts/") +for cfg in cfg_files: + log("Moving %s to new directory..." % cfg) + shutil.move(cfg, depts_dir) + +# Move logos +if not os.path.exists(SCODOC_LOGOS_DIR): + old_logos = os.path.join(SCODOC_DIR,"logos") + if os.path.exists(old_logos): + log("Moving logos to new directory...") + dest = os.path.normpath(os.path.join(SCODOC_LOGOS_DIR, "..")) + shutil.move(old_logos, dest) + else: + log("Warning: logos directory is missing (%s)" % SCODOC_LOGOS_DIR) + +# Move dept-specific logos +for d in glob.glob( SCODOC_DIR + "/logos_*" ): + log("Moving %s to %s" % (d, SCODOC_LOGOS_DIR)) + shutil.move(d, SCODOC_LOGOS_DIR) + +# Continue here... + +# --- +sys.exit(0) diff --git a/config/psql_restore_databases.sh b/config/psql_restore_databases.sh new file mode 100755 index 00000000..9eb260a5 --- /dev/null +++ b/config/psql_restore_databases.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# aux script called by restore_scodoc_data.sh as "postgres" user +# DO NOT CALL DIRECTLY + +PG_DUMPFILE=$1 + +# Check locale of installation. If invalid, reinitialize all system + +is_latin1=$(psql -l | grep postgres | grep iso88591 | wc -l) +if [ $is_latin1 -gt 1 ] +then + echo "Recreating postgres cluster using UTF-8" + + pg_dropcluster --stop 9.1 main + + pg_createcluster --locale en_US.UTF-8 --start 9.1 main +fi + + +# Drop all current ScoDoc databases, if any: +for f in $(psql -l --no-align --field-separator . | grep SCO | cut -f 1 -d.); do + echo dropping $f + dropdb $f +done +echo "Restoring postgres data..." +psql -f "$PG_DUMPFILE" postgres + diff --git a/config/restore_scodoc_data.sh b/config/restore_scodoc_data.sh new file mode 100755 index 00000000..aed3a1ea --- /dev/null +++ b/config/restore_scodoc_data.sh @@ -0,0 +1,145 @@ +#!/bin/bash + +# +# ScoDoc: restore data (saved by save_scodoc_data) into current install +# +# Utile pour migrer ScoDoc d'un serveur a un autre +# A executer en tant que root sur le nouveau serveur +# +# E. Viennet, Sept 2011, Nov 2013, Mar 2017, Aug 2020 +# + + +INSTANCE_DIR=/opt/scodoc/ +SCODOC_DIR="${INSTANCE_DIR}/Products/ScoDoc" +SCODOC_VAR_DIR="${INSTANCE_DIR}/var/scodoc" + +source utils.sh +check_uid_root $0 + +# Safety check +echo "Ce script va remplacer les donnees de votre installation ScoDoc par celles" +echo "enregistrees dans le fichier fourni." +echo "Ce fichier doit avoir ete cree par le script save_scodoc_data.sh, sur une autre machine." +echo +echo "Attention: TOUTES LES DONNEES DE CE SERVEUR SERONT REMPLACEES !" +echo "Notamment, tous les utilisateurs et departements existants seront effaces !" +echo +echo "TOUTES LES BASES POSTGRESQL SERONT EFFACEES !!!" +echo +echo -n "Voulez vous poursuivre cette operation ? (y/n) [n]" +read ans +if [ ! "$(norm_ans "$ans")" = 'Y' ] +then + echo "Annulation" + exit 1 +fi + +# Usage +if [ ! $# -eq 1 ] +then + echo "Usage: $0 directory_or_archive" + exit 1 +fi + +SRC=$1 + +if [ "${SRC:0:1}" != "/" ] +then + echo "Usage: $0 directory_or_archive" + echo "Erreur: utiliser un chemin absolu (commencant par /)" + exit 1 +fi + +# Source directory +if [ "${SRC##*.}" = 'tgz' ] +then + echo "Opening tgz archive..." + tmp=$(mktemp -d) + chmod a+rx "$tmp" + cd "$tmp" + tar xfz "$SRC" + SRC=$(ls -1d "$tmp"/*) + IS_TMP=1 + # If source is a tgz, can use mv + COPY="mv" +else + IS_TMP=0 + # If source is a directory, does not modify its content + COPY="cp -rp" +fi + +echo "Source is $SRC" +echo "Stopping ScoDoc..." +/etc/init.d/scodoc stop + +# Erase all postgres databases and load data +chmod a+rx "$SRC" +chmod a+r "$SRC"/scodoc.dump.txt +PG_DUMPFILE="$SRC/scodoc.dump.txt" + +su -c "$SCODOC_DIR/config/psql_restore_databases.sh $PG_DUMPFILE" postgres + +# +echo Copying data files... + +rm -rf "$INSTANCE_DIR/var" +$COPY "$SRC/var" "$INSTANCE_DIR" + +if [ ! -e "${SCODOC_VAR_DIR}/config/" ] +then + mkdir "${SCODOC_VAR_DIR}/config/" + chown www-data.www-data "${SCODOC_VAR_DIR}/config/" + chmod 775 "${SCODOC_VAR_DIR}/config/" +fi + +rm -rf "$SCODOC_DIR/config/depts" +if [ -e "$SRC/depts" ] +then + # legacy depts => move them to var + $COPY "$SRC/depts" "${SCODOC_VAR_DIR}/config/" +fi + +rm -rf "$SCODOC_DIR/static/photos" +if [ -e "$SRC/photos" ] +then + # legacy photos (in /static/photos) => move them to var + $COPY "$SRC/photos" "${SCODOC_VAR_DIR}/" +fi + +rm -rf "$SCODOC_DIR/logos" +$COPY "$SRC/logos" "$SCODOC_DIR/" + +mv "$SCODOC_DIR/config/scodoc_config.py" "$SCODOC_DIR/config/scodoc_config.py.$(date +%Y%m%d-%H%M%S)" +$COPY "$SRC/scodoc_config.py" "$SCODOC_DIR/config/" +# Verifie le codage de ce fichier: +if [ -z "$(file $SCODOC_DIR/config/scodoc_config.py | grep -i UTF-8)" ] +then + mv "$SCODOC_DIR/config/scodoc_config.py" "$SCODOC_DIR/config/scodoc_config.py.orig" + iconv -f iso8859-15 -t utf-8 "$SCODOC_DIR/config/scodoc_config.py.orig" > "$SCODOC_DIR/config/scodoc_config.py" +fi + +rm -rf "$INSTANCE_DIR/log" +$COPY "$SRC/log" "$INSTANCE_DIR/" + +# Fix file ownership and access rights +chown -R www-data.root "$INSTANCE_DIR/log" +chown -R www-data.root "$INSTANCE_DIR/var" +chmod 775 "$INSTANCE_DIR./log" "$INSTANCE_DIR./var" +chown -R www-data.root "$SCODOC_DIR" +chmod -R 775 "$SCODOC_DIR" + +# Remove tmp directory +if [ $IS_TMP = "1" ] +then + rm -rf $tmp +fi + +# Mise a jour BD ScoDoc +cd $SCODOC_DIR/config +./upgrade.sh + +# +echo +echo "Ok. Run \"/etc/init.d/scodoc start\" to start ScoDoc." + diff --git a/config/save_scodoc_data.sh b/config/save_scodoc_data.sh new file mode 100755 index 00000000..09cc0644 --- /dev/null +++ b/config/save_scodoc_data.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +# +# ScoDoc: save all user data (database, configs, images, archives...) in separate directory +# +# Utile pour migrer ScoDoc d'un serveur a un autre +# Executer en tant que root sur le serveur d'origine +# +# E. Viennet, Sept 2011, Aug 2020 +# + +# Destination directory +if [ ! $# -eq 1 ] +then + echo "Usage: $0 destination_directory" + exit 1 +fi +DEST=$1 +# remove trailing slashs if needed: +shopt -s extglob +DEST="${DEST%%+(/)}" + +if [ ! -e "$DEST" ] +then + echo Creating directory "$DEST" + mkdir "$DEST" +else + echo "Error: Directory " "$DEST" " exists" + echo "remove it or specify another destination !" + exit 2 +fi + +INSTANCE_DIR=/opt/scodoc +SCODOC_DIR="$INSTANCE_DIR/Products/ScoDoc" + +source utils.sh +check_uid_root $0 + +echo "Stopping ScoDoc..." +/etc/init.d/scodoc stop + +# Dump all postgres databases +echo "Dumping SQL database..." +chown postgres "$DEST" +su -c "pg_dumpall > \"$DEST\"/scodoc.dump.txt" postgres +if [ ! $? -eq 0 ] +then + echo "Error dumping postgresql database\nPlease check that SQL server is running\nAborting." + exit 1 +fi +chown root "$DEST" + +# Zope DB and ScoDoc archives: +echo "Copying var/ ..." +cp -rp "$INSTANCE_DIR/var" "$DEST" + +# Depts db config (now in .../var) +shopt -s nullglob +if [ ! -z "$(echo ${SCODOC_DIR}/config/depts/*.cfg)" ] +then + echo "Copying legacy depts configs..." + cp -rp "$SCODOC_DIR/config/depts" "$DEST" +fi + + + +# Photos des etudiants (now in .../var) +if [ -e "$SCODOC_DIR/static/photos" ] +then + echo "Copying legacy photos..." + cp -rp "$SCODOC_DIR/static/photos" "$DEST" +fi + +echo "Copying logos..." +cp -rp "$SCODOC_DIR/logos" "$DEST" + +echo "Copying configuration file..." +cp -p "$SCODOC_DIR/config/scodoc_config.py" "$DEST" + +echo "Copying server logs..." +cp -rp "$INSTANCE_DIR/log" "$DEST" + + +# --- Archive all files in a tarball to ease transfer +echo +echo "Archiving backup files in a $DEST.tgz..." +base=$(basename "$DEST") +(cd "$DEST"/..; tar cfz "$DEST".tgz "$base") + +echo "Done (you can copy " "$DEST"".tgz to destination machine)." diff --git a/config/scodoc_config.py b/config/scodoc_config.py new file mode 100644 index 00000000..32d3fa21 --- /dev/null +++ b/config/scodoc_config.py @@ -0,0 +1,131 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +# +# Configuration globale de ScoDoc (version juin 2009) +# + +# La plupart des réglages sont stoqués en base de donnée et accessibles via le web +# (pages de paramètres ou préférences). +# Les valeurs indiquées ici sont les valeurs initiales que prendront +# les paramètres lors de la création d'un nouveau département, +# elles ne sont plus utilisées ensuite. + +# Nota: il y a aussi des réglages dans sco_utils.py, mais ils nécessitent +# souvent de comprendre le code qui les utilise pour ne pas faire d'erreur: attention. + + +class CFG : + pass + +CONFIG = CFG() + +CONFIG.always_require_ine = 0 # set to 1 if you want to require INE + +# The base URL, use only if you are behind a proxy +# eg "https://scodoc.example.net/ScoDoc" +CONFIG.ABSOLUTE_URL = "" + +# +# ------------- Documents PDF ------------- +# +CONFIG.SCOLAR_FONT = 'Helvetica' +CONFIG.SCOLAR_FONT_SIZE = 10 +CONFIG.SCOLAR_FONT_SIZE_FOOT = 6 + +# Pour pieds de pages Procès verbaux: +# (markup leger reportlab supporté, par ex. blah blah) +CONFIG.INSTITUTION_NAME="Institut Universitaire de Technologie - Université Paris 13" +CONFIG.INSTITUTION_ADDRESS="Web www.iutv.univ-paris13.fr - 99 avenue Jean-Baptiste Clément - F 93430 Villetaneuse" + +CONFIG.INSTITUTION_CITY="Villetaneuse" + + +# Taille du l'image logo: largeur/hauteur (ne pas oublier le . !!!) +CONFIG.LOGO_FOOTER_ASPECT = 326/96. # W/H XXX provisoire: utilisera PIL pour connaitre la taille de l'image +CONFIG.LOGO_FOOTER_HEIGHT = 10 # taille dans le document en millimetres + +CONFIG.LOGO_HEADER_ASPECT = 549 / 346. # XXX logo IUTV +CONFIG.LOGO_HEADER_HEIGHT = 28 # taille verticale dans le document en millimetres + +# Pied de page PDF : un format Python, %(xxx)s est remplacé par la variable xxx. +# Les variables définies sont: +# day : Day of the month as a decimal number [01,31] +# month : Month as a decimal number [01,12]. +# year : Year without century as a decimal number [00,99]. +# Year : Year with century as a decimal number. +# hour : Hour (24-hour clock) as a decimal number [00,23]. +# minute: Minute as a decimal number [00,59]. +# +# server_url: URL du serveur ScoDoc +# scodoc_name: le nom du logiciel (ScoDoc actuellement, voir VERSION.py) + +CONFIG.DEFAULT_PDF_FOOTER_TEMPLATE = "Edité par %(scodoc_name)s le %(day)s/%(month)s/%(year)s à %(hour)sh%(minute)s sur %(server_url)s" + + + +# +# ------------- Calcul bonus modules optionnels (sport, culture...) ------------- +# +from bonus_sport import * + +CONFIG.compute_bonus = bonus_iutv +# Mettre "bonus_demo" pour logguer des informations utiles au developpement... + +# +# ------------- Capitalisation des UEs ------------- +# Deux écoles: +# - règle "DUT": capitalisation des UE obtenues avec moyenne UE >= 10 ET de toutes les UE +# des semestres validés (ADM, ADC, AJ). (conforme à l'arrêté d'août 2005) +# +# - règle "LMD": capitalisation uniquement des UE avec moy. > 10 + +CONFIG.CAPITALIZE_ALL_UES = True # si vrai, capitalise toutes les UE des semestres validés (règle "LMD"). + + +# +# ----------------------------------------------------- +# +# -------------- Personnalisation des pages +# +# ----------------------------------------------------- +# Nom (chemin complet) d'un fichier .html à inclure juste après le +# le des pages ScoDoc +CONFIG.CUSTOM_HTML_HEADER = '' + +# Fichier html a inclure en fin des pages (juste avant le ) +CONFIG.CUSTOM_HTML_FOOTER = '' + +# Fichier .html à inclure dans la pages connexion/déconnexion (accueil) +# si on veut que ce soit différent (par défaut la même chose) +CONFIG.CUSTOM_HTML_HEADER_CNX = CONFIG.CUSTOM_HTML_HEADER +CONFIG.CUSTOM_HTML_FOOTER_CNX = CONFIG.CUSTOM_HTML_FOOTER + + +# ----------------------------------------------------- +# +# -------------- Noms de Lycées +# +# ----------------------------------------------------- + +# Fichier de correspondance codelycee -> noms +# (chemin relatif au repertoire d'install des sources) +CONFIG.ETABL_FILENAME = 'config/etablissements.csv' + + +# ---------------------------------------------------- +CONFIG.ALLOW_NULL_PRENOM = False # True for UCAC (étudiants camerounais sans prénoms) + +CONFIG.ETUD_MAX_FILE_SIZE = 10*1024*1024 # taille max des fichiers archive etudiants (en octets) + +CONFIG.PUBLISH_PORTAL_PHOTO_URL = False # si pas de photo et portail, publie l'url (était vrai jusqu'en oct 2016) + +CONFIG.MIN_PASSWORD_LENGTH = 0 # si > 0: longueur minimale requise des nouveaux mots de passe (le test cracklib.FascistCheck s'appliquera dans tous les cas) + +# ---------------------------------------------------- +# Ce dictionnaire est fusionné à celui de sco_codes_parcours +# pour définir les codes jury et explications associées +CONFIG.CODES_EXPL = { + # AJ : 'Ajourné (échec)', + + } diff --git a/config/scodocutils.py b/config/scodocutils.py new file mode 100644 index 00000000..98a5be95 --- /dev/null +++ b/config/scodocutils.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- + +""" + Some utilities used by upgrade scripts +""" + + +import sys, os, psycopg2, glob, subprocess, traceback, time + +sys.path.append("..") + + +def log(msg): + sys.stdout.flush() + sys.stderr.write(msg + "\n") + sys.stderr.flush() + + +SCODOC_DIR = os.environ.get("SCODOC_DIR", "") +if not SCODOC_DIR: + log("Error: environment variable SCODOC_DIR is not defined") + sys.exit(1) +SCODOC_VAR_DIR = os.environ.get("SCODOC_VAR_DIR", "") +if not SCODOC_VAR_DIR: + log("Error: environment variable SCODOC_VAR_DIR is not defined") + sys.exit(1) +SCODOC_LOGOS_DIR = os.environ.get("SCODOC_LOGOS_DIR", "") + +def get_dept_cnx_str(dept): + "db cnx string for dept" + f = os.path.join(SCODOC_VAR_DIR, "config", "depts", dept + ".cfg") + try: + return open(f).readline().strip() + except: + log("Error: can't read connexion string for dept %s" % dept) + log("(tried to open %s)" % f) + raise + + +def get_users_cnx_str(): + "db cnx string for users database (used only during upgrades to modify db schema)" + # uses default in sco_utils + # For customized installs, define the value here (used only during upgrades) + import sco_utils + + return sco_utils.SCO_DEFAULT_SQL_USERS_CNX + + +def get_depts(): + "list of defined depts" + files = glob.glob(SCODOC_VAR_DIR + "/config/depts/*.cfg") + return [os.path.splitext(os.path.split(f)[1])[0] for f in files] + + +def field_exists(cnx, table, field): + "true if field exists in sql table" + cursor = cnx.cursor() + cursor.execute( + "SELECT column_name FROM information_schema.columns WHERE table_name = '%s'" + % table + ) + r = cursor.fetchall() + fields = [f[0] for f in r] + return field in fields + + +def list_constraint(cnx, constraint_name=""): + "liste la contrainte (utile surtout pour savoir si elle existe)" + cursor = cnx.cursor() + cursor.execute( + "SELECT * FROM information_schema.table_constraints WHERE constraint_name = %(constraint_name)s", + {"constraint_name": constraint_name}, + ) + return cursor.fetchall() + + +def list_table_index(cnx, table): + "liste les index associés à cette table" + cursor = cnx.cursor() + cursor.execute( + """SELECT t.relname as table_name, i.relname as index_name, a.attname as column_name + FROM + pg_class t, pg_class i, pg_index ix, pg_attribute a + WHERE + t.oid = ix.indrelid and i.oid = ix.indexrelid and a.attrelid = t.oid + and a.attnum = ANY(ix.indkey) and t.relkind = 'r' + and t.relname = %(table)s; + """, + {"table": table}, + ) + r = cursor.fetchall() + return [x[1] for x in r] # ne garde que le nom de l'index + + +def _run_sql(sql, cnx): + cursor = cnx.cursor() + error = False + try: + for cmd in sql: + log("executing SQL: %s" % cmd) + cursor.execute(cmd) + cnx.commit() + except: + cnx.rollback() + log("check_field: failure. Aborting transaction.") + error = True + traceback.print_exc() + return error + + +def check_field(cnx, table, field, sql_create_commands): + "if field does not exists in table, run sql commands" + if not field_exists(cnx, table, field): + log("missing field %s in table %s: trying to create it" % (field, table)) + error = _run_sql(sql_create_commands, cnx) + if not field_exists(cnx, table, field): + log("check_field: new field still missing !") + raise Exception("database configuration problem") + elif error: + log("\n\nAN UNEXPECTED ERROR OCCURRED WHILE UPGRADING DATABASE !\n\n") + else: + log("field %s added successfully." % field) + + +def table_exists(cnx, table): + "true if SQL table exists" + cursor = cnx.cursor() + cursor.execute( + "SELECT table_name FROM information_schema.tables where table_name='%s'" % table + ) + r = cursor.fetchall() + return len(r) > 0 + + +def check_table(cnx, table, sql_create_commands): + "if table does not exists in table, run sql commands" + if not table_exists(cnx, table): + log("missing table %s: trying to create it" % (table)) + error = _run_sql(sql_create_commands, cnx) + if not table_exists(cnx, table): + log("check_table: new table still missing !") + raise Exception("database configuration problem") + elif error: + log("\n\nAN UNEXPECTED ERROR OCCURRED WHILE UPGRADING DATABASE !\n\n") + else: + log("table %s added successfully." % table) + + +def sequence_exists(cnx, seq_name): + "true if SQL sequence exists" + cursor = cnx.cursor() + cursor.execute( + """SELECT relname FROM pg_class + WHERE relkind = 'S' and relname = '%s' + AND relnamespace IN ( + SELECT oid FROM pg_namespace WHERE nspname NOT LIKE 'pg_%%' AND nspname != 'information_schema' + ); + """ + % seq_name + ) + r = cursor.fetchall() + return len(r) > 0 + + +def function_exists(cnx, func_name): + "true if SQL function exists" + cursor = cnx.cursor() + cursor.execute( + """SELECT routine_name FROM information_schema.routines + WHERE specific_schema NOT IN ('pg_catalog', 'information_schema') + AND type_udt_name != 'trigger' + AND routine_name = '%s';""" + % func_name + ) + r = cursor.fetchall() + return len(r) > 0 diff --git a/config/set_passwd.sh b/config/set_passwd.sh new file mode 100755 index 00000000..2d024c3a --- /dev/null +++ b/config/set_passwd.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# +# ScoDoc: reglage du mot de passe admin Zope +# (in Zope terminology, an emergency user) +# +# Doit tre lanc par l'utilisateur unix root dans le repertoire .../config +# ^^^^^^^^^^^^^^^^^^^^^ +# E. Viennet, Juin 2008, Jul 2019 +# + +source config.sh +source utils.sh + + +if [ "$UID" != "0" ] +then + echo "Erreur: le script $0 doit etre lance par root" + exit 1 +fi + +echo "Creation d'un utilisateur d'urgence pour ScoDoc" +echo "(utile en cas de perte de votre mot de passe admin)" + +if [ ${debian_version} != "10" ] +then + mdir=/opt/zope213/lib/python2.7/site-packages/Zope2-2.13.21-py2.7.egg/Zope2/utilities +else + mdir=/opt/zope213/lib/python2.7/site-packages/Zope2/utilities +fi + +python $mdir/zpasswd.py $SCODOC_DIR/../../access + +echo +echo "redemarrer scodoc pour prendre en compte le mot de passe" +echo diff --git a/config/softs/get-iana.sh b/config/softs/get-iana.sh new file mode 100755 index 00000000..f1a9f912 --- /dev/null +++ b/config/softs/get-iana.sh @@ -0,0 +1,214 @@ +#!/bin/bash + +# $Id: get-iana.sh,v 1.15 2013/01/06 23:49:08 ktsaou Exp $ +# +# $Log: get-iana.sh,v $ +# Revision 1.15 2013/01/06 23:49:08 ktsaou +# Removed depedency to get-iana.sh +# It is not usefull any more. +# +# Revision 1.14 2010/06/07 15:44:09 ktsaou +# Made get-iana.sh support the latest IANA format. +# +# Revision 1.13 2010/04/08 22:03:08 ktsaou +# Removed --proxy=off for wget. +# +# Revision 1.12 2008/03/17 22:08:43 ktsaou +# Updated for latest IANA reservations format. +# +# Revision 1.11 2007/06/13 14:40:04 ktsaou +# *** empty log message *** +# +# Revision 1.10 2007/05/05 23:38:31 ktsaou +# Added support for external definitions of: +# +# RESERVED_IPS +# PRIVATE_IPS +# MULTICAST_IPS +# UNROUTABLE_IPS +# +# in files under the same name in /etc/firehol/. +# Only RESERVED_IPS is mandatory (firehol will complain if it is not there, +# but it will still work without it), and is also the only file that firehol +# checks how old is it. If it is 90+ days old, firehol will complain again. +# +# Changed the supplied get-iana.sh script to generate the RESERVED_IPS file. +# FireHOL also instructs the user to use this script if the file is missing +# or is too old. +# +# Revision 1.9 2007/04/29 19:34:11 ktsaou +# *** empty log message *** +# +# Revision 1.8 2005/06/02 15:48:52 ktsaou +# Allowed 127.0.0.1 to be in RESERVED_IPS +# +# Revision 1.7 2005/05/08 23:27:23 ktsaou +# Updated RESERVED_IPS to current IANA reservations. +# +# Revision 1.6 2004/01/10 18:44:39 ktsaou +# Further optimized and reduced PRIVATE_IPS using: +# http://www.vergenet.net/linux/aggregate/ +# +# The supplied get-iana.sh uses 'aggregate-flim' if it finds it in the path. +# (aggregate-flim is the name of this program when installed on Gentoo) +# +# Revision 1.5 2003/08/23 23:26:50 ktsaou +# Bug #793889: +# Change #!/bin/sh to #!/bin/bash to allow FireHOL run on systems that +# bash is not linked to /bin/sh. +# +# Revision 1.4 2002/10/27 12:44:42 ktsaou +# CVS test +# + +# +# Program that downloads the IPv4 address space allocation by IANA +# and creates a list with all reserved address spaces. +# + +# IPV4_ADDRESS_SPACE_URL="http://www.iana.org/assignments/ipv4-address-space" +IPV4_ADDRESS_SPACE_URL="http://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.txt" + +# The program will match all rows in the file which start with a number, have a slash, +# followed by another number, for which the following pattern will also match on the +# same rows +IANA_RESERVED="(RESERVED|UNALLOCATED)" + +# which rows that are matched by the above, to ignore +# (i.e. not include them in RESERVED_IPS)? +#IANA_IGNORE="(Multicast|Private use|Loopback|Local Identification)" +IANA_IGNORE="Multicast" + +tempfile="/tmp/iana.$$.$RANDOM" + +AGGREGATE="`which aggregate-flim 2>/dev/null`" +if [ -z "${AGGREGATE}" ] +then + AGGREGATE="`which aggregate 2>/dev/null`" +fi + +if [ -z "${AGGREGATE}" ] +then + echo >&2 + echo >&2 + echo >&2 "WARNING" + echo >&2 "Please install 'aggregate-flim' to shrink the list of IPs." + echo >&2 + echo >&2 +fi + +echo >&2 +echo >&2 "Fetching IANA IPv4 Address Space, from:" +echo >&2 "${IPV4_ADDRESS_SPACE_URL}" +echo >&2 + +wget -O - "${IPV4_ADDRESS_SPACE_URL}" |\ + egrep "^ *[0-9]+/[0-9]+.*${IANA_RESERVED}" |\ + egrep -vi "${IANA_IGNORE}" |\ + sed "s/^ \+//g" |\ + cut -d ' ' -f 1 |\ +( + while IFS="/" read range net + do + # echo >&2 "$range/$net" + + if [ ! $net -eq 8 ] + then + echo >&2 "Cannot handle network masks of $net bits ($range/$net)" + continue + fi + + first=`echo $range | cut -d '-' -f 1` + first=`expr $first + 0` + last=`echo $range | cut -d '-' -f 2` + last=`expr $last + 0` + + x=$first + while [ ! $x -gt $last ] + do + # test $x -ne 127 && echo "$x.0.0.0/$net" + echo "$x.0.0.0/$net" + x=$[x + 1] + done + done +) | \ +( + if [ ! -z "${AGGREGATE}" -a -x "${AGGREGATE}" ] + then + "${AGGREGATE}" + else + cat + fi +) >"${tempfile}" + +echo >&2 +echo >&2 +echo >&2 "FOUND THE FOLLOWING RESERVED IP RANGES:" +printf "RESERVED_IPS=\"" +i=0 +for x in `cat ${tempfile}` +do + i=$[i + 1] + printf "${x} " +done +printf "\"\n" + +if [ $i -eq 0 ] +then + echo >&2 + echo >&2 + echo >&2 "Failed to find reserved IPs." + echo >&2 "Possibly the file format has been changed, or I cannot fetch the URL." + echo >&2 + + rm -f ${tempfile} + exit 1 +fi +echo >&2 +echo >&2 +echo >&2 "Differences between the fetched list and the list installed in" +echo >&2 "/etc/firehol/RESERVED_IPS:" + +echo >&2 "# diff /etc/firehol/RESERVED_IPS ${tempfile}" +diff /etc/firehol/RESERVED_IPS ${tempfile} + +if [ $? -eq 0 ] +then + touch /etc/firehol/RESERVED_IPS + echo >&2 + echo >&2 "No differences found." + echo >&2 + + rm -f ${tempfile} + exit 0 +fi + +echo >&2 +echo >&2 +echo >&2 "Would you like to save this list to /etc/firehol/RESERVED_IPS" +echo >&2 "so that FireHOL will automatically use it from now on?" +echo >&2 +while [ 1 = 1 ] +do + printf >&2 "yes or no > " + read x + + case "${x}" in + yes) cp -f /etc/firehol/RESERVED_IPS /etc/firehol/RESERVED_IPS.old 2>/dev/null + cat "${tempfile}" >/etc/firehol/RESERVED_IPS || exit 1 + echo >&2 "New RESERVED_IPS written to '/etc/firehol/RESERVED_IPS'." + break + ;; + + no) + echo >&2 "Saved nothing." + break + ;; + + *) echo >&2 "Cannot understand '${x}'." + ;; + esac +done + +rm -f ${tempfile} + diff --git a/config/softs/pyExcelerator-0.6.3a.patched.tgz b/config/softs/pyExcelerator-0.6.3a.patched.tgz new file mode 100644 index 00000000..32c6bf31 Binary files /dev/null and b/config/softs/pyExcelerator-0.6.3a.patched.tgz differ diff --git a/config/softs/pydot-0.9.10.tar.gz b/config/softs/pydot-0.9.10.tar.gz new file mode 100644 index 00000000..b923b58c Binary files /dev/null and b/config/softs/pydot-0.9.10.tar.gz differ diff --git a/config/upgrade.sh b/config/upgrade.sh new file mode 100755 index 00000000..32e34f65 --- /dev/null +++ b/config/upgrade.sh @@ -0,0 +1,139 @@ +#!/bin/bash + +# Upgrade ScoDoc installation using SVN +# SVN must be properly configured and have read access to ScoDoc repository +# This script STOP and RESTART ScoDoc and should be runned as root +# +# Upgrade also the Linux system using apt. +# +# Script for ScoDoc 7 (Debian 7, 8, 9, 10) +# +# E. Viennet, sep 2013, mar 2017, jun 2019, aug 2020 + +cd /opt/scodoc/Products/ScoDoc/config +source config.sh +source utils.sh + +check_uid_root $0 + +if [ -z "$SCODOC_UPGRADE_RUNNING" ] + then + apt-get update && apt-get -y dist-upgrade +fi + +# Upgrade svn working copy if possible +svnver=$(svn --version --quiet) +if [[ ${svnver} > "1.7" ]] +then + (cd "$SCODOC_DIR"; find . -name .svn -type d -exec dirname {} \; | xargs svn upgrade) +fi + +echo "Stopping ScoDoc..." +/etc/init.d/scodoc stop + +echo +echo "Using SVN to update $SCODOC_DIR..." +(cd "$SCODOC_DIR"; svn update) + +SVNVERSION=$(cd ..; svnversion) + +if [ ! -e "${SCODOC_VERSION_DIR}" ]; then + mkdir -p "${SCODOC_VERSION_DIR}" + chown www-data.www-data "${SCODOC_VERSION_DIR}" +fi +if [ ! -e "${SCODOC_VERSION_DIR}"/scodoc.sn ]; then + if [ -e "$SCODOC_DIR"/config/scodoc.sn ]; then + # migrate from old scodoc installs + mv "$SCODOC_DIR"/config/scodoc.sn "${SCODOC_VERSION_DIR}" + fi +fi + +SN=$(cat "$SCODOC_DIR"/config/scodoc.sn 2> /dev/null || cat "${SCODOC_VERSION_DIR}"/scodoc.sn) +if [ -e "$SCODOC_DIR"/config/scodoc.sn ] || [ -e "${SCODOC_VERSION_DIR}"/scodoc.sn ] +then + if [[ ! "${SN}" =~ ^[0-9].* ]] + then + SN='' # fix for invalid previous replies + fi + mode=upgrade +else + mode=install +fi + +CMD="curl --fail --connect-timeout 5 --silent http://scodoc.iutv.univ-paris13.fr/scodoc-installmgr/version?mode=$mode\&svn=${SVNVERSION}\&sn=${SN}" +#echo $CMD +SVERSION="$(${CMD})" + +if [ $? == 0 ]; then + #echo "answer=${SVERSION}" + echo "${SVERSION}" > "${SCODOC_VERSION_DIR}"/scodoc.sn +else + echo 'Warning: cannot connect to scodoc release server' +fi + + +# Check that no Zope "access" file has been forgotten in the way: +if [ -e $SCODOC_DIR/../../access ] +then + mv $SCODOC_DIR/../../access $SCODOC_DIR/../../access.bak +fi + +# Fix some permissions which may have been altered in the way: +chsh -s /bin/sh $POSTGRES_USER # www-data, nologin in Debian 9 +chown root.www-data "$SCODOC_DIR" # important to create .pyc +chmod 775 "${SCODOC_DIR}" +chmod a+r "$SCODOC_DIR"/*.py + +chown -R root.www-data "$SCODOC_DIR"/config +chmod 775 "$SCODOC_DIR"/config +chmod a+rx "$SCODOC_DIR"/config/postupgrade-db.py +chmod a+r "$SCODOC_DIR"/config/scodocutils.py + +chown -R root.www-data "$SCODOC_DIR"/misc +chmod -R a+r "$SCODOC_DIR"/misc +# depts dir: +if [ ! -e "${SCODOC_VAR_DIR}/config/depts" ] +then + mkdir "${SCODOC_VAR_DIR}/config/depts" +fi +# ScoDoc must be able to write to var directory: +chown -R www-data.www-data "${SCODOC_VAR_DIR}" +chmod -R u+w "${SCODOC_VAR_DIR}" + +# Important to create .pyc: +chgrp -R www-data "${SCODOC_DIR}"/ZopeProducts +chmod -R g+w "${SCODOC_DIR}"/ZopeProducts + +# Se recharge car ce fichier peut avoir change durant le svn up ! +if [ -z "$SCODOC_UPGRADE_RUNNING" ] +then + export SCODOC_UPGRADE_RUNNING=1 + ./upgrade.sh + exit 0 +fi + +# Add some python modules: +"$SCODOC_DIR"/config/install_cal_modules.sh +/opt/zope213/bin/python -c "import requests" >& /dev/null +if [ $? -ne 0 ] +then + /opt/zope213/bin/pip install requests +fi + +# Ensure www-data can duplicate databases (for dumps) +su -c $'psql -c \'alter role "www-data" with CREATEDB;\'' "$POSTGRES_SUPERUSER" +#' + +# post-upgrade scripts +echo "Executing post-upgrade script..." +"$SCODOC_DIR"/config/postupgrade.py + +echo "Executing post-upgrade database script..." +su -c "$SCODOC_DIR/config/postupgrade-db.py" $POSTGRES_USER + +# +echo +echo "Starting ScoDoc..." +/etc/init.d/scodoc start + + diff --git a/config/utils.sh b/config/utils.sh new file mode 100644 index 00000000..8637b736 --- /dev/null +++ b/config/utils.sh @@ -0,0 +1,57 @@ + +# Misc utilities for ScoDoc install shell scripts + +to_lower() { + echo $1 | tr "[:upper:]" "[:lower:]" +} + +to_upper() { + echo $1 | tr "[:lower:]" "[:upper:]" +} + +norm_ans() { + x=$(to_upper $1 | tr O Y) + echo ${x:0:1} +} + +check_uid_root() { + if [ "$UID" != "0" ] + then + echo "Erreur: le script $1 doit etre lance par root" + exit 1 + fi +} + +terminate() { + echo + echo "Erreur: $1" + echo + exit 1 +} + +# --- Ensure postgres user www-data exists +init_postgres_user() { # run as root + if [ -z $(echo "select usename from pg_user;" | su -c "$PSQL -d template1 -p $POSTGRES_PORT" $POSTGRES_SUPERUSER | grep $POSTGRES_USER) ] + then + # add database user + echo "Creating postgresql user $POSTGRES_USER" + su -c "createuser -p $POSTGRES_PORT --no-superuser --no-createdb --no-adduser --no-createrole ${POSTGRES_USER}" $POSTGRES_SUPERUSER + fi +} + + +# XXX inutilise +gen_passwd() { + PASSWORD_LENGTH="8" + ALLOWABLE_ASCII="~@#$%^&*()_+=-?><0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + SEED=$(head -c4 /dev/urandom | od -t u4 | awk '{ print $2 }') + RANDOM=$SEED + n=1 + password="" + while [ "$n" -le "$PASSWORD_LENGTH" ] + do + password="$password${ALLOWABLE_ASCII:$(($RANDOM%${#ALLOWABLE_ASCII})):1}" + n=$((n+1)) + done + echo $password +} diff --git a/csv2rules.py b/csv2rules.py new file mode 100755 index 00000000..c99c9343 --- /dev/null +++ b/csv2rules.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +"""Converti fichier CSV decrivant regles parcours DUT +en "regles" python. + +CSV: utf-8/Euro, tab, no text delimiter +(le faire avec OpenOffice ou NeoOffice...) + +""" +import sys, pdb + +sourcefile = sys.argv[1] # fichier CSV + +ALL = "ALL" + +HEAD = """# -*- coding: utf-8 -*- +# +# Generated by csv2rules.py *** DO NOT EDIT *** +# +# Command: %s %s +# +from sco_codes_parcours import * + +rules_source_file='%s' + +""" % ( + sys.argv[0], + sys.argv[1], + sourcefile, +) + +from sco_codes_parcours import * + + +def _fmt(s): + if not s: + return None + if strlower(s) in ("ok", "oui", "o", "y", "yes"): + return True + if strlower(s) in ("no", "non"): + return False + if s == "*": + return ALL + return s + + +colidx_code_sem = 7 # index colonne 'code_sem' dans feuille CSV +iue = colidx_code_sem + 1 +icodeprev = 1 # idex col "Prev" + + +def genrules(csv): + r = [] + linenum = 1 + try: + for line in csv: + line = line.strip() + if line: + if line[0] == "#": + r.append(line) + else: + fs = [_fmt(s) for s in line.split("\t")] + if not "," in fs[icodeprev]: + fs[icodeprev] = fs[icodeprev] + "," # liste codes prev + if fs[iue] and not "," in fs[iue]: + fs[iue] = fs[iue] + "," # liste codes UE + if fs[iue]: + fs[iue] = "(" + fs[iue] + ")" + else: + fs[iue] = "()" + fs[-1] = "'" + fs[-1].replace("'", "\\'") + "'" + r.append( + "( '%s', ((%s), %s, %s, %s, %s, %s)," + % tuple(fs[:colidx_code_sem]) + ) + r.append( + " (%s, %s, %s, %s, %s, %s) )," % tuple(fs[colidx_code_sem:]) + ) + linenum += 1 + except: + sys.stderr.write("error line %d on\n%s\n" % (linenum, csv[linenum])) + raise + return ( + HEAD + + "DUTRules = [ DUTRule(rid, p, c) for (rid, p,c) in (\n" + + "\n".join(r) + + "\n)]" + ) + + +print(genrules(open(sourcefile).readlines())) diff --git a/debug.py b/debug.py new file mode 100644 index 00000000..b8c0d763 --- /dev/null +++ b/debug.py @@ -0,0 +1,116 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +"""Configuration pour debugguer en mode console + +Lancer ScoDoc ainsi: (comme root) + + /opt/scodoc/bin/zopectl debug + +Puis + +from debug import * +context = go(app) +# ou +context = go_dept(app, 'CJ') + +authuser = app.acl_users.getUserById('admin') +# ou app.ScoDoc.acl_users.getUserById('toto') pour un utilisateur scodoc +authuser = authuser.__of__(app.acl_users) + +Exemple: +sems = context.Notes.formsemestre_list() +formsemestre_id = sems[0]['formsemestre_id'] + +# Affiche tous les semestres: +for sem in sems: + print sem['formsemestre_id'], sem['titre_num'] + +# Recupere la table de notes: +nt = context.Notes._getNotesCache().get_NotesTable(context.Notes, formsemestre_id) + + +""" +from notesdb import * +from notes_log import log +from sco_utils import * + +from gen_tables import GenTable +import sco_archives +import sco_groups +import sco_evaluations +import sco_formsemestre +import sco_formsemestre_edit +import sco_compute_moy +import sco_parcours_dut +import sco_codes_parcours +import sco_bulletins +import sco_excel +import sco_formsemestre_status +import sco_bulletins_xml + +# Prend le premier departement comme context + + +def go(app, n=0): + context = app.ScoDoc.objectValues("Folder")[n].Scolarite + print("context in dept ", context.DeptId()) + return context + + +def go_dept(app, dept): + objs = app.ScoDoc.objectValues("Folder") + for o in objs: + context = o.Scolarite + if context.DeptId() == dept: + print("context in dept ", context.DeptId()) + return context + raise ValueError("dep %s not found" % dept) + + +class FakeUser: + def __init__(self, name): + self.name = name + + def __str__(self): + return self.name + + def has_permission(self, op, context): + return True + + +class DummyResponse: + """Emulation vide de Reponse http Zope""" + + def __init__(self): + self.header = {} + self.redirected = "" + + def setHeader(self, name, value): + self.header[name] = value + + def redirect(self, url): + self.redirected = url + + +class DummyRequest: + """Emulation vide de Request Zope""" + + def __init__(self): + self.RESPONSE = DummyResponse() + self.AUTHENTICATED_USER = FakeUser("admin") + self.form = {} + self.URL = "http://scodoc/" + self.URL1 = self.URL + self.URL0 = self.URL + self.BASE0 = "localhost" + self.REMOTE_ADDR = "127.0.0.1" + self.HTTP_REFERER = "" + self.REQUEST_METHOD = "get" + self.QUERY_STRING = "" + + +REQUEST = DummyRequest() + +# handful shorcut +pp = pprint.pprint diff --git a/dtml/docLogin.dtml b/dtml/docLogin.dtml new file mode 100644 index 00000000..fd46ec9f --- /dev/null +++ b/dtml/docLogin.dtml @@ -0,0 +1,49 @@ + +
      + + + + + + +

      + +

      + + + + + Added by Emmanuel for ScoDoc + + + + + + + + + + + + + + + +
      + Nom + + +
      + Mot de passe + + +
      +
      +
      +Ok "> +
      + +
      + +
      + diff --git a/dtml/docLogout.dtml b/dtml/docLogout.dtml new file mode 100644 index 00000000..5210fc74 --- /dev/null +++ b/dtml/docLogout.dtml @@ -0,0 +1,16 @@ + +

      +

      +

      Vous êtes déconnecté de ScoDoc. +

      + +

      ">revenir à l'accueil

      + +
      +

      (Attention: si vous êtes administrateur, vous ne pouvez vous déconnecter complètement qu'en relançant votre navigateur) +

      +
      + + + + diff --git a/dtml/entreprises/entreprise_contact_create.dtml b/dtml/entreprises/entreprise_contact_create.dtml new file mode 100644 index 00000000..e0c3ab05 --- /dev/null +++ b/dtml/entreprises/entreprise_contact_create.dtml @@ -0,0 +1,62 @@ + + + + + + + + + + + +

      Nouveau "contact" avec l'entreprise

      + + + + + + + + +

      +
      + + + + + + + +
      + + \ No newline at end of file diff --git a/dtml/entreprises/entreprise_contact_delete.dtml b/dtml/entreprises/entreprise_contact_delete.dtml new file mode 100644 index 00000000..df73659b --- /dev/null +++ b/dtml/entreprises/entreprise_contact_delete.dtml @@ -0,0 +1,27 @@ + + + + +

      Suppression du contact

      + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dtml/entreprises/entreprise_contact_edit.dtml b/dtml/entreprises/entreprise_contact_edit.dtml new file mode 100644 index 00000000..5fe437f1 --- /dev/null +++ b/dtml/entreprises/entreprise_contact_edit.dtml @@ -0,0 +1,62 @@ + + + + + + + +

      + + +

      Contact avec entreprise

      + + + + + + + +

      +
      + + + +

      Supprimer ce contact

      +
      + + + + + + +
      + + + \ No newline at end of file diff --git a/dtml/entreprises/entreprise_contact_list.dtml b/dtml/entreprises/entreprise_contact_list.dtml new file mode 100644 index 00000000..cffeb351 --- /dev/null +++ b/dtml/entreprises/entreprise_contact_list.dtml @@ -0,0 +1,54 @@ + + + + + + +

      Listes des contacts avec l'entreprise

      + + +

      Listes des contacts

      + +
      + + + + + + + + + + + + + + + + + + + + + + + +
      DateObjetEntrepriseEtudiantDescription
      "> + + + + + "> + + +
      Aucun contact !
      + + + +

      +">nouveau "contact" +

      +
      +
      + + \ No newline at end of file diff --git a/dtml/entreprises/entreprise_correspondant_create.dtml b/dtml/entreprises/entreprise_correspondant_create.dtml new file mode 100644 index 00000000..2ffdbba4 --- /dev/null +++ b/dtml/entreprises/entreprise_correspondant_create.dtml @@ -0,0 +1,43 @@ + + + + + +

      dans l'entreprise

      + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dtml/entreprises/entreprise_correspondant_delete.dtml b/dtml/entreprises/entreprise_correspondant_delete.dtml new file mode 100644 index 00000000..ac50550e --- /dev/null +++ b/dtml/entreprises/entreprise_correspondant_delete.dtml @@ -0,0 +1,27 @@ + + + + +

      Suppression du correspondant

      + + + + + + + + + + + + + + + + diff --git a/dtml/entreprises/entreprise_correspondant_edit.dtml b/dtml/entreprises/entreprise_correspondant_edit.dtml new file mode 100644 index 00000000..2b254568 --- /dev/null +++ b/dtml/entreprises/entreprise_correspondant_edit.dtml @@ -0,0 +1,41 @@ + + + + +

      + + + + + + + + + + + + + + + diff --git a/dtml/entreprises/entreprise_correspondant_list.dtml b/dtml/entreprises/entreprise_correspondant_list.dtml new file mode 100644 index 00000000..085d4375 --- /dev/null +++ b/dtml/entreprises/entreprise_correspondant_list.dtml @@ -0,0 +1,52 @@ + + + + +

      Listes des correspondants dans l'entreprise

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NomEntrepriseFonctionTéléphoneMailNote
      "> + + + + + + / + / + + "> +
      ">
      +
      ">modifier ">supprimer
      Aucun correspondant dans cette entreprise !
      + +

      + + + diff --git a/dtml/entreprises/entreprise_delete.dtml b/dtml/entreprises/entreprise_delete.dtml new file mode 100644 index 00000000..7f7db5f8 --- /dev/null +++ b/dtml/entreprises/entreprise_delete.dtml @@ -0,0 +1,60 @@ + + + + + +

      Suppression de l'entreprise

      + +

      Attention: supression définitive de l'entreprise, de ses correspondants et contacts. +

      + + + + +

      Correspondants dans l'entreprise (seront supprimés):

      +
        + + +
      • + + () +
      • +
        +
        +
      +
      + + + +

      Contacts avec l'entreprise (seront supprimés):

      +
        + + +
      • + () +
      • +
        +
        +
      +
      + + + + + + + + + + + + + + + diff --git a/dtml/entreprises/index_html.dtml b/dtml/entreprises/index_html.dtml new file mode 100644 index 00000000..1e2fbc25 --- /dev/null +++ b/dtml/entreprises/index_html.dtml @@ -0,0 +1,139 @@ + + + + + + + + + + +

      + + +

      +Attention: version test préliminaire. Signaler les problèmes à Emmanuel +

      +
      + + + + + + + + + + + + + + +

      valeur invalide pour 'sort_type' !

      +
      + + + + + + + + + + +
      + + + + + + + + +
      nomvilleétudiant     Tri par:
      +
      + + + + + +
      + + + +
      EntreprisesRésultats - sur +
      + + + + + + + + + + + + + + + + + + + + + + +
      &start=">
       
      + + +

      Aucune entreprise !

      + + + +

      +

      + "> + + + + + + page précédente     + + + + + + page suivante + + + +   Résultats par page : + +
      + +

      + diff --git a/dtml/manage_addZNotesForm.dtml b/dtml/manage_addZNotesForm.dtml new file mode 100644 index 00000000..e58b49e0 --- /dev/null +++ b/dtml/manage_addZNotesForm.dtml @@ -0,0 +1,49 @@ + + + + +
      +

      +Notes Objects are very usefull thus not documented yet... +

      +
      + +

      + + + + + + + + + + + + +
      + +
      + + + diff --git a/dtml/manage_addZScolarForm.dtml b/dtml/manage_addZScolarForm.dtml new file mode 100644 index 00000000..580a83e1 --- /dev/null +++ b/dtml/manage_addZScolarForm.dtml @@ -0,0 +1,62 @@ + + + + +
      +

      +ZScolar: gestion scolarite d'un departement +

      +
      + +

      + +
      +
      + Id +
      +
      + +
      +
      + Title +
      +
      + +
      + + + + + + + + + + + + + + + + + +
      + +
      + + + diff --git a/dtml/manage_editZNotesForm.dtml b/dtml/manage_editZNotesForm.dtml new file mode 100644 index 00000000..456b631f --- /dev/null +++ b/dtml/manage_editZNotesForm.dtml @@ -0,0 +1,21 @@ + + + + + + +

      + id:
      + title:
      +
      + +
      + + + + + diff --git a/dtml/manage_editZScolarForm.dtml b/dtml/manage_editZScolarForm.dtml new file mode 100644 index 00000000..aab8859e --- /dev/null +++ b/dtml/manage_editZScolarForm.dtml @@ -0,0 +1,21 @@ + + + + + + +

      + id:
      + title:
      +
      + +
      + + + + + diff --git a/dutrules.py b/dutrules.py new file mode 100644 index 00000000..e0c37115 --- /dev/null +++ b/dutrules.py @@ -0,0 +1,354 @@ +# -*- coding: utf-8 -*- +# +# Generated by csv2rules.py *** DO NOT EDIT *** +# +# Command: ./csv2rules.py misc/parcoursDUT.csv +# +from sco_codes_parcours import * + +rules_source_file = "misc/parcoursDUT.csv" + +DUTRules = [ + DUTRule(rid, p, c) + for (rid, p, c) in ( + # Id Prev. Assiduité Moy Gen Barres UE Comp prev/cur Suivant Code SEM Codes UE Code prev. (si modifié) Devenir Action Explication + # Semestre prec. validé: + ( + "10", + ((None, ADM, ADC, ADJ), True, True, True, ALL, ALL), + (ADM, (ADM,), None, NEXT, None, "Passage normal"), + ), + ( + "20", + ((None, ADM, ADC, ADJ), True, False, True, ALL, True), + (ATT, (ADM,), None, NEXT, None, "Pas moy: attente suivant pour compenser"), + ), + ( + "30", + ((None, ADM, ADC, ADJ), True, ALL, False, ALL, ALL), + (ATB, (ADM, AJ), None, NEXT, None, "Pas barre UE"), + ), + ( + "40", + ((None, ADM, ADC, ADJ), False, ALL, ALL, ALL, True), + ( + ATJ, + (AJ,), + None, + NEXT, + None, + "Pb assiduité, passe sans valider pour l'instant", + ), + ), + ( + "50", + ((ADM, ADJ, ADC), True, False, ALL, True, ALL), + (ADC, (ADM, CMP), None, NEXT, None, "Compense avec semestre précédent"), + ), + # Semestre prec. ATJ (pb assiduité): + ( + "60", + ((ATJ,), False, ALL, ALL, ALL, ALL), + (NAR, (AJ,), AJ, REO, None, "Pb assiduité persistant: réorientation"), + ), + ( + "70", + ((ATJ,), False, ALL, ALL, ALL, ALL), + ( + AJ, + (AJ,), + AJ, + REDOANNEE, + None, + "Pb assiduité persistant: redoublement année", + ), + ), + ( + "80", + ((ALL,), False, ALL, ALL, ALL, ALL), + (AJ, (), ADM, REO, None, "Pb assiduité, étudiant en échec."), + ), + # Semestre prec. ATT (pb moy gen): + ( + "90", + ((ATT,), True, True, True, True, ALL), + (ADM, (ADM,), ADC, NEXT, None, "Passage, et compense précédent"), + ), + ( + "100", + ((ATT,), True, True, True, ALL, ALL), + (ADM, (ADJ,), ADJ, NEXT, None, "Passage, le jury valide le précédent"), + ), + ( + "110", + ((ATT,), False, True, True, ALL, True), + ( + ATJ, + (AJ,), + ADJ, + NEXT, + None, + "Passage, le jury valide le précédent, pb assiduité", + ), + ), + ( + "120", + ((ATT,), True, False, ALL, ALL, ALL), + (AJ, (AJ,), AJ, REDOANNEE, None, "Redoublement année"), + ), + ( + "130", + ((ATT,), ALL, True, True, False, ALL), + ( + AJ, + (AJ,), + AJ, + REDOANNEE, + None, + "Pas de compensation ni validation du précédent", + ), + ), + ( + "140", + ((ATT,), True, False, True, ALL, ALL), + ( + ATT, + (), + ADJ, + NEXT, + None, + "Pas moy, le jury valide le précédent, semestre en attente pour compenser", + ), + ), + # Semestre prec. ATB (pb barre UE): + ( + "200", + ((ATB,), ALL, ALL, ALL, ALL, ALL), + ( + AJ, + (ADM, AJ), + AJ, + REDOANNEE, + None, + "Le précédent ne peut pas être validé, redoublement année", + ), + ), + ( + "210", + ((ATB,), ALL, ALL, ALL, ALL, ALL), + ( + NAR, + (ADM, AJ), + NAR, + REO, + None, + "Le précédent ne peut pas être validé, réorientation", + ), + ), + ( + "220", + ((ATB,), True, True, True, ALL, ALL), + (ADM, (ADM,), ADJ, NEXT, None, "Le jury valide le précédent"), + ), + ( + "230", + ((ATB,), True, False, True, ALL, True), + ( + ATT, + (ADM, AJ), + ADJ, + NEXT, + None, + "Le jury valide le précédent, pas moyenne gen., attente suivant", + ), + ), + ( + "240", + ((ATB,), True, ALL, False, ALL, True), + ( + ATB, + (ADM, AJ), + ADJ, + NEXT, + None, + "Le jury valide le précédent, pb barre UE, attente", + ), + ), + ( + "250", + ((ATB,), False, ALL, ALL, ALL, True), + ( + ATJ, + (AJ,), + ADJ, + NEXT, + None, + "Le jury valide le précédent, mais probleme assiduité.", + ), + ), + ( + "260", + ((ATB, ATT), ALL, True, True, ALL, ALL), + ( + ADJ, + (), + AJ, + REDOANNEE, + None, + "Le jury valide ce semestre, et fait recommencer le précédent.", + ), + ), + # Semestre prec. AJ (ajourné): + ( + "300", + ((AJ,), True, False, ALL, ALL, ALL), + (AJ, (), AJ, REDOANNEE, None, "Echec de 2 semestres, redouble année"), + ), + ( + "310", + ((AJ,), True, True, False, ALL, ALL), + (AJ, (), AJ, REDOANNEE, None, "Echec de 2 semestres, redouble année"), + ), + ( + "320", + ((AJ,), False, ALL, ALL, ALL, ALL), + (NAR, (), None, REO, None, "Echec, pas assidu: réorientation"), + ), + ( + "330", + ((AJ,), True, True, True, ALL, ALL), + ( + ATT, + (), + None, + REDOANNEE, + None, + "Ne valide pas car mais manque le précédent: redouble (modif 2017)", + ), + ), + # Décisions du jury: + ( + "400", + ((ALL,), True, False, ALL, ALL, ALL), + (ADJ, (ADM, CMP), None, NEXT, None, "Le jury décide de valider"), + ), + ( + "410", + ((ATT, ATB), True, False, ALL, ALL, ALL), + ( + ADJ, + (ADM, CMP), + ADJ, + NEXT, + None, + "Le jury décide de valider ce semestre et le précédent", + ), + ), + ( + "420", + ((ALL,), True, True, False, ALL, ALL), + (ADJ, (ADM, CMP), None, NEXT, None, "Le jury décide de valider"), + ), + ( + "430", + ((ATT, ATB), True, True, False, ALL, ALL), + ( + ADJ, + (ADM, CMP), + ADJ, + NEXT, + None, + "Le jury décide de valider ce semestre et le précédent", + ), + ), + ( + "450", + ((ATT, ATB), False, False, True, ALL, True), + ( + ATT, + (ADM, AJ), + ADJ, + NEXT, + None, + "Pb moy: attente, mais le jury valide le précédent", + ), + ), + # Semestres “décales” (REDOSEM) + ( + "500", + ((None, ADM, ADC, ADJ, ATT, ATB), True, False, ALL, False, ALL), + (AJ, (), None, REDOSEM, None, "Pas moy: redouble ce semestre"), + ), + ( + "510", + ((None, ADM, ADC, ADJ, ATT, ATB), True, True, False, False, ALL), + (AJ, (), None, REDOSEM, None, "Pas barre UE: redouble ce semestre"), + ), + ( + "520", + ((None, ADM, ADC, ADJ, ATB, ATT), False, ALL, ALL, ALL, ALL), + (AJ, (), None, REDOSEM, None, "Pb assiduité: redouble ce semestre"), + ), + # Nouvelles regles avec plusieurs devenirs en semestres decales: + ( + "550", + ((ATT, ATB), ALL, False, ALL, False, ALL), + ( + AJ, + (), + None, + RA_OR_RS, + None, + "Deux semestres ratés, choix de recommencer le premier ou le second", + ), + ), + ( + "560", + ((ATT, ATB), ALL, True, False, False, ALL), + ( + AJ, + (), + None, + RA_OR_RS, + None, + "Deux semestres ratés, choix de recommencer le premier ou le second", + ), + ), + ( + "570", + ((None, ADM, ADJ, ADC), ALL, False, True, False, ALL), + ( + ATT, + (), + None, + RS_OR_NEXT, + None, + "Semestre raté, choix de redoubler le semestre ou de continuer pour éventuellement compenser.", + ), + ), + ( + "580", + ((None, ADM, ADJ, ADC), ALL, ALL, False, False, ALL), + ( + ATB, + (), + None, + RS_OR_NEXT, + None, + "Semestre raté, choix de redoubler ou de s'en remettre au jury du semestre suivant.", + ), + ), + # Exclusion (art. 22): si precedent non valide et pas les barres dans le courant, on peut ne pas autoriser a redoubler: + # (le cas ATB est couvert plus haut) + ( + "600", + ((AJ, ATT, NAR), True, False, ALL, ALL, ALL), + (NAR, (), NAR, REO, None, "Non autorisé à redoubler"), + ), + ( + "610", + ((AJ, ATT, NAR), True, True, False, ALL, ALL), + (NAR, (), NAR, REO, None, "Non autorisé à redoubler"), + ), + ) +] diff --git a/gen_tables.py b/gen_tables.py new file mode 100644 index 00000000..0e14585f --- /dev/null +++ b/gen_tables.py @@ -0,0 +1,706 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +"""Géneration de tables aux formats XHTML, PDF, Excel, XML et JSON. + +Les données sont fournies comme une liste de dictionnaires, chaque élément de +cette liste décrivant une ligne du tableau. + +Chaque colonne est identifiée par une clé du dictionnaire. + +Voir exemple en fin de ce fichier. + +Les clés commençant par '_' sont réservées. Certaines altèrent le traitement, notamment +pour spécifier les styles de mise en forme. +Par exemple, la clé '_css_row_class' spécifie le style CSS de la ligne. + +""" + +import random +from collections import OrderedDict + +# XML generation package (apt-get install jaxml) +import jaxml + +import sco_excel +from sco_pdf import * + + +def mark_paras(L, tags): + """Put each (string) element of L between + """ + for tag in tags: + b = "<" + tag + ">" + c = "" + L = [b + (x or "") + c for x in L] + return L + + +class DEFAULT_TABLE_PREFERENCES: + values = { + "SCOLAR_FONT": "Helvetica", # used for PDF, overriden by preferences argument + "SCOLAR_FONT_SIZE": 10, + "SCOLAR_FONT_SIZE_FOOT": 6, + } + + def __getitem__(self, k): + return self.values[k] + + +class GenTable: + """Simple 2D tables with export to HTML, PDF, Excel, CSV. + Can be sub-classed to generate fancy formats. + """ + + default_css_class = "gt_table stripe cell-border compact hover order-column" + + def __init__( + self, + rows=[{}], # liste de dict { column_id : value } + columns_ids=[], # id des colonnes a afficher, dans l'ordre + titles={}, # titres (1ere ligne) + bottom_titles={}, # titres derniere ligne (optionnel) + caption=None, + page_title="", # titre fenetre html + pdf_link=True, + xls_link=True, + xml_link=False, + table_id=None, # for html and xml + html_class=None, # class de l'element
      +
      + Id +
      +
      + +
      +
      + Title +
      +
      + +
      +
      + DB connexion string +
      +
      + +
      (en plus des classes par defaut, + html_class_ignore_default=False, # sauf si html_class_ignore_default est vrai) + html_sortable=False, + html_highlight_n=2, # une ligne sur 2 de classe "gt_hl" + html_col_width=None, # force largeur colonne + html_generate_cells=True, # generate empty ]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*\s*$/g,bz={option:[1,""],legend:[1,"
      ","
      "],thead:[1,"
      cells even if not in rows (useless?) + html_title="", # avant le tableau en html + html_caption=None, # override caption if specified + html_header=None, + html_next_section="", # html fragment to put after the table + html_with_td_classes=False, # put class=column_id in each + html_before_table="", # html snippet to put before the in the page + html_empty_element="", # replace table when empty + base_url=None, + origin=None, # string added to excel and xml versions + filename="table", # filename, without extension + xls_sheet_name="feuille", + xls_before_table=[], # liste de cellules a placer avant la table + pdf_title="", # au dessus du tableau en pdf + pdf_table_style=None, + pdf_col_widths=None, + xml_outer_tag="table", + xml_row_tag="row", + text_fields_separator="\t", + preferences=None, + ): + self.rows = rows # [ { col_id : value } ] + self.columns_ids = columns_ids # ordered list of col_id + self.titles = titles # { col_id : title } + self.bottom_titles = bottom_titles + self.origin = origin + self.base_url = base_url + self.filename = filename + self.caption = caption + self.html_header = html_header + self.html_before_table = html_before_table + self.html_empty_element = html_empty_element + self.page_title = page_title + self.pdf_link = pdf_link + self.xls_link = xls_link + self.xml_link = xml_link + # HTML parameters: + if not table_id: # random id + self.table_id = "gt_" + str(random.randint(0, 1000000)) + else: + self.table_id = table_id + self.html_generate_cells = html_generate_cells + self.html_title = html_title + self.html_caption = html_caption + self.html_next_section = html_next_section + self.html_with_td_classes = html_with_td_classes + if html_class is None: + html_class = self.default_css_class + if html_class_ignore_default: + self.html_class = html_class + else: + self.html_class = self.default_css_class + " " + html_class + + self.sortable = html_sortable + self.html_highlight_n = html_highlight_n + self.html_col_width = html_col_width + # XLS parameters + self.xls_sheet_name = xls_sheet_name + self.xls_before_table = xls_before_table + # PDF parameters + self.pdf_table_style = pdf_table_style + self.pdf_col_widths = pdf_col_widths + self.pdf_title = pdf_title + # XML parameters + self.xml_outer_tag = xml_outer_tag + self.xml_row_tag = xml_row_tag + # TEXT parameters + self.text_fields_separator = text_fields_separator + # + if preferences: + self.preferences = preferences + else: + self.preferences = DEFAULT_TABLE_PREFERENCES() + + def get_nb_cols(self): + return len(self.columns_ids) + + def get_nb_rows(self): + return len(self.rows) + + def is_empty(self): + return len(self.rows) == 0 + + def get_data_list( + self, + with_titles=False, + with_lines_titles=True, + with_bottom_titles=True, + omit_hidden_lines=False, + pdf_mode=False, # apply special pdf reportlab processing + pdf_style_list=[], # modified: list of platypus table style commands + ): + "table data as a list of lists (rows)" + T = [] + line_num = 0 # line number in input data + out_line_num = 0 # line number in output list + if with_titles and self.titles: + l = [] + if with_lines_titles: + if self.titles.has_key("row_title"): + l = [self.titles["row_title"]] + + T.append(l + [self.titles.get(cid, "") for cid in self.columns_ids]) + + for row in self.rows: + line_num += 1 + l = [] + if with_lines_titles: + if row.has_key("row_title"): + l = [row["row_title"]] + + if not (omit_hidden_lines and row.get("_hidden", False)): + colspan_count = 0 + col_num = len(l) + for cid in self.columns_ids: + colspan_count -= 1 + # if colspan_count > 0: + # continue # skip cells after a span + content = row.get(cid, "") or "" # nota: None converted to '' + colspan = row.get("_%s_colspan" % cid, 0) + if colspan > 1: + pdf_style_list.append( + ( + "SPAN", + (col_num, out_line_num), + (col_num + colspan - 1, out_line_num), + ) + ) + colspan_count = colspan + l.append(content) + col_num += 1 + if pdf_mode: + mk = row.get("_pdf_row_markup", []) # a list of tags + if mk: + l = mark_paras(l, mk) + T.append(l) + # + for cmd in row.get("_pdf_style", []): # relocate line numbers + pdf_style_list.append( + ( + cmd[0], + (cmd[1][0], cmd[1][1] + out_line_num), + (cmd[2][0], cmd[2][1] + out_line_num), + ) + + cmd[3:] + ) + + out_line_num += 1 + if with_bottom_titles and self.bottom_titles: + line_num += 1 + l = [] + if with_lines_titles: + if self.bottom_titles.has_key("row_title"): + l = [self.bottom_titles["row_title"]] + + T.append(l + [self.bottom_titles.get(cid, "") for cid in self.columns_ids]) + return T + + def get_titles_list(self): + "list of titles" + l = [] + return l + [self.titles.get(cid, "") for cid in self.columns_ids] + + def gen(self, format="html", columns_ids=None): + """Build representation of the table in the specified format. + See make_page() for more sophisticated output. + """ + if format == "html": + return self.html() + elif format == "xls": + return self.excel() + elif format == "text" or format == "csv": + return self.text() + elif format == "pdf": + return self.pdf() + elif format == "xml": + return self.xml() + elif format == "json": + return self.json() + raise ValueError("GenTable: invalid format: %s" % format) + + def _gen_html_row(self, row, line_num=0, elem="td", css_classes=""): + "row is a dict, returns a string ..." + if not row: + return "" # empty row + + if self.html_col_width: # XXXX Obsolete ? + std = ' style="width:%s;"' % self.html_col_width + else: + std = "" + + cla = css_classes + " " + row.get("_css_row_class", "") + if line_num % self.html_highlight_n: + cls = ' class="gt_hl %s"' % cla + else: + if cla: + cls = ' class="%s"' % cla + else: + cls = "" + H = ["" % (cls, row.get("_tr_attrs", ""))] + # titre ligne + if row.has_key("row_title"): + content = str(row["row_title"]) + help = row.get("row_title_help", "") + if help: + content = '%s' % ( + help, + content, + ) + H.append('") + r = [] + colspan_count = 0 + for cid in self.columns_ids: + if not cid in row and not self.html_generate_cells: + continue # skip cell + colspan_count -= 1 + if colspan_count > 0: + continue # skip cells after a span + content = row.get("_" + str(cid) + "_html", row.get(cid, "")) + if content is None: + content = "" + else: + content = str(content) + help = row.get("_%s_help" % cid, "") + if help: + target = row.get("_%s_target" % cid, "#") + else: + target = row.get("_%s_target" % cid, "") + cell_id = row.get("_%s_id" % cid, None) + if cell_id: + idstr = ' id="%s"' % cell_id + else: + idstr = "" + cell_link_class = row.get("_%s_link_class" % cid, "discretelink") + if help or target: + content = '%s' % ( + cell_link_class, + target, + help, + idstr, + content, + ) + klass = row.get("_%s_class" % cid, "") + if self.html_with_td_classes: + c = cid + else: + c = "" + if c or klass: + klass = ' class="%s"' % (" ".join((klass, c))) + else: + klass = "" + colspan = row.get("_%s_colspan" % cid, 0) + if colspan > 1: + colspan_txt = ' colspan="%d" ' % colspan + colspan_count = colspan + else: + colspan_txt = "" + r.append( + "<%s%s %s%s%s>%s" + % ( + elem, + std, + row.get("_%s_td_attrs" % cid, ""), + klass, + colspan_txt, + content, + elem, + ) + ) + + H.append("".join(r) + "") + return "".join(H) + + def html(self): + "Simple HTML representation of the table" + if self.is_empty() and self.html_empty_element: + return self.html_empty_element + "\n" + self.html_next_section + hid = ' id="%s"' % self.table_id + tablclasses = [] + if self.html_class: + tablclasses.append(self.html_class) + if self.sortable: + tablclasses.append("sortable") + if tablclasses: + cls = ' class="%s"' % " ".join(tablclasses) + else: + cls = "" + + if self.html_col_width: + std = ' style="width:%s;"' % self.html_col_width + else: + std = "" + H = [self.html_before_table, "" % (hid, cls)] + + line_num = 0 + # thead + H.append("") + if self.titles: + H.append( + self._gen_html_row( + self.titles, line_num, elem="th", css_classes="gt_firstrow" + ) + ) + # autres lignes à placer dans la tête: + for row in self.rows: + if row.get("_table_part") == "head": + line_num += 1 + H.append(self._gen_html_row(row, line_num)) # uses td elements + H.append("") + + H.append("") + for row in self.rows: + if row.get("_table_part", "body") == "body": + line_num += 1 + H.append(self._gen_html_row(row, line_num)) + H.append("") + + H.append("") + for row in self.rows: + if row.get("_table_part") == "foot": + line_num += 1 + H.append(self._gen_html_row(row, line_num)) + if self.bottom_titles: + H.append( + self._gen_html_row( + self.bottom_titles, + line_num + 1, + elem="th", + css_classes="gt_lastrow sortbottom", + ) + ) + H.append("") + + H.append("
      ' + content + "
      ") + + caption = self.html_caption or self.caption + if caption or self.base_url: + H.append('

      ') + if caption: + H.append(caption) + if self.base_url: + if self.xls_link: + H.append( + ' %s' + % (self.base_url, ICON_XLS) + ) + if self.xls_link and self.pdf_link: + H.append("  ") + if self.pdf_link: + H.append( + ' %s' + % (self.base_url, ICON_PDF) + ) + H.append("

      ") + + H.append(self.html_next_section) + return "\n".join(H) + + def excel(self, wb=None): + "Simple Excel representation of the table" + L = sco_excel.ScoExcelSheet(sheet_name=self.xls_sheet_name) + style_bold = sco_excel.Excel_MakeStyle(bold=True) + + L.cells += self.xls_before_table + L.set_style(style_bold, li=len(L.cells)) + L.append(self.get_titles_list()) + L.cells += [[x for x in line] for line in self.get_data_list()] + if self.caption: + L.append([]) # empty line + L.append([self.caption]) + if self.origin: + L.append([]) # empty line + L.append([self.origin]) + + return L.gen_workbook(wb=wb) + + def text(self): + "raw text representation of the table" + return "\n".join( + [ + self.text_fields_separator.join([x for x in line]) + for line in self.get_data_list() + ] + ) + + def pdf(self): + "PDF representation: returns a ReportLab's platypus Table instance" + r = [] + try: + PDFLOCK.acquire() + r = self._pdf() + finally: + PDFLOCK.release() + return r + + def _pdf(self): + """PDF representation: returns a list of ReportLab's platypus objects + (notably a Table instance) + """ + if not self.pdf_table_style: + LINEWIDTH = 0.5 + self.pdf_table_style = [ + ("FONTNAME", (0, 0), (-1, 0), self.preferences["SCOLAR_FONT"]), + ("LINEBELOW", (0, 0), (-1, 0), LINEWIDTH, Color(0, 0, 0)), + ("GRID", (0, 0), (-1, -1), LINEWIDTH, Color(0, 0, 0)), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ] + nb_cols = len(self.columns_ids) + if self.rows and self.rows[0].has_key("row_title"): + nb_cols += 1 + if not self.pdf_col_widths: + self.pdf_col_widths = (None,) * nb_cols + # + CellStyle = styles.ParagraphStyle({}) + CellStyle.fontSize = self.preferences["SCOLAR_FONT_SIZE"] + CellStyle.fontName = self.preferences["SCOLAR_FONT"] + CellStyle.leading = 1.0 * self.preferences["SCOLAR_FONT_SIZE"] # vertical space + LINEWIDTH = 0.5 + # + titles = ["%s" % x for x in self.get_titles_list()] + pdf_style_list = [] + Pt = [ + [Paragraph(SU(str(x)), CellStyle) for x in line] + for line in ( + self.get_data_list( + pdf_mode=True, + pdf_style_list=pdf_style_list, + with_titles=True, + omit_hidden_lines=True, + ) + ) + ] + pdf_style_list += self.pdf_table_style + # log('len(Pt)=%s' % len(Pt)) + # log( 'line lens=%s' % [ len(x) for x in Pt ] ) + # log( 'style=\n%s' % pdf_style_list) + col_min = min([x[1][0] for x in pdf_style_list]) + col_max = max([x[2][0] for x in pdf_style_list]) + lin_min = min([x[1][1] for x in pdf_style_list]) + lin_max = max([x[2][1] for x in pdf_style_list]) + # log('col_min=%s col_max=%s lin_min=%s lin_max=%s' % (col_min, col_max, lin_min, lin_max)) + T = Table(Pt, repeatRows=1, colWidths=self.pdf_col_widths, style=pdf_style_list) + + objects = [] + StyleSheet = styles.getSampleStyleSheet() + if self.pdf_title: + objects.append(Paragraph(SU(self.pdf_title), StyleSheet["Heading3"])) + if self.caption: + objects.append(Paragraph(SU(self.caption), StyleSheet["Normal"])) + objects.append(Spacer(0, 0.4 * cm)) + objects.append(T) + + return objects + + def xml(self): + """XML representation of the table. + The schema is very simple: + + + + +
      + The tag names and can be changed using + xml_outer_tag and xml_row_tag + """ + doc = jaxml.XML_document(encoding=SCO_ENCODING) + getattr(doc, self.xml_outer_tag)( + id=self.table_id, origin=self.origin or "", caption=self.caption or "" + ) + doc._push() + for row in self.rows: + doc._push() + row_title = row.get("row_title", "") + if row_title: + getattr(doc, self.xml_row_tag)(title=row_title) + else: + getattr(doc, self.xml_row_tag)() + for cid in self.columns_ids: + doc._push() + v = row.get(cid, "") + if v is None: + v = "" + getattr(doc, cid)(value=str(v)) + doc._pop() + doc._pop() + doc._pop() + return repr(doc) + + def json(self): + """JSON representation of the table. + """ + d = [] + for row in self.rows: + r = {} + for cid in self.columns_ids: + v = row.get(cid, None) + if v != None: + v = str(v) + r[cid] = v + d.append(r) + return json.dumps(d, encoding=SCO_ENCODING) + + def make_page( + self, + context, + title="", + format="html", + page_title="", + filename=None, + REQUEST=None, + javascripts=[], + with_html_headers=True, + publish=True, + init_qtip=False, + ): + """ + Build page at given format + This is a simple page with only a title and the table. + If not publish, does not set response header + """ + if not filename: + filename = self.filename + page_title = page_title or self.page_title + html_title = self.html_title or title + if format == "html": + H = [] + if with_html_headers: + H.append( + self.html_header + or context.sco_header( + REQUEST, + page_title=page_title, + javascripts=javascripts, + init_qtip=init_qtip, + ) + ) + if html_title: + H.append(html_title) + H.append(self.html()) + if with_html_headers: + H.append(context.sco_footer(REQUEST)) + return "\n".join(H) + elif format == "pdf": + objects = self.pdf() + doc = pdf_basic_page(objects, title=title, preferences=self.preferences) + if publish: + return sendPDFFile(REQUEST, doc, filename + ".pdf") + else: + return doc + elif format == "xls": + xls = self.excel() + if publish: + return sco_excel.sendExcelFile(REQUEST, xls, filename + ".xls") + else: + return xls + elif format == "text": + return self.text() + elif format == "csv": + return sendCSVFile(REQUEST, self.text(), filename + ".csv") + elif format == "xml": + xml = self.xml() + if REQUEST and publish: + REQUEST.RESPONSE.setHeader("content-type", XML_MIMETYPE) + return xml + elif format == "json": + js = self.json() + if REQUEST and publish: + REQUEST.RESPONSE.setHeader("content-type", JSON_MIMETYPE) + return js + else: + log("make_page: format=%s" % format) + raise ValueError("_make_page: invalid format") + + +# ----- +class SeqGenTable: + """Sequence de GenTable: permet de générer un classeur excel avec un tab par table. + L'ordre des tabs est conservé (1er tab == 1ere table ajoutée) + """ + + def __init__(self): + self.genTables = OrderedDict() + + def add_genTable(self, name, gentable): + self.genTables[name] = gentable + + def get_genTable(self, name): + return self.genTables.get(name) + + def excel(self): + """Export des genTables dans un unique fichier excel avec plusieurs feuilles tagguées""" + book = sco_excel.Workbook() # Le fichier xls en devenir + for (name, gt) in self.genTables.items(): + gt.excel(wb=book) # Ecrit dans un fichier excel + return book.savetostr() + + +# ----- Exemple d'utilisation minimal. +if __name__ == "__main__": + T = GenTable( + rows=[{"nom": "Toto", "age": 26}, {"nom": "Titi", "age": 21}], + columns_ids=("nom", "age"), + ) + print("--- HTML:") + print(T.gen(format="html")) + print("\n--- XML:") + print(T.gen(format="xml")) + print("\n--- JSON:") + print(T.gen(format="json")) diff --git a/html_sco_header.py b/html_sco_header.py new file mode 100644 index 00000000..c6f4dc05 --- /dev/null +++ b/html_sco_header.py @@ -0,0 +1,262 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +from sco_utils import * +from sco_formsemestre_status import formsemestre_page_title + +""" +HTML Header/Footer for ScoDoc pages +""" + + +# Some constants: + +# Multiselect menus are used on a few pages and not loaded by default +BOOTSTRAP_MULTISELECT_JS = [ + "libjs/bootstrap-3.1.1-dist/js/bootstrap.min.js", + "libjs/bootstrap-multiselect/bootstrap-multiselect.js", + "libjs/purl.js", +] + +BOOTSTRAP_MULTISELECT_CSS = [ + "libjs/bootstrap-3.1.1-dist/css/bootstrap.min.css", + "libjs/bootstrap-3.1.1-dist/css/bootstrap-theme.min.css", + "libjs/bootstrap-multiselect/bootstrap-multiselect.css", +] + +# Header: +def sco_header( + context, + REQUEST=None, + # optional args + container=None, # objet qui a lancé la demande + page_title="", # page title + no_side_bar=False, # hide sidebar + cssstyles=[], # additionals CSS sheets + javascripts=[], # additionals JS filenames to load + scripts=[], # script to put in page header + bodyOnLoad="", # JS + init_jquery=True, # load and init jQuery + init_jquery_ui=True, # include all stuff for jquery-ui and initialize scripts + init_qtip=False, # include qTip + init_google_maps=False, # Google maps + init_datatables=True, + titrebandeau="", # titre dans bandeau superieur + head_message="", # message action (petit cadre jaune en haut) + user_check=True, # verifie passwords temporaires +): + "Main HTML page header for ScoDoc" + + # If running for first time, initialize roles and permissions + try: + ri = context.roles_initialized + except: + ri = None # old instances does not have this attribute + if ri == "0": + context._setup_initial_roles_and_permissions() + + # context est une instance de ZScolar. container est une instance qui "acquiert" ZScolar + if container: + context = container # je pense que cela suffit pour ce qu'on veut. + + # Add a HTTP header (can be used by Apache to log requests) + if REQUEST.AUTHENTICATED_USER: + REQUEST.RESPONSE.setHeader("X-ScoDoc-User", str(REQUEST.AUTHENTICATED_USER)) + + # Get more parameters from REQUEST + if not head_message and REQUEST.form.has_key("head_message"): + head_message = REQUEST.form["head_message"] + + params = { + "page_title": page_title or context.title_or_id(), + "no_side_bar": no_side_bar, + "ScoURL": context.ScoURL(), + "encoding": SCO_ENCODING, + "titrebandeau_mkup": "", + "authuser": str(REQUEST.AUTHENTICATED_USER), + } + if bodyOnLoad: + params["bodyOnLoad_mkup"] = """onload="%s" """ % bodyOnLoad + else: + params["bodyOnLoad_mkup"] = "" + if no_side_bar: + params["margin_left"] = "1em" + else: + params["margin_left"] = "140px" + + if init_jquery_ui or init_qtip or init_datatables: + init_jquery = True + + H = [ + """ + + + +%(page_title)s + + + + + +""" + % params + ] + # jQuery UI + if init_jquery_ui: + # can modify loaded theme here + H.append( + '\n' + ) + if init_google_maps: + # It may be necessary to add an API key: + H.append( + '' + ) + + # Feuilles de style additionnelles: + for cssstyle in cssstyles: + H.append( + """\n""" + % cssstyle + ) + + H.append( + """ + + + + + + +""" + % params + ) + + # jQuery + if init_jquery: + H.append( + """ + """ + ) + H.append( + '' + ) + # qTip + if init_qtip: + H.append( + '' + ) + H.append( + '' + ) + + if init_jquery_ui: + H.append( + '' + ) + # H.append('') + H.append( + '' + ) + if init_google_maps: + H.append( + '' + ) + if init_datatables: + H.append( + '' + ) + H.append( + '' + ) + # JS additionels + for js in javascripts: + H.append( + """\n""" + % js + ) + + H.append( + """ +""" + % params + ) + # Scripts de la page: + if scripts: + H.append("""""") + + H.append("") + + # Body et bandeau haut: + H.append("""""" % params) + H.append(CUSTOM_HTML_HEADER) + # + if not no_side_bar: + H.append(context.sidebar(REQUEST)) + H.append("""
      """) + # + # Barre menu semestre: + H.append(formsemestre_page_title(context, REQUEST)) + + # Avertissement si mot de passe à changer + if user_check: + authuser = REQUEST.AUTHENTICATED_USER + passwd_temp = context.Users.user_info(user_name=str(authuser))["passwd_temp"] + if passwd_temp: + H.append( + """
      + Attention !
      + Vous avez reçu un mot de passe temporaire.
      + Vous devez le changer: cliquez ici +
      """ + % (context.ScoURL(), str(authuser)) + ) + # + if head_message: + H.append('
      ' + cgi.escape(head_message) + "
      ") + # + # div pour affichage messages temporaires + H.append('
      ') + # + return "".join(H) + + +def sco_footer(context, REQUEST=None): + """Main HTMl pages footer + """ + return """
      """ + CUSTOM_HTML_FOOTER + """""" diff --git a/html_sidebar.py b/html_sidebar.py new file mode 100644 index 00000000..cf379ce6 --- /dev/null +++ b/html_sidebar.py @@ -0,0 +1,185 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +from sco_utils import * +from ZAbsences import getAbsSemEtud + +""" +Génération de la "sidebar" (marge gauche des pages HTML) +""" + + +def sidebar_common(context, REQUEST=None): + "partie commune a toutes les sidebar" + authuser = REQUEST.AUTHENTICATED_USER + params = {"ScoURL": context.ScoURL(), "authuser": str(authuser)} + H = [ + 'ScoDoc', + '' + % params, + context.sidebar_dept(REQUEST), + """

      Scolarité

      + Semestres
      + Programmes
      + Absences
      + """ + % params, + ] + + if authuser.has_permission(ScoUsersAdmin, context) or authuser.has_permission( + ScoUsersView, context + ): + H.append( + """Utilisateurs
      """ + % params + ) + + if 0: # XXX experimental + H.append( + """Services
      """ + % params + ) + + if authuser.has_permission(ScoChangePreferences, context): + H.append( + """Paramétrage
      """ + % params + ) + + return "".join(H) + + +def sidebar(context, REQUEST=None): + "Main HTML page sidebar" + # rewritten from legacy DTML code + params = {"ScoURL": context.ScoURL(), "SCO_USER_MANUAL": SCO_USER_MANUAL} + + H = [' +""" + % params + ) # ' + # + return "".join(H) + + +def sidebar_dept(context, REQUEST=None): + """Partie supérieure de la marge de gauche + """ + infos = { + "BASE0": REQUEST.BASE0, + "DeptIntranetTitle": context.get_preference("DeptIntranetTitle"), + "DeptIntranetURL": context.get_preference("DeptIntranetURL"), + "DeptName": context.get_preference("DeptName"), + "ScoURL": context.ScoURL(), + } + + H = [ + """

      Dépt. %(DeptName)s

      + Accueil
      """ + % infos + ] + if infos["DeptIntranetURL"]: + H.append( + '%(DeptIntranetTitle)s
      ' + % infos + ) + H.append( + """
      Entreprises
      """ + % infos + ) + return "\n".join(H) diff --git a/htmlutils.py b/htmlutils.py new file mode 100644 index 00000000..aab3bbb7 --- /dev/null +++ b/htmlutils.py @@ -0,0 +1,72 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +############################################################################## +# +# Gestion scolarite IUT +# +# Copyright (c) 1999 - 2020 Emmanuel Viennet. All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Emmanuel Viennet emmanuel.viennet@viennet.net +# +############################################################################## + +"""Various HTML generation functions +""" + + +def horizontal_bargraph(value, mark): + """html drawing an horizontal bar and a mark + used to vizualize the relative level of a student + """ + tmpl = """ + + + + + """ + return tmpl % {"value": int(value), "mark": int(mark)} + + +import listhistogram + + +def histogram_notes(notes): + "HTML code drawing histogram" + if not notes: + return "" + bins, H = listhistogram.ListHistogram(notes, 21, minmax=(0, 20)) + D = ['
        • '] + left = 5 + colwidth = 16 # must match #q-graph li.bar width in stylesheet + if max(H) <= 0: + return "" + hfactor = 95.0 / max(H) # garde une marge de 5% pour l'esthetique + for i in range(len(H)): + if H[i] >= 0: + x = left + i * (4 + colwidth) + heightpercent = H[i] * hfactor + if H[i] > 0: + nn = "

          %d

          " % H[i] + else: + nn = "" + D.append( + '
        • %s

          %d

        • ' + % (x, heightpercent, nn, i) + ) + D.append("
      ") + return "\n".join(D) diff --git a/imageresize.py b/imageresize.py new file mode 100644 index 00000000..252a6f6d --- /dev/null +++ b/imageresize.py @@ -0,0 +1,29 @@ +"""Simple image resize using PIL""" + +from PIL import Image as PILImage +from cStringIO import StringIO + + +def ImageScale(img_file, maxx, maxy): + im = PILImage.open(img_file) + im.thumbnail((maxx, maxy), PILImage.ANTIALIAS) + out_file_str = StringIO() + im.save(out_file_str, im.format) + out_file_str.seek(0) + tmp = out_file_str.read() + out_file_str.close() + return tmp + + +def ImageScaleH(img_file, W=None, H=90): + im = PILImage.open(img_file) + if W is None: + # keep aspect + W = (im.size[0] * H) / im.size[1] + im.thumbnail((W, H), PILImage.ANTIALIAS) + out_file_str = StringIO() + im.save(out_file_str, im.format) + out_file_str.seek(0) + tmp = out_file_str.read() + out_file_str.close() + return tmp diff --git a/intervals.py b/intervals.py new file mode 100644 index 00000000..f7ac47d0 --- /dev/null +++ b/intervals.py @@ -0,0 +1,245 @@ +# -*- mode: python -*- +# -*- coding: utf-8 -*- + + +# Code from http://code.activestate.com/recipes/457411/ + +from bisect import bisect_left, bisect_right +from itertools import izip + + +class intervalmap(object): + """ + This class maps a set of intervals to a set of values. + + >>> i = intervalmap() + >>> i[0:5] = '0-5' + >>> i[8:12] = '8-12' + >>> print i[2] + 0-5 + >>> print i[10] + 8-12 + >>> print repr(i[-1]) + None + >>> print repr(i[17]) + None + >>> i[4:9] = '4-9' + >>> print [(j,i[j]) for j in range(6)] + [(0, '0-5'), (1, '0-5'), (2, '0-5'), (3, '0-5'), (4, '4-9'), (5, '4-9')] + >>> print list(i.items()) + [((0, 4), '0-5'), ((4, 9), '4-9'), ((9, 12), '8-12')] + >>> i[:0] = 'less than 0' + >>> i[-5] + 'less than 0' + >>> i[0] + '0-5' + >>> print list(i.items()) + [((None, 0), 'less than 0'), ((0, 4), '0-5'), ((4, 9), '4-9'), ((9, 12), '8-12')] + >>> i[21:] = 'more than twenty' + >>> i[42] + 'more than twenty' + >>> i[10.5:15.5] = '10.5-15.5' + >>> i[11.5] + '10.5-15.5' + >>> i[0.5] + '0-5' + >>> print list(i.items()) + [((None, 0),... ((9, 10.5), '8-12'), ((10.5, 15.5), '10.5-15.5'), ((21, None),... + >>> i = intervalmap() + >>> i[0:2] = 1 + >>> i[2:8] = 2 + >>> i[4:] = 3 + >>> i[5:6] = 4 + >>> i + {[0, 2] => 1, [2, 4] => 2, [4, 5] => 3, [5, 6] => 4, [6, None] => 3} + """ + + def __init__(self): + """ + Initializes an empty intervalmap. + """ + self._bounds = [] + self._items = [] + self._upperitem = None + + def __setitem__(self, _slice, _value): + """ + Sets an interval mapping. + """ + assert isinstance(_slice, slice), "The key must be a slice object" + + if _slice.start is None: + start_point = -1 + else: + start_point = bisect_left(self._bounds, _slice.start) + + if _slice.stop is None: + end_point = -1 + else: + end_point = bisect_left(self._bounds, _slice.stop) + + if start_point >= 0: + if ( + start_point < len(self._bounds) + and self._bounds[start_point] < _slice.start + ): + start_point += 1 + + if end_point >= 0: + self._bounds[start_point:end_point] = [_slice.start, _slice.stop] + if start_point < len(self._items): + self._items[start_point:end_point] = [ + self._items[start_point], + _value, + ] + else: + self._items[start_point:end_point] = [self._upperitem, _value] + else: + self._bounds[start_point:] = [_slice.start] + if start_point < len(self._items): + self._items[start_point:] = [self._items[start_point], _value] + else: + self._items[start_point:] = [self._upperitem] + self._upperitem = _value + else: + if end_point >= 0: + self._bounds[:end_point] = [_slice.stop] + self._items[:end_point] = [_value] + else: + self._bounds[:] = [] + self._items[:] = [] + self._upperitem = _value + + def __getitem__(self, _point): + """ + Gets a value from the mapping. + """ + assert not isinstance(_point, slice), "The key cannot be a slice object" + + index = bisect_right(self._bounds, _point) + if index < len(self._bounds): + return self._items[index] + else: + return self._upperitem + + def items(self): + """ + Returns an iterator with each item being + ((low_bound,high_bound), value). The items are returned + in order. + """ + previous_bound = None + for b, v in izip(self._bounds, self._items): + if v is not None: + yield (previous_bound, b), v + previous_bound = b + if self._upperitem is not None: + yield (previous_bound, None), self._upperitem + + def values(self): + """ + Returns an iterator with each item being a stored value. The items + are returned in order. + """ + for v in self._items: + if v is not None: + yield v + if self._upperitem is not None: + yield self._upperitem + + def __repr__(self): + s = [] + for b, v in self.items(): + if v is not None: + s.append("[%r, %r] => %r" % (b[0], b[1], v)) + return "{" + ", ".join(s) + "}" + + +if __name__ == "__main__": + # Test 1 + i = intervalmap() + i[9:] = "!" + assert repr(i) == "{[9, None] => '!'}" + i[:5] = "Hello" + i[6:7] = "World" + assert repr(i) == "{[None, 5] => 'Hello', [6, 7] => 'World', [9, None] => '!'}" + i[8:10] = "(Test)" + assert ( + repr(i) + == "{[None, 5] => 'Hello', [6, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}" + ) + i[:3] = "My," + assert ( + repr(i) + == "{[None, 3] => 'My,', [3, 5] => 'Hello', [6, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}" + ) + i[5.5:6] = "Cruel" + assert ( + repr(i) + == "{[None, 3] => 'My,', [3, 5] => 'Hello', [5.5, 6] => 'Cruel', [6, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}" + ) + i[6:6.5] = "And Harsh" + assert ( + repr(i) + == "{[None, 3] => 'My,', [3, 5] => 'Hello', [5.5, 6] => 'Cruel', [6, 6.5] => 'And Harsh', [6.5, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}" + ) + i[5.9:6.6] = None + assert ( + repr(i) + == "{[None, 3] => 'My,', [3, 5] => 'Hello', [5.5, 5.9000000000000004] => 'Cruel', [6.5999999999999996, 7] => 'World', [8, 10] => '(Test)', [10, None] => '!'}" + ) + assert " ".join(i.values()) == "My, Hello Cruel World (Test) !" + print("Test 1 OK") + + # Test 2 + i = intervalmap() + i[:0] = "A" + i[2:5] = "B" + i[8:10] = "C" + i[12:] = "D" + assert ( + repr(i) + == "{[None, 0] => 'A', [2, 5] => 'B', [8, 10] => 'C', [12, None] => 'D'}" + ) + i[:] = "K" + assert repr(i) == "{[None, None] => 'K'}" + assert i[5] == "K" + i[0:10] = "L" + i[6:8] = "M" + i[20:] = "J" + assert i[-1] == "K" + assert i[5] == "L" + assert i[7] == "M" + assert i[9] == "L" + assert i[15] == "K" + assert i[42] == "J" + print("Test 2 OK") + + # Test 3 + try: + from datetime import datetime + except: + print("Test 3 skipped") + else: + i = intervalmap() + i[: datetime(2005, 10, 24)] = "A" + i[datetime(2005, 11, 11) : datetime(2005, 11, 17)] = "B" + i[datetime(2005, 11, 30) :] = "C" + assert i[datetime(2005, 9, 25)] == "A" + assert i[datetime(2005, 10, 23)] == "A" + assert i[datetime(2005, 10, 26)] == None + assert i[datetime(2005, 11, 9)] == None + assert i[datetime(2005, 11, 16)] == "B" + assert i[datetime(2005, 11, 23)] == None + assert i[datetime(2005, 11, 29)] == None + assert i[datetime(2005, 11, 30)] == "C" + assert i[datetime(2005, 12, 3)] == "C" + print("Test 3 OK") + + try: + import doctest + except: + print("Skipping the doctests") + else: + print("And now, the doctests") + doctest.testmod(optionflags=doctest.ELLIPSIS) diff --git a/listhistogram.py b/listhistogram.py new file mode 100644 index 00000000..2e19a6b4 --- /dev/null +++ b/listhistogram.py @@ -0,0 +1,31 @@ +import math + + +def ListHistogram(L, nbins, minmax=None, normalize=None): + """Compute histogram of a list. + Does not use Numeric or numarray. + + H[i] is the number of elements from L + such that bins[i] <= L[i] < bins[i+1] + """ + n = len(L) + if minmax is None: + xmin = min(L) + xmax = max(L) + else: + xmin, xmax = minmax + # clip data + for i in range(n): + if L[i] < xmin: + L[i] = xmin + if L[i] > xmax: + L[i] = xmax + bin_width = (xmax - xmin) / float(nbins - 1) + H = [0] * nbins + for i in range(n): + idx = int(math.floor((L[i] - xmin) / bin_width)) + H[idx] += 1 + bins = [] + for i in range(nbins): + bins.append(xmin + bin_width * i) + return bins, H diff --git a/logos/logo_footer.jpg b/logos/logo_footer.jpg new file mode 100644 index 00000000..1afe1fe6 Binary files /dev/null and b/logos/logo_footer.jpg differ diff --git a/logos/logo_header.jpg b/logos/logo_header.jpg new file mode 100644 index 00000000..53117a9f Binary files /dev/null and b/logos/logo_header.jpg differ diff --git a/misc/ArreteDUT2005.txt b/misc/ArreteDUT2005.txt new file mode 100644 index 00000000..4cbf2943 --- /dev/null +++ b/misc/ArreteDUT2005.txt @@ -0,0 +1,88 @@ + +Source: http://www.education.gouv.fr/bo/2005/31/MENS0501754A.htm + +Extrait: +Titre III - Validation des parcours de formation + +Chapitre 2 - Contrôle des connaissances et déroulement des études + +Article 19 - Les unités d'enseignement sont définitivement acquises et +capitalisables dès lors que l'étudiant y a obtenu la moyenne. +L'acquisition de l'unité d'enseignement emporte l'acquisition +des crédits européens correspondants. + +Toute unité d'enseignement capitalisée est prise en compte dans le +dispositif de compensation, au même titre et dans les mêmes conditions +que les autres unités d'enseignement. + +Dans le cas de redoublement d'un semestre, si un étudiant ayant acquis +une unité d'enseignement souhaite, notamment pour améliorer les +conditions de réussite de sa formation, suivre les enseignements de +cette unité d'enseignement et se représenter au contrôle des +connaissances correspondant, la compensation prend en compte le +résultat le plus favorable pour l'étudiant. + + +Article 20 - La validation d'un semestre est acquise de droit lorsque +l'étudiant a obtenu à la fois: + +a) une moyenne générale égale ou supérieure à 10 sur 20 et une moyenne +égale ou supérieure à 8 sur 20 dans chacune des unités d'enseignement; + +b) la validation des semestres précédents, lorsqu'ils existent. +Lorsque les conditions posées ci-dessus ne sont pas remplies, la +validation est assurée, sauf opposition de l'étudiant, par une +compensation organisée entre deux semestres consécutifs sur la base +d'une moyenne générale égale ou supérieure à 10 sur 20 et d'une +moyenne égale ou supérieure à 8 sur 20 dans chacune des unités +d'enseignement constitutives de ces semestres. Le semestre servant à +compenser ne peut être utilisé qu'une fois au cours du cursus. + +En outre, le directeur de l'IUT peut prononcer la validation d'un +semestre sur proposition du jury. La validation de tout semestre +donne lieu à l'obtention de l'ensemble des unités d'enseignement qui +le composent et des crédits européens correspondants. + + +Article 21 - La poursuite d'études dans un nouveau semestre est de +droit pour tout étudiant à qui ne manque au maximum que la validation +d'un seul semestre de son cursus. + + +Article 22 - Le redoublement est de droit dans les cas où : + +- l'étudiant a obtenu la moyenne générale et lorsque celle-ci ne +suffit pas pour remplir la condition posée au a) de l'article 20 +ci-dessus; +- l'étudiant a rempli la condition posée au a) de l'article 20 +ci-dessus dans un des deux semestres utilisés dans le processus de +compensation. + +En outre, l'étudiant peut être autorisé à redoubler par décision du +directeur de l'IUT, sur proposition du jury de passage ou du jury de +délivrance pour l'obtention du diplôme universitaire de technologie. +Durant la totalité du cursus conduisant au diplôme universitaire de +technologie, l'étudiant ne peut être autorisé à redoubler plus de deux +semestres. En cas de force majeure dûment justifiée et appréciée par +le directeur de l'IUT, un redoublement supplémentaire peut être +autorisé. + +La décision définitive refusant l'autorisation de redoubler est prise +après avoir entendu l'étudiant à sa demande. Elle doit être motivée et +assortie de conseils d'orientation. + + +(...) + + +Chapitre 3 - Jurys, délivrance du diplôme et droits des étudiants + +Article 25 - Les unités d'enseignement dans lesquelles la moyenne de +10 a été obtenue sont capitalisables en vue de la reprise d'études en +formation continue. + +Les étudiants qui sortent de l'IUT sans avoir obtenu le diplôme +universitaire de technologie reçoivent une attestation d'études +comportant la liste des unités d'enseignement capitalisables qu'ils +ont acquises, ainsi que les crédits européens correspondants, délivrée +par le directeur de l'IUT. diff --git a/misc/Interface.xlsm b/misc/Interface.xlsm new file mode 100644 index 00000000..281f7f97 Binary files /dev/null and b/misc/Interface.xlsm differ diff --git a/misc/PublicationBulletins/Bulletins-Orleans/images/code.jpg b/misc/PublicationBulletins/Bulletins-Orleans/images/code.jpg new file mode 100644 index 00000000..95ff057d Binary files /dev/null and b/misc/PublicationBulletins/Bulletins-Orleans/images/code.jpg differ diff --git a/misc/PublicationBulletins/Bulletins-Orleans/images/info.png b/misc/PublicationBulletins/Bulletins-Orleans/images/info.png new file mode 100644 index 00000000..822a5132 Binary files /dev/null and b/misc/PublicationBulletins/Bulletins-Orleans/images/info.png differ diff --git a/misc/PublicationBulletins/Bulletins-Orleans/images/puce-iut-transparent.png b/misc/PublicationBulletins/Bulletins-Orleans/images/puce-iut-transparent.png new file mode 100644 index 00000000..d74977e6 Binary files /dev/null and b/misc/PublicationBulletins/Bulletins-Orleans/images/puce-iut-transparent.png differ diff --git a/misc/PublicationBulletins/Bulletins-Orleans/images/stop.png b/misc/PublicationBulletins/Bulletins-Orleans/images/stop.png new file mode 100644 index 00000000..2fe56626 Binary files /dev/null and b/misc/PublicationBulletins/Bulletins-Orleans/images/stop.png differ diff --git a/misc/PublicationBulletins/Bulletins-Orleans/index.php b/misc/PublicationBulletins/Bulletins-Orleans/index.php new file mode 100644 index 00000000..5e3259b1 --- /dev/null +++ b/misc/PublicationBulletins/Bulletins-Orleans/index.php @@ -0,0 +1,310 @@ + Menu Semestre => Modifier le semestre => Publication" soit cochée. + +// Code contribué par Yann Leboulanger (Université Paris 10), Juin 2013 +// et modifié par Pascal Legrand (Nov 2017) +// +// Exemple publication des bulletins de notes vers les étudiants +// L'étudiant est authenfié via le CAS +// Le bulletin est récupéré en format XML en interrogeant ScoDoc +// +// Il faut créer un utilisateur ScoDoc n'ayant que des droits de lecture. +// +// A adapter à  vos besoins locaux. + +include_once 'CAS.php'; +// *********************************************** CONFIGURATION *************************************************** +phpCAS::client(CAS_VERSION_2_0,'URL_CAS',443,''); +phpCAS::setNoCasServerValidation(); +phpCAS::forceAuthentication(); + +$nip = phpCAS::getUser(); + +// Login information of a scodoc user that can access notes +$sco_user = 'USER'; +$sco_pw = 'PASS'; +$sco_url = 'https://SERVEUR/ScoDoc/'; + +// URL où sont stockées les photos, si celle-ci diffère de "$sco_url". +// Cette valeur est concaténée avec la valeur de "etudiant['photo_url']". (/ScoDoc/static/photos/.....) +$photo_url = 'https://SERVEUR/ScoDoc/'; +// *********************************************** CONFIGURATION *************************************************** + +// ************************************************* FONCTIONS ***************************************************** +// Définition de la fonction d'encodage des headers +function http_build_headers( $headers ) { + $headers_brut = ''; + foreach( $headers as $nom => $valeur ) { + $headers_brut .= $nom . ': ' . $valeur . "\r\n"; + } + return $headers_brut; +} + +// Récupération du département +function get_dept($nip) { + global $sco_url; + $dept = file_get_contents( $sco_url . 'get_etud_dept?code_nip=' . $nip); + return ($dept); +} + +function get_EtudInfos_page($nip, $dept) { +// Récupération des informations concernant l'étudiant. +// Nécessite une authentification avec sco_user et sco_pw - Il est possible de choisir le format XML ou JSON. +// etud_info +// Paramètres: etudid ou code_nip ou code_ine +// Résultat: informations sur cet étudiant et les semestres dans lesquels il est (ou a été) inscrit. +// Exemple: etud_info?format=json&etudid=12345 + global $sco_user; + global $sco_pw; + global $sco_url; + $donnees = array('format' => 'xml', 'code_nip' => $nip, '__ac_name' => $sco_user, '__ac_password' => $sco_pw); + // Création du contenu brut de la requête + $contenu = http_build_query($donnees); + // Définition des headers + $headers = http_build_headers(array('Content-Type' => 'application/x-www-form-urlencoded', 'Content-Length' => strlen( $contenu))); + // Définition du contexte + $options = array('http' => array('method' => 'POST', 'content' => $contenu, 'header' => $headers)); + // Création du contexte + $contexte = stream_context_create($options); + // Envoi du formulaire POST + $retour = file_get_contents($sco_url . $dept . '/Scolarite/Notes/etud_info', false, $contexte); + return ($retour); +} + +function get_all_semestres($xml_data) +// Tous les semestres suivis par l'étudiant +{ + $data = array(); + $xml = simplexml_load_string($xml_data); + foreach ($xml->insemestre as $s) { + $sem = (array) $s['formsemestre_id']; + $data[] = $sem[0]; + } + return $data; +} + +function get_current_semestre($xml_data) +// Semestre courrant suivi par l'étudiant +{ + $xml = simplexml_load_string($xml_data); + foreach ($xml->insemestre as $s) { + if ($s['current'] == 1) + $sem = (array) $s['formsemestre_id']; + return ($sem[0]); + } +} + +function get_semestre_info($sem, $dept) { +// Renvoi les informations détaillées d'un semestre +// Ne nécessite pas d'authentification avec sco_user et sco_pw - Il est possible de choisir le format XML ou JSON. +// formsemestre_list +// Paramètres (tous optionnels): formsesmestre_id, formation_id, etape_apo, etape_apo2 +// Coquille dans la doc : formsesmestre_id +// Résultat: liste des semestres correspondant. +// Exemple: formsemestre_list?format=xml&etape_apo=V1RT + global $sco_pw; + global $sco_user; + global $sco_url; + $donnees = array('format' => 'xml', 'formsemestre_id' => $sem, '__ac_name' => $sco_user, '__ac_password' => $sco_pw); + // Création du contenu brut de la requête + $contenu = http_build_query( $donnees ); + // Définition des headers + $headers = http_build_headers( array('Content-Type' => 'application/x-www-form-urlencoded', 'Content-Length' => strlen( $contenu) ) ); + // Définition du contexte + $options = array( 'http' => array('method' => 'POST', 'content' => $contenu, 'header' => $headers ) ); + // Création du contexte + $contexte = stream_context_create($options); + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Notes/formsemestre_list', false, $contexte ); +/* + echo '

      '; + echo 'get_semestre_info : '; + echo '
      ' . htmlentities($retour) . '
      '; + echo '
      '; +*/ + return ($retour); +} + +function print_semestres_list($sems, $dept, $sem) { +// Affiche le nom (titre_num) de tous les semestres suivis par l'étudiant dans un formulaire + echo '
      ' . "\n"; + echo '
      ' . "\n"; + echo ' Liste des semestres' . "\n"; + echo '

      ' . "\n"; + echo ' ' . "\n"; + echo '
      ' . "\n"; + echo '
      ' . "\n"; + echo '

      ' . "\n"; + echo '

      ' . "\n"; + echo ' ' . "\n"; +} + +function get_bulletinetud_page($nip, $sem, $dept) { +// formsemestre_bulletinetud +// Paramètres: formsemestre_id, etudid, format (xml ou json), version (short, selectedevalsou long) +// Résultat: bulletin de notes +// Exemple: ici au format JSON, pour une version courte (version=short) + global $sco_user; + global $sco_pw; + global $sco_url; + $donnees = array('format' => 'xml', 'code_nip' => $nip, 'formsemestre_id' => $sem, 'version' => 'long', '__ac_name' => $sco_user, '__ac_password' => $sco_pw ); + // Création du contenu brut de la requête + $contenu = http_build_query( $donnees ); + // Définition des headers + $headers = http_build_headers( array('Content-Type' => 'application/x-www-form-urlencoded', 'Content-Length' => strlen( $contenu) ) ); + // Définition du contexte + $options = array( 'http' => array('method' => 'POST', 'content' => $contenu, 'header' => $headers ) ); + // Création du contexte + $contexte = stream_context_create($options); + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Notes/formsemestre_bulletinetud', false, $contexte ); + return ($retour); +} + +function print_semestre($xml_data, $sem, $dept, $show_moy) { + global $photo_url; + $xml = simplexml_load_string($xml_data); + echo '

      ' . $xml->etudiant['sexe'] . ' ' . $xml->etudiant['prenom'] . ' ' . $xml->etudiant['nom'] . '

      ' . "\n" . '
      ' . "\n"; + $retour = get_semestre_info($sem, $dept); + $xml2 = simplexml_load_string($retour); + $publie= $xml2->formsemestre['bul_hide_xml']; + if (isset($xml->absences)) { + (isset($xml->absences['nbabs'])) ? $nbabs = $xml->absences['nbabs']: $nbabs = 0; + (isset($xml->absences['nbabsjust'])) ? $nbabsjust = $xml->absences['nbabsjust']: $nbabsjust = 0; + echo ' Vous avez à  ce jour ' . $nbabs . ' demi-journée(s) d\'absences, dont ' . $nbabsjust . ' justifiée(s)
      ' . "\n"; + } + else { + echo ' Les absences ne sont pas saisies.
      ' . "\n"; + } + echo '

      ' . htmlentities($xml2->formsemestre['titre_num']) . '

      ' . "\n"; + if ($publie == 1) { + echo ' Publication des notes non activée sur ScoDoc pour ce semestre
      ' . "\n"; + } + else { + echo '
      ' . "\n"; + echo '
      ' . "\n"; + echo '
      " + titrebandeau + "
      ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + if ($show_moy) { + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + } + foreach ($xml->ue as $ue) { + $coef = 0; + foreach ($ue->module as $mod) { + $coef += (float) $mod['coefficient']; + } + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + if ($show_moy) { + echo ' ' . "\n"; + } + else { + echo ' ' . "\n"; + } + echo ' ' . "\n"; + foreach ($ue->module as $mod) { + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + if ($show_moy) { + echo ' ' . "\n"; + } + else { + echo ' ' . "\n"; + } + echo ' ' . "\n"; + echo ' ' . "\n"; + if (!$show_moy) { + foreach ($mod->evaluation as $eval) { + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + echo ' ' . "\n"; + } + } + } + } + echo '
      UEModuleEvaluationNote/20Coef
      Moyenne générale:' . $xml->note['value'] . '
      ' . $ue['acronyme'] . '
      ' . htmlentities($ue['titre']) . '
      ' . $ue->note['value'] . '' . $coef . '
      ' . htmlentities($mod['titre']) . '' . $mod->note['value'] . '' . $mod['coefficient'] . '
      ' . htmlentities($eval['description']) . '' . $eval->note['value'] . '
      ' . "\n"; + echo ' ' . "\n"; + echo '
      ' . "\n"; + if ($show_moy) { + echo $xml->situation . "\n"; + } + } +} +// ************************************************* FONCTIONS ***************************************************** + +// ************************************************** HTML ***************************************************** +echo' + + + Bulletins de notes + + + + + +'; + +$dept = get_dept($nip); +if ($dept) { + $etud_info = get_EtudInfos_page($nip, $dept); + $sems = get_all_semestres($etud_info); + $sem_current = get_current_semestre($etud_info); +// (Condition) ? : + (isset($_POST['sem'])) ? $sem = $_POST['sem']:$sem = $sem_current; + print_semestres_list($sems, $dept, $sem); + (!isset($_POST['notes_moy'])) ? $_POST['notes_moy']='notes':''; + echo '
      Affichage des ' . ucfirst($_POST['notes_moy']) . '' . "\n";; + $bulletin_page = get_bulletinetud_page($nip, $sem, $dept); + ($_POST['notes_moy'] == 'notes') ? print_semestre($bulletin_page, $sem, $dept, False):print_semestre($bulletin_page, $sem, $dept, True); +// ($sem == $sem_current) ? print_semestre($bulletin_page, $sem, $dept, False):print_semestre($bulletin_page, $sem, $dept, True); +} +else { + echo ' Numéro étudiant inconnu : ' . $nip . ' - Contactez votre Chef de département
      ' . "\n"; +} +$erreur=0; // Tout est OK +/* +echo '

      '; +echo 'get_etud_info : '; +echo '
      ' . htmlentities($etud_info) . '
      '; +echo 'sems : '; +echo '
      ' . print_r($sems) . '
      '; +echo 'sem_current : '; +echo '
      ' . htmlentities($sem_current) . '
      '; +echo 'get_bulletinetud_page : '; +echo '
      ' . htmlentities($bulletin_page) . '
      '; +echo '
      '; +*/ +echo '' . "\n"; +echo '' . "\n"; +// ************************************************** HTML ***************************************************** +?> diff --git a/misc/PublicationBulletins/Bulletins-Orleans/style.css b/misc/PublicationBulletins/Bulletins-Orleans/style.css new file mode 100644 index 00000000..d9de68f2 --- /dev/null +++ b/misc/PublicationBulletins/Bulletins-Orleans/style.css @@ -0,0 +1,419 @@ +html:after { + /* common custom values */ + content: "IUT de Chartres Specimen"; /* your site name */ + font-size: 720%; /* font size */ + color: rgba(0, 0, 0, .05); + /* alpha, could be even rgba(0,0,0,.02) */ + + /* rest of the logic */ + z-index: 9999; + cursor: default; + display: block; + position: fixed; + top: 33%; + right: 0; + bottom: 0; + left: 0; + font-family: Verdana, Geneva, sans-serif; + font-weight: bold; + font-style: italic; + text-align: center; + line-height: 100%; + + /* not sure about who implemented what .. + ... so bring it all */ + -webkit-pointer-events: none; + -moz-pointer-events: none; + -ms-pointer-events: none; + -o-pointer-events: none; + pointer-events: none; + + -webkit-transform: rotate(-45deg); + -moz-transform: rotate(-45deg); + -ms-transform: rotate(-45deg); + -o-transform: rotate(-45deg); + transform: rotate(-45deg); + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; +} + +body { + font-weight:light; + font-family:arial; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color: #4a6e91; + text-align: left; +} + +table { + border-collapse: collapse; + border: #4a6e91 1px solid; + width: 100%; + font-family : arial, verdana, sans-serif ; + font-size: 12px; +} + +td.titre { + border-top: #4a6e91 1px solid; + border-right: #4a6e91 1px solid; + border-left: #4a6e91 1px solid; + border-bottom: #4a6e91 1px solid; + font-weight:bold; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.titre_vide { + background-color: #dddddd; + border-top: #4a6e91 1px solid; + font-weight:bold; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.ue { + background-color: #dddddd; + border-top: #4a6e91 1px solid; + + font-weight:bold; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.ue_vide { + background-color: #dddddd; + border-right: #4a6e91 1px solid; + font-weight:bold; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.module { + background-color: #eeeeee; + border-top: #4a6e91 1px solid; + border-right: #4a6e91 0px solid; + border-left: #4a6e91 1px solid; + font-weight:bold; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.module_vide { + background-color: #eeeeee; + font-weight:light; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.evaluation { + background-color: #ffffff; + font-weight:light; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.evaluation_vide { + background-color: #eeeeee; + border-top: #4a6e91 1px solid; + font-weight:light; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.note { + background-color: #ffffff; + font-weight:light; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.note_vide { + background-color: #eeeeee; + border-top: #4a6e91 1px solid; + font-weight:light; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.coef { + background-color: #eeeeee; + border-top: #4a6e91 1px solid; + font-weight:bold; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.coef_ue { + background-color: #dddddd; + border-top: #4a6e91 1px solid; + border-bottom: #4a6e91 1px solid; + font-weight:bold; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.coef_vide { + background-color: #ffffff; + font-weight:light; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.moyennes { + background-color: #eeeeee; + border-top: #4a6e91 1px solid; + border-right: #4a6e91 1px solid; + font-weight:light; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +td.moyennes_bold { + background-color: #dddddd; + border-top: #4a6e91 1px solid; + font-weight:bold; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color:#4a6e91; + text-align: left; + text-decoration:none; + padding:2px; +} + +img { + vertical-align : middle; +} + +form { + display: table; + width: auto; /*matching the parents div width*/ + color: #333333; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; +} + +fieldset { + width: 100%; + background: #F6F6F6; + -webkit-border-radius: 8px; + -moz-border-radius: 8px; + border-radius: 8px; + border: 0; + background: -webkit-gradient(linear, left top, left bottom, from(#EFEFEF), to(#FFFFFF)); + background: -moz-linear-gradient(center top, #EFEFEF, #FFFFFF 100%); + box-shadow: 3px 3px 10px #ccc; + -moz-box-shadow: 3px 3px 10px #ccc; + -webkit-box-shadow: 3px 3px 10px #ccc; +} + +legend { + font-family: Verdana, Geneva, sans-serif; + font-size: 14px; + font-weight:bold; + background-color: #4F709F; + color: white; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + box-shadow: 2px 2px 4px #888; + -moz-box-shadow: 2px 2px 4px #888; + -webkit-box-shadow: 2px 2px 4px #888; + text-shadow: 1px 1px 1px #333; +} + +label { + color: #4a6e91; + font-family: Verdana, Geneva, sans-serif; + font-weight:bold; + font-size: 12px; + text-align: right; + height: 20px; + line-height: 20px; +} + +input, textarea, select { + border: 1px solid #d9d9d9; + font-family: Verdana, Geneva, sans-serif; + font-size: 12px; + color: #4a6e91; +} + +.submit { + font-family: Verdana, Geneva, sans-serif; + font-size: 14px; + font-weight:bold; + width: 100px; + border: 0; + background: #009900; + color: white; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + box-shadow: 2px 2px 4px #888; + -moz-box-shadow: 2px 2px 4px #888; + -webkit-box-shadow: 2px 2px 4px #888; + margin-bottom: 4px; + text-shadow: 1px 1px 1px #333; +} + +.bulletin { + width: 85%; + padding: 20px; + background: #F6F6F6; + -webkit-border-radius: 8px; + -moz-border-radius: 8px; + border-radius: 8px; + border: 0; + background: -webkit-gradient(linear, left top, left bottom, from(#EFEFEF), to(#FFFFFF)); + background: -moz-linear-gradient(center top, #EFEFEF, #FFFFFF 100%); + box-shadow: 3px 3px 10px #ccc; + -moz-box-shadow: 3px 3px 10px #ccc; + -webkit-box-shadow: 3px 3px 10px #ccc; +} + +.absences { + width: 75%; + padding: 20px; + background: #F6F6F6; + -webkit-border-radius: 8px; + -moz-border-radius: 8px; + border-radius: 8px; + border: 0; + background: -webkit-gradient(linear, left top, left bottom, from(#EFEFEF), to(#FFFFFF)); + background: -moz-linear-gradient(center top, #EFEFEF, #FFFFFF 100%); + box-shadow: 3px 3px 10px #ccc; + -moz-box-shadow: 3px 3px 10px #ccc; + -webkit-box-shadow: 3px 3px 10px #ccc; +} + +.info { + display: table; + width: auto; /*matching the parents div width*/ + padding: 10px; + font-family: Verdana, Geneva, sans-serif; + font-size: 14px; + font-weight:bold; + border: 0; + background: #4F709F; + color: white; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + box-shadow: 2px 2px 4px #888; + -moz-box-shadow: 2px 2px 4px #888; + -webkit-box-shadow: 2px 2px 4px #888; + margin-bottom: 4px; + text-shadow: 1px 1px 1px #333; +} + +.alert { + display: table; + width: auto; /*matching the parents div width*/ + padding: 10px; + font-family: Verdana, Geneva, sans-serif; + font-size: 14px; + font-weight:bold; + border: 0; + background: #4F709F; + color: white; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + box-shadow: 2px 2px 4px #888; + -moz-box-shadow: 2px 2px 4px #888; + -webkit-box-shadow: 2px 2px 4px #888; + margin-bottom: 4px; + text-shadow: 1px 1px 1px #333; +} + +.code { + border: 1px dotted black; + display: table; + width: auto; /*matching the parents div width*/ + padding: 10px; + font-family: Verdana, Geneva, sans-serif; + font-size: 14px; + font-weight:light; + font-style: italic; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + margin-bottom: 4px; +} + +.nbabs { + font-family: Verdana, Geneva, sans-serif; + font-size: 14px; + font-weight:bold; + color: #FF4500; +} + +.nbabsjust { + font-family: Verdana, Geneva, sans-serif; + font-size: 14px; + font-weight:bold; + color: #32CD32; +} + + diff --git a/misc/PublicationBulletins/ExemplePHP/index-abs.php b/misc/PublicationBulletins/ExemplePHP/index-abs.php new file mode 100644 index 00000000..06a28503 --- /dev/null +++ b/misc/PublicationBulletins/ExemplePHP/index-abs.php @@ -0,0 +1,761 @@ + $valeur ) { + $headers_brut .= $nom . ': ' . $valeur . "\r\n"; + } + + return $headers_brut; +} + + +function get_EtudAbs_page($nip, $dept,$beg_date) +{ + global $user_agent; + global $sco_user; + global $sco_pw; + global $sco_url; + $end_date=date("Y-m-d"); + $donnees = array( + 'format' => 'xml', + 'code_nip' => $nip, + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw, + 'beg_date' => $beg_date, + 'end_date' => $end_date); + + // Cration du contenu brut de la requte + $contenu = http_build_query( $donnees ); + + // Dfinition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Dfinition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Cration du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Absences/XMLgetAbsEtud', false, $contexte ); + + return ($retour); +} + + +function get_BilletAbs_list($nip, $dept) +{ + global $user_agent; + global $sco_user; + global $sco_pw; + global $sco_url; + $donnees = array( + 'format' => 'xml', + 'code_nip' => $nip, + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw, +); + + // Cration du contenu brut de la requte + $contenu = http_build_query( $donnees ); + + // Dfinition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Dfinition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Cration du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Absences/XMLgetBilletsEtud', false, $contexte ); + + return ($retour); +} + + +function Get_EtudAbs_billet($nip, $dept,$begin,$end,$description) +{ + global $user_agent; + global $sco_user; + global $sco_pw; + global $sco_url; + $end_date=date("Y-m-d"); +$justified="0"; + $donnees = array( + 'format' => 'xml', + 'code_nip' => $nip, + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw, + 'description' =>$description, + 'justified' =>$justified, + 'begin' => $begin, + 'end' => $end); + // Cration du contenu brut de la requte + $contenu = http_build_query( $donnees ); + + // Dfinition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Dfinition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Cration du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Absences/AddBilletAbsence', false, $contexte ); + + return ($retour); +} + + +function get_EtudInfos_page($nip, $dept) +{ + global $user_agent; + global $sco_user; + global $sco_pw; + global $sco_url; + + $donnees = array( + 'code_nip' => $nip, + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw ); + + // Cration du contenu brut de la requte + $contenu = http_build_query( $donnees ); + + // Dfinition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Dfinition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Cration du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Notes/XMLgetEtudInfos', false, $contexte ); + + return ($retour); +} + +function get_bulletinetud_page($nip, $sem, $dept) { + global $user_agent; + global $sco_user; + global $sco_pw; + global $sco_url; + $donnees = array( + 'format' => 'xml', + 'code_nip' => $nip, + 'formsemestre_id' => $sem, + 'version' => 'selectedevals', + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw ); + + // Cration du contenu brut de la requte + $contenu = http_build_query( $donnees ); + + // Dfinition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Dfinition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Cration du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Notes/formsemestre_bulletinetud', false, $contexte ); + + return ($retour); +} + +function get_semestre_info($sem, $dept) +{ + global $user_agent; + global $sco_user; + global $sco_pw; + global $sco_url; + $donnees = array( + 'formsemestre_id' => $sem, + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw ); + + // Cration du contenu brut de la requte + $contenu = http_build_query( $donnees ); + + // Dfinition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Dfinition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Cration du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Notes/XMLgetFormsemestres', false, $contexte ); + + return ($retour); +} + +function get_all_semestres($xml_data) +{ + $data = array(); + $xml = simplexml_load_string($xml_data); + foreach ($xml->insemestre as $s) { + $sem = (array) $s['formsemestre_id']; + $data[] = $sem[0]; + } + return $data; +} + +function get_current_semestre($xml_data) +{ + $xml = simplexml_load_string($xml_data); + foreach ($xml->insemestre as $s) { + if ($s['current'] == 1) + $sem = (array) $s['formsemestre_id']; + return ($sem[0]); + } +} + +function print_semestres_list($sems, $dept, $sem) +{ + echo 'Semestre : + +'; +} + +function print_semestre($xml_data, $sem, $dept, $show_moy=False) +{ + global $etudid; + global $nip; + global $sco_user; + global $sco_pw; + global $sco_url; + $xml = simplexml_load_string($xml_data); + $etudid= $xml->etudiant['etudid']; + + if (!$show_moy) { + echo '

      Les informations contenues dans ce tableau sont + provisoires. L'état n'a pas valeur de bulletin de notes.';} + + echo '
      Il vous appartient de contacter vos enseignants + ou votre dpartement en cas de dsaccord.

      '; + + echo '

      ' . $xml->etudiant['sexe'] . ' ' . $xml->etudiant['prenom'] . ' ' . $xml->etudiant['nom'] . '

      '; + //echo '
      '; + $retour = get_semestre_info($sem, $dept); + $xml2 = simplexml_load_string($retour); + $debut=date("Y-m-d",strtotime($xml2->formsemestre['dateord'])); + + echo ''.convertir_utf8($xml2->formsemestre['titre_num']).'
      '; + if (!$show_moy) { echo "vous avez ce jour ".convertir_utf8($xml->absences['nbabs'])." demi-journes d'absences dont ".convertir_utf8($xml->absences['nbabsjust']).' justifies';} + echo ' +
      +
      +'; + echo ' + + + + + + + + + +'; + if ($show_moy) { + echo ''; + } + foreach ($xml->ue as $ue) { + $coef = 0; + foreach ($ue->module as $mod) { + $coef += $mod['coefficient']; + } + echo ' + + + + +'; + + if ($show_moy) { + echo ' +'; + } + else { + echo ' + +'; + } + +echo ' +'; + foreach ($ue->module as $mod) { + echo ' + + + + +'; + + if ($show_moy) { + echo ' +'; + } + else { + echo ' +'; + } + + echo ' +'; + + if (!$show_moy) { + foreach ($mod->evaluation as $eval) { + echo ' + + + + + + +'; + } + } + } + } + echo '
      UECode ModuleModuleEvaluationNote/20(Min/Max)Coef
      Moyenne gnrale:' . $xml->note['value'] . '('.$xml->note['min'].'/'.$xml->note['max'].')
      -' . $ue['acronyme'] . '' . $ue->note['value'] . '('.$ue->note['min'].'/'.$ue->note['max'].')' . $coef . '
      ' . $mod['code'] . '' . convertir_utf8($mod['titre']) . '' . $mod->note['value'] . '('.$mod->note['min'].'/'.$mod->note['max'].')' . $mod['coefficient'] . '
      ' . convertir_utf8($eval['description']) . '' . $eval->note['value'] . '('.$eval->note['min'].'/'.$eval->note['max'].')(' . $eval['coefficient'] . ')
      +
      +'; +$code=$xml->decision['code']; + +$date_fin=$xml->decision['date_fin']; +echo $date_fin; + + if ($show_moy) { + echo "Situation sous rserve de validation par le jury :
      ".convertir_utf8($xml->situation); + } + else{if($code!=""){echo "Situation sous rserve de validation par le jury :
      ". convertir_utf8($xml->situation);}} + + + if (!$show_moy) { +echo ' + +

      Cliquez ici pour afficher/masquer la liste des absences du semestre:

      '; + + $retourabs = get_EtudAbs_page($nip, $dept,$debut); + $xmlabs = simplexml_load_string($retourabs); + + + + echo ' + '; + +echo ' +
      '; + +echo ' +

      Dclaration des motifs d'absences:

      '; + + echo ' + + + + + + + + + +
      Date et heure de dbut: + + + +
      Date et heure de fin: + + + +
      + + Motif: + +
      + + +
      +Veuillez indiquer les matires et enseignants concerns (pour les absences de courte dure).
      Apportez par ailleurs le numéro du billet affich dans le tableau ci aprs ainsi que vos justificatifs ventuels au secrtariat du dpartement.
      + + + +
      + +
      '; + + + +if (isset($_POST["begin"]) and isset($_POST["end"]) and isset($_POST["begtime"]) and isset($_POST["endtime"]) and isset($_POST["description"]) and $_POST["end"]>=$_POST["begin"]){ +$date1 = new DateTime($_POST["begin"]); +$date1->setTime(intval(substr($_POST["begtime"],0,2)), intval(substr($_POST["begtime"],-2))); + +$date2 = new DateTime($_POST["end"]); +$date2->setTime(intval(substr($_POST["endtime"],0,2)), intval(substr($_POST["endtime"],-2))); +Get_EtudAbs_billet($nip, $dept,$date1->format('Y-m-d H:i:s') , $date2->format('Y-m-d H:i:s') , $_POST["description"]);} + +echo ' +

      Billets d'absences déposés:

      '; + $retourbillets = get_BilletAbs_list($nip, $dept); + $xmlbillets = simplexml_load_string($retourbillets); + + echo ' + + + + + + +'; + +foreach ($xmlbillets->row as $billet) { + echo " +"; +} + +echo '
      Billet Du Au MotifSituation
      ". $billet->billet_id['value'] . ''. convertir_utf8($billet->abs_begin_str['value']). ' ' . convertir_utf8($billet->abs_end_str['value']) . ' ' . convertir_utf8($billet->description['value']) .' ' . convertir_utf8($billet->etat_str['value']) ."
      +
      '; + + +}} + + + + +function get_dept($nip) +{ + global $sco_url; + $dept = file_get_contents( $sco_url . 'get_etud_dept?code_nip=' . $nip); + return ($dept); +} + + +// function pour la recuperation des infos ldap +function search_results($info) { + foreach ($info as $inf) { + if (is_array($inf)) { + foreach ($inf as $key => $in) { + if ((count($inf[$key]) - 1) > 0) { + if (is_array($in)) { + unset($inf[$key]["count"]); + } + $results[$key] = $inf[$key]; + } + } + } + } + $results["dn"] = explode(',', $info[0]["dn"]); + return $results; +} + + +// Programme principal + + +echo ' + +Bulletin de notes + + + + + + + + + + '; + +echo " + + + + +"; + + +$user = $_SERVER['PHP_AUTH_USER']; +//echo 'USER: '.$user."\n"."
      "; + +//$user = "ei121713"; +//echo "On triche USER = ".$user."\n"."
      "; + +$ds = ldap_connect("ldap://ldap"); +if ($ds) { + $r = ldap_bind($ds); + $sr = ldap_search($ds, "ou=people,dc=univ-lehavre,dc=fr", "(&(objectClass=ulhEtudiant)(uid=$user))"); + $info = ldap_get_entries($ds, $sr); + + //echo $info["count"]." IS Search Result(s) for \"".$user."\"\n"; + $results = search_results($info); + // si pas de reponse de l a nnuaire, ce n est pas un etudiant + if ($info["count"] == 0 ) { + echo ' + + getEtud + + +

      Service de consultation des notes

      +
      + Il faut être etudiant de l'IUT pour accéder à ses notes. +
      + + '; + } else { + foreach ($results as $key => $result) { + if ($key == 'supannetuid' ) { + //echo " * ".$key." : \n"; + if (is_array($result)){ + foreach($result as $res){ + //echo " ".$res."\n"; + } + } + //echo "
      "; + $nip=$res; + } + } + } + ldap_close($ds); +} +// Login information of a scodoc user that can access notes +$sco_user = 'lecturenotes'; +$sco_pw = 'XXXXXXX'; +$sco_url = 'https://scodoc.XXXXX.fr/ScoDoc/'; + +$user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1) Gecko/20061010 Firefox/2.0'; + +echo '
      '; +if ($nip) { +$dept = get_dept($nip); +if ($dept) { + $retour = get_EtudInfos_page($nip, $dept); + $sems = get_all_semestres($retour); + $sem_current = get_current_semestre($retour); + if (isset($_POST["sem"])) { + $sem = $_POST["sem"]; + } + else { + $sem = $sem_current; + } + print_semestres_list($sems, $dept, $sem); + $retour = get_bulletinetud_page($nip, $sem, $dept); + if ($sem == $sem_current) { + print_semestre($retour, $sem, $dept, False); + } + else { + print_semestre($retour, $sem, $dept, True); + } + $erreur=0; // Tout est OK +} +else { + echo "Numro tudiant inconnu : " . $nip . ". Contactez votre Chef de dpartement."; +} +} + +echo '
      '; + + +echo ' + +'; + + +?> diff --git a/misc/PublicationBulletins/ExemplePHP/index.php b/misc/PublicationBulletins/ExemplePHP/index.php new file mode 100644 index 00000000..2ecefa6c --- /dev/null +++ b/misc/PublicationBulletins/ExemplePHP/index.php @@ -0,0 +1,320 @@ + $valeur ) { + $headers_brut .= $nom . ': ' . $valeur . "\r\n"; + } + + return $headers_brut; +} + +function get_EtudInfos_page($nip, $dept) +{ + global $sco_user; + global $sco_pw; + global $sco_url; + $donnees = array( + 'code_nip' => $nip, + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw ); + + // Création du contenu brut de la requête + $contenu = http_build_query( $donnees ); + + // Définition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Définition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Création du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Notes/XMLgetEtudInfos', false, $contexte ); + + return ($retour); +} + +function get_bulletinetud_page($nip, $sem, $dept) { + global $sco_user; + global $sco_pw; + global $sco_url; + $donnees = array( + 'format' => 'xml', + 'code_nip' => $nip, + 'formsemestre_id' => $sem, + 'version' => 'selectedevals', + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw ); + + // Création du contenu brut de la requête + $contenu = http_build_query( $donnees ); + + // Définition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Définition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Création du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Notes/formsemestre_bulletinetud', false, $contexte ); + + return ($retour); +} + +function get_semestre_info($sem, $dept) +{ + global $sco_user; + global $sco_pw; + global $sco_url; + $donnees = array( + 'formsemestre_id' => $sem, + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw ); + + // Création du contenu brut de la requête + $contenu = http_build_query( $donnees ); + + // Définition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Définition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Création du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Notes/XMLgetFormsemestres', false, $contexte ); + + return ($retour); +} + +function get_all_semestres($xml_data) +{ + $data = array(); + $xml = simplexml_load_string($xml_data); + foreach ($xml->insemestre as $s) { + $sem = (array) $s['formsemestre_id']; + $data[] = $sem[0]; + } + return $data; +} + +function get_current_semestre($xml_data) +{ + $xml = simplexml_load_string($xml_data); + foreach ($xml->insemestre as $s) { + if ($s['current'] == 1) + $sem = (array) $s['formsemestre_id']; + return ($sem[0]); + } +} + +function print_semestres_list($sems, $dept, $sem) +{ + echo 'Semestre : + +'; +} + +function print_semestre($xml_data, $sem, $dept, $show_moy=False) +{ + $xml = simplexml_load_string($xml_data); + echo '

      ' . $xml->etudiant['sexe'] . ' ' . $xml->etudiant['prenom'] . ' ' . $xml->etudiant['nom'] . '

      '; + echo '
      +'; + $retour = get_semestre_info($sem, $dept); + $xml2 = simplexml_load_string($retour); + echo $xml2->formsemestre['titre_num']; + echo ' +
      +
      +'; + echo ' + + + + + + + +'; + if ($show_moy) { + echo ''; + } + foreach ($xml->ue as $ue) { + $coef = 0; + foreach ($ue->module as $mod) { + $coef += $mod['coefficient']; + } + echo ' + + + +'; + + if ($show_moy) { + echo ' +'; + } + else { + echo ' +'; + } + +echo ' +'; + foreach ($ue->module as $mod) { + echo ' + + + +'; + + if ($show_moy) { + echo ' +'; + } + else { + echo ' +'; + } + + echo ' +'; + + if (!$show_moy) { + foreach ($mod->evaluation as $eval) { + echo ' + + + + + +'; + } + } + } + } + echo '
      UEModuleEvaluationNote/20Coef
      Moyenne générale:' . $xml->note['value'] . '
      -' . $ue['acronyme'] . '' . $ue->note['value'] . '' . $coef . '
      ' . $mod['code'] . '' . $mod->note['value'] . '' . $mod['coefficient'] . '
      ' . $eval['description'] . '' . $eval->note['value'] . '
      +
      +'; + if ($show_moy) { + echo $xml->situation; + } +} + +function get_dept($nip) +{ + global $sco_url; + $dept = file_get_contents( $sco_url . 'get_etud_dept?code_nip=' . $nip); + return ($dept); +} + + + +echo ' + +Bulletin de notes + + + + + + +'; + +echo '
      '; + +$dept = get_dept($nip); +if ($dept) { + $retour = get_EtudInfos_page($nip, $dept); + $sems = get_all_semestres($retour); + $sem_current = get_current_semestre($retour); + if (isset($_POST["sem"])) { + $sem = $_POST["sem"]; + } + else { + $sem = $sem_current; + } + print_semestres_list($sems, $dept, $sem); + $retour = get_bulletinetud_page($nip, $sem, $dept); + if ($sem == $sem_current) { + print_semestre($retour, $sem, $dept, False); + } + else { + print_semestre($retour, $sem, $dept, True); + } + $erreur=0; // Tout est OK +} +else { + echo "Numéro étudiant inconnu : " . $nip . ". Contactez votre Chef de département."; +} + +echo '
      '; + +echo ' +'; +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/Acces aux notes etudiants.pdf b/misc/PublicationBulletins/Portail-LeHavre/Acces aux notes etudiants.pdf new file mode 100644 index 00000000..7503b0ee Binary files /dev/null and b/misc/PublicationBulletins/Portail-LeHavre/Acces aux notes etudiants.pdf differ diff --git a/misc/PublicationBulletins/Portail-LeHavre/Billets_etudiants.pdf b/misc/PublicationBulletins/Portail-LeHavre/Billets_etudiants.pdf new file mode 100644 index 00000000..72d824e8 Binary files /dev/null and b/misc/PublicationBulletins/Portail-LeHavre/Billets_etudiants.pdf differ diff --git a/misc/PublicationBulletins/Portail-LeHavre/PDF_Billet.php b/misc/PublicationBulletins/Portail-LeHavre/PDF_Billet.php new file mode 100755 index 00000000..8bf7a7bf --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/PDF_Billet.php @@ -0,0 +1,151 @@ +AddPage(); + + +$PDF->SetFont('Arial','B',16); + +$fill = 1; +$PDF->SetFillColor(224,228,200); +$PDF->SetTextColor(0,0,0); + +$PDF->Text(20,15,"$semestre"); +$PDF->SetXY(20,20); +$PDF->MultiCell(180,5,"(du $debutsemestre au $finsemestre)",0,'C',0); + + +$PDF->SetXY(20,30); +$PDF->SetFont('Arial','',10); +$PDF->MultiCell(180,5,"Formulaire complter par l'tudiant. \n A faire signer par les enseignants et dposer au secrtariat sans attendre avec les justificatifs s'il y a lieu.",0,'C',0); +$PDF->SetFont('Arial','B',12); + +$PDF->Text(15,40,"$trait"); +$PDF->Text(20,50,"$etudiant"); +$PDF->Text(100,55,"Groupe (TD/TP): $groupe"); +$PDF->Text(20,55,"$numbillet",0,0,'L'); +$PDF->Text(15,60,"$trait"); +$PDF->SetFont('Arial','',11); +$PDF->Text(20,70,"$dates"); + +$PDF->Text(20,75,"Justificatif apport: Oui Non"); +$PDF->SetFont('Arial','B',11); +$PDF->Text(20,80,"Motif: "); +$PDF->SetFont('Arial','',10); +$PDF->SetXY(20,82); +$PDF->MultiCell(180,5,"$motif",1,'L',0); +$PDF->SetXY(20,122); + +// Dfinition des proprits du tableau. +$larccel=38; +$larccel2=55; +//$R=151;$G=190;$B=13; +$R=224;$G=228;$B=216; + +$proprietesTableau = array( + 'TB_ALIGN' => 'L', + 'L_MARGIN' => 1, + 'BRD_COLOR' => array(0,0,0), + 'BRD_SIZE' => '0.5', + ); + +// Definition des proprietes du header du tableau. +$proprieteHeader = array( + 'T_COLOR' => array(0,0,0), + 'T_SIZE' => 10, + 'T_FONT' => 'Arial', + 'T_ALIGN' => 'C', + 'V_ALIGN' => 'T', + 'T_TYPE' => 'B', + 'LN_SIZE' => 7, + 'BG_COLOR_COL0' => array($R, $G, $B), + 'BG_COLOR' => array($R, $G, $B), + 'BRD_COLOR' => array(0,0,0), + 'BRD_SIZE' => 0.2, + 'BRD_TYPE' => '1', + 'BRD_TYPE_NEW_PAGE' => '', + ); + +// Contenu du header du tableau. +$contenuHeader = array( + $larccel, $larccel, $larccel2, $larccel2, + "Matiere","Enseignant","Emargement Enseignant","Observations" + ); + + // Contenu du tableau. +$contenuTableau = array( + "", "", "","", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "","", + "", "", "", "", + 0, 0, 0, "", + ); +// Definition des proprits du reste du contenu du tableau. +$proprieteContenu = array( + 'T_COLOR' => array(0,0,0), + 'T_SIZE' => 10, + 'T_FONT' => 'Arial', + 'T_ALIGN_COL0' => 'L', + 'T_ALIGN' => 'R', + 'V_ALIGN' => 'M', + 'T_TYPE' => '', + 'LN_SIZE' => 6, + 'BG_COLOR_COL0' => array($R, $G, $B), + 'BG_COLOR' => array(255,255,255), + 'BRD_COLOR' => array(0,0,0), + 'BRD_SIZE' => 0.2, + 'BRD_TYPE' => '1', + 'BRD_TYPE_NEW_PAGE' => '', + ); + + +$PDF->drawTableau($PDF, $proprietesTableau, $proprieteHeader, $contenuHeader, $proprieteContenu, $contenuTableau); +$PDF->Text(15,180,"Indiquez ci-dessous les Devoirs surveills, contrles TP, interrogations crites concerns:"); +$PDF->SetXY(20,182); +$PDF->drawTableau($PDF, $proprietesTableau, $proprieteHeader, $contenuHeader, $proprieteContenu, $contenuTableau); +$PDF->SetXY(20,235); +$PDF->SetFont('Arial','I',10); +$PDF->MultiCell(180,3,'Je dclare avoir fait, ou faire expressment, le ncessaire pour rattraper tous les cours cits ci-dessus, tant au niveau des documents distribus, du droulement des sances de travail et dventuelles valuations. +La recevabilit de labsence sera apprcie par lquipe de direction.',0,'L',0); +$PDF->SetFont('Arial','B',11); +$PDF->SetFillColor(224,228,216); +$PDF->SetXY(20,260); +$PDF->MultiCell(180,5,"Partie rserve l'administration: +Absence justifie : Oui Non Autoris rattraper les contrles: Oui Non",1,'C',1); +$PDF->Text(15,250,"Signature du Directeur des tudes:"); +$PDF->Text(125,250,"Signature de l'tudiant:"); +$PDF->Text(80,290,"Imprim le ".date("d/n/Y H:i")); + + +$PDF->Ln(); +$PDF->Output(); +$PDF->Output($nom.".pdf", "D"); + + +?> +

       

      diff --git a/misc/PublicationBulletins/Portail-LeHavre/README.txt b/misc/PublicationBulletins/Portail-LeHavre/README.txt new file mode 100644 index 00000000..7a757dcd --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/README.txt @@ -0,0 +1,6 @@ +Portail-LeHavre + +Auteurs: Code contribué par Yann Leboulanger (Université Paris 10), Juin 2013 +Modifié par D. SOUDIERE avec le concours de Catherine Hatinguais + + diff --git a/misc/PublicationBulletins/Portail-LeHavre/css/radar_bulletin.css b/misc/PublicationBulletins/Portail-LeHavre/css/radar_bulletin.css new file mode 100644 index 00000000..a70e2517 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/css/radar_bulletin.css @@ -0,0 +1,90 @@ +/* # -*- mode: css -*- + ScoDoc, (c) Emmanuel Viennet 1998 - 2012 + */ + +div#radar_bulletin { + width: 500px; + margin-left: auto ; + margin-right: auto ; +} + +.radar { + font-family: sans-serif; + font-size: 12pt; +} + +.radar rect { + stroke: white; + fill: steelblue; +} + +path { + fill:none; + stroke:gray; +} + +path.radarnoteslines { + stroke: darkblue; + stroke-width: 1.5px; +} + +path.radarmoylines { + stroke: rgb(20,90,50);; + stroke-width: 1.25px; + stroke-dasharray: 8, 4; +} + +line.radarrad { + stroke: rgb(150, 150, 150); +} + +circle.radar_disk_tick { + stroke-width: 1; + stroke : rgb(150,150,150); + fill : none; +} +circle.radar_disk_red { + stroke-width: 0px; + fill: #FFC0CB; + fill-opacity: 0.5; +} + +path#radar_disk_tic_8 { + fill: #FF493C; + fill-opacity: 0.5; +} + +path#radar_disk_tic_10 { + fill: #FFC0CB; + fill-opacity: 0.5; + stroke-width: 1.5px; + stroke: #F44646; +} + +path#radar_disk_tic_20 { + fill: grey; + fill-opacity: 0.1; +} + +.radar_center_mark { + stroke: red; + stroke-width : 2; + fill: red; +} + +rect.radartip { + fill: #F5FB84; + font-size: 10pt; +} + +text.textaxis { + fill: red; + font-size: 8pt; + text-anchor: end; +} + +text.note_label { + fill: darkblue; + font-size: 10pt; + font-weight: bold; +} diff --git a/misc/PublicationBulletins/Portail-LeHavre/css/scodoc.css b/misc/PublicationBulletins/Portail-LeHavre/css/scodoc.css new file mode 100644 index 00000000..bc50a343 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/css/scodoc.css @@ -0,0 +1,1900 @@ +/* # -*- mode: css -*- + ScoDoc, (c) Emmanuel Viennet 1998 - 2012 + */ + +html,body { + margin:0; + padding:0; + width: 100%; + background-color: rgb(238,238,255); +} + +a:link { color: #008080 } +a:visited { color : #000080 } + + +@media print { +.noprint { + display:none; +} +} + +h1, h2, h3 { + font-family : arial, verdana, sans-serif ; +} + +/* ----- bandeau haut ------ */ +span.bandeaugtr { + width: 100%; + margin: 0; + border-width: 0; + padding-left: 160px; +/* background-color: rgb(17,51,85); */ +} +@media print { +span.bandeaugtr { + display:none; +} +} +tr.bandeaugtr { +/* background-color: rgb(17,51,85); */ + color: rgb(255,215,0); + /* font-style: italic; */ + font-weight: bold; + border-width: 0; + margin: 0; +} + +#authuser { + margin-top: 16px; +} + +#authuserlink { + color: rgb(255,0,0); + text-decoration: none; +} +#authuserlink:hover { + text-decoration: underline; +} +#deconnectlink { + font-size: 75%; + font-style: normal; + color: rgb(255,0,0); + text-decoration: underline; +} + +/* ----- page content ------ */ + +div.about-logo { + text-align: center; + padding-top: 10px; +} + + +div.head_message { + margin-top: 2px; + margin-bottom: 8px; + padding: 5px; + margin-left: auto; + margin-right: auto; + background-color: #ffff73; + -moz-border-radius: 8px; + -khtml-border-radius: 8px; + border-radius: 8px; + font-family : arial, verdana, sans-serif ; + font-weight: bold; + width: 40%; + text-align: center; +} + +div.passwd_warn { + font-weight: bold; + font-size: 200%; + background-color: #feb199; + width: 80%; + height: 200px; + text-align: center; + padding: 20px; + margin-left: auto; + margin-right: auto; + margin-top: 10px; +} + + +P.footer { + font-size: 80%; + color: rgb(60,60,60); + margin-top: 10px; + border-top: 1px solid rgb(60,60,60); +} + +/* ---- (left) SIDEBAR ----- */ + +div.sidebar { + position: absolute; + top: 5px; left: 5px; + width: 130px; + border: black 1px; + /* debug background-color: rgb(245,245,245); */ +} +@media print { +div.sidebar { + display:none; +} +} +a.sidebar:link { + color:#358; + text-decoration:none; +} +a.sidebar:visited { + color: #358; + text-decoration: none; +} +a.sidebar:hover { + color: rgb(153,51,51); + text-decoration: underline; +} + +a.scodoc_title { + color: rgb(102,102,102); + font-family: arial,verdana,sans-serif; + font-size: large; + font-weight: bold; + text-transform: uppercase; + text-decoration: none; +} +h2.insidebar { + color: rgb(102,102,102); + font-weight: bold; + font-size: large; + margin-bottom: 0; +} + +h3.insidebar { + color: rgb(102,102,102); + font-weight: bold; + font-size: medium; + margin-bottom: 0; + margin-top: 0; +} + +ul.insidebar { + padding-left: 1em; + list-style: circle; +} + + +.etud-insidebar { + font-size: small; + background-color: rgb(220,220,220); + width: 100%; + -moz-border-radius: 6px; + -khtml-border-radius: 6px; + border-radius: 6px; +} + +.etud-insidebar h2 { + color: rgb(153,51,51); + font-size: medium; +} + + +.etud-insidebar ul { + padding-left: 1.5em; + margin-left: 0; +} + +div.logo-insidebar { + padding-left: 1em; +} + +div.etud_info_div { + border: 4px solid gray; + height: 90px; + background-color: #f7f7ff; +} + +div.eid_left { + padding: 4px; + border: 0px; + vertical-align: top; + margin-right: 100px; +} + +span.eid_right { + padding: 0px; + border: 0px; + position: absolute; + right: 5px; + top: 5px; +} + +table.listesems th { + text-align: left; + padding-top: 0.5em; + padding-left: 0.5em; +} + +table.listesems td.semicon { + padding-left: 1.5em; +} + +table.listesems tr.firstsem td { + padding-top: 0.8em; +} + +h2.listesems { + padding-bottom: 0px; + margin-bottom: 0px; +} + +/* ----- Liste des news ----- */ + +div.news { + margin-top: 1em; + margin-bottom: 0px; + margin-right: 16px; + margin-left: 16px; + padding: 0.5em; + background-color: #F5F6CE; + -moz-border-radius: 8px; + -khtml-border-radius: 8px; + border-radius: 8px; +} + +span.newstitle { + font-family : arial, verdana, sans-serif ; + font-weight: bold; +} + +ul.newslist { + padding-left: 1em; + padding-bottom: 0em; + list-style: circle; +} + +span.newsdate { + padding-right: 2em; +} + +span.newstext { + font-style: italic; +} + +/* --- infos sur premiere page Sco --- */ +div.scoinfos { + margin-top: 0.5em; + margin-bottom: 0px; + padding: 2px; + padding-bottom: 0px; + background-color: #F4F4B2; +} + +/* ----- fiches etudiants ------ */ + +div.ficheEtud { + background-color: #f5edc8; /* rgb(255,240,128); */ + border: 1px solid gray; + width: 910px; + padding: 10px; + margin-top: 10px; +} + +div.menus_etud { + position: absolute; + margin-left: 1px; + margin-top: 1px; +} +div.ficheEtud h2 { + padding-top: 10px; +} + +div.code_nip { + padding-top: 10px; + font-family: "Andale Mono", "Courier"; +} + +div.fichesituation { + background-color: rgb( 231, 234, 218 ); /* E7EADA */ + margin: 0.5em 0 0.5em 0; + padding: 0.5em; + -moz-border-radius: 8px; + -khtml-border-radius: 8px; + border-radius: 8px; +} + +div.ficheadmission { + background-color: rgb( 231, 234, 218 ); /* E7EADA */ + margin: 0.5em 0 0.5em 0; + padding: 0.5em; + -moz-border-radius: 8px; + -khtml-border-radius: 8px; + border-radius: 8px; +} + +div.etudarchive ul { + padding:0; + margin:0; + margin-left: 1em; + list-style-type:none; +} + +div.etudarchive ul li { + background-image: url(/ScoDoc/static/icons/bullet_arrow.png); + background-repeat: no-repeat; + background-position: 0 .4em; + padding-left: .6em; +} +div.etudarchive ul li.addetudarchive { + background-image: url(/ScoDoc/static/icons/bullet_plus.png); + padding-left: 1.2em +} +span.etudarchive_descr { + margin-right: .4em; +} +span.deletudarchive { + margin-left: 0.5em; +} +div.fichedebouche { + background-color: rgb( 0, 100, 0 ); + color: gold; + margin: 0.5em 0 0.5em 0; + padding: 0.5em; + -moz-border-radius: 8px; + -khtml-border-radius: 8px; + border-radius: 8px; +} + +span.debouche_tit { + font-weight: bold; + padding-right: 1em; +} + +div.ficheinscriptions { + background-color: #eae3e2; /* was EADDDA */ + margin: 0.5em 0 0.5em 0; + padding: 0.5em; + -moz-border-radius: 8px; + -khtml-border-radius: 8px; + border-radius: 8px; +} + +.ficheinscriptions a.sem { + text-decoration: none; + font-weight: bold; + color: blue; +} + +.ficheinscriptions a.sem:hover { + color: red; +} + + +td.photocell { + padding-left: 32px; +} + +div.fichetitre { + font-weight: bold; +} + +td.fichetitre2 { + font-weight: bold; + vertical-align: top; +} + +td.fichetitre2 .formula { + font-weight: normal; + color: rgb(0,64,0); + border: 1px solid red; + padding-left: 1em; + padding-right: 1em; + padding-top: 3px; + padding-bottom: 3px; + margin-right: 1em; +} + +span.formula { + font-size: 80%; + font-family: Courier, monospace; + font-weight: normal; +} + +td.fichetitre2 .fl { + font-weight: normal; +} + +.ficheannotations { + background-color: #ddffdd; + margin: 0.5em 0 0.5em 0; + padding: 0.5em; + -moz-border-radius: 8px; + -khtml-border-radius: 8px; + border-radius: 8px; +} + +td.annodel { +} + +/* Page accueil Sco */ +span.infostitresem { + font-weight: normal; +} +span.linktitresem { + font-weight: normal; +} +span.linktitresem a:link {color: red;} +span.linktitresem a:visited {color: red;} + +.listegroupelink a:link { color: blue; } +.listegroupelink a:visited { color: blue; } +.listegroupelink a:hover { color: red; } + +a.stdlink, a.stdlink:visited { + color: blue; + text-decoration: underline; +} +a.stdlink:hover { + color: red; + text-decoration: underline; +} + +/* markup non semantique pour les cas simples */ + +.fontred { color: red; } +.fontorange { color: rgb(215, 90, 0); } +.fontitalic { font-style: italic; } +.redboldtext { + font-weight: bold; + color: red; +} + +.greenboldtext { + font-weight: bold; + color: green; +} + +a.redlink { + color: red; +} +a.redlink:hover { + color: rgb(153,51,51); + text-decoration: underline; +} + +a.discretelink { + color: black; + text-decoration: none; +} + +a.discretelink:hover { + color: rgb(153,51,51); + text-decoration: underline; +} + +.rightcell { + text-align: right; +} + +.rightjust { + padding-left: 2em; +} + +.centercell { + text-align: center; +} + +.help { + font-style: italic; +} + +p.indent { + padding-left: 2em; +} + +.blacktt { + font-family: Courier, monospace; + font-weight: normal; + color: black; +} + +p.msg { + color: red; + font-weight: bold; + border: 1px solid blue; + background-color: rgb(140,230,250); + padding: 10px; +} + +table.tablegrid { + border-color: black; + border-width: 0 0 1px 1px; + border-style: solid; + border-collapse: collapse; +} +table.tablegrid td, table.tablegrid th { + border-color: black; + border-width: 1px 1px 0 0; + border-style: solid; + margin: 0; + padding-left: 4px; + padding-right: 4px; +} + +/* ----- Notes ------ */ +a.smallbutton { + border-width: 0; + margin: 0; + margin-left: 2px; + text-decoration: none; +} +span.evallink { + font-size: 80%; + font-weight: normal; +} +.boldredmsg { + color: red; + font-weight: bold; +} + +tr.etuddem td { + color: rgb(100,100,100); + font-style: italic; +} + +td.etudabs, td.etudabs a.discretelink, tr.etudabs td.moyenne a.discretelink { + color: rgb(180,0,0); +} +tr.moyenne td { + font-weight: bold; +} + +table.notes_evaluation th.eval_complete a.sortheader { + color: green; +} +table.notes_evaluation th.eval_incomplete a.sortheader { + color: red; +} +table.notes_evaluation th.eval_attente a.sortheader { + color: rgb(215, 90, 0);; +} +table.notes_evaluation tr td a.discretelink:hover { + text-decoration: none; +} +table.notes_evaluation tr td.tdlink a.discretelink:hover { + color: red; + text-decoration: underline; +} +table.notes_evaluation tr td.tdlink a.discretelink, table.notes_evaluation tr td.tdlink a.discretelink:visited { + color: blue; + text-decoration: underline; +} + +table.notes_evaluation tr td { + padding-left: 0.5em; + padding-right: 0.5em; +} + +div.notes_evaluation_stats { + margin-top: -15px; +} + +.eval_description p { + margin-bottom: 5px; + margin-top: 5px; +} +span.eval_info { + font-style: italic; +} +span.eval_complete { + color: green; +} +span.eval_incomplete { + color: red; +} +span.eval_attente { + color: rgb(215, 90, 0); +} + +table.tablenote { + border-collapse: collapse; + border: 2px solid blue; +/* width: 100%;*/ + margin-bottom: 20px; + margin-right: 20px; +} +table.tablenote th { + padding-left: 1em; +} +.tablenote a { + text-decoration: none; + color: black; +} +.tablenote a:hover { + color: rgb(153,51,51); + text-decoration: underline; +} + +table.tablenote_anonyme { + border-collapse: collapse; + border: 2px solid blue; +} + +tr.tablenote { + border: solid blue 1px; +} +td.colnote { + text-align : right; + padding-right: 0.5em; + border: solid blue 1px; +} +td.colnotemoy { + text-align : right; + padding-right: 0.5em; + font-weight: bold; +} +td.colcomment, span.colcomment { + text-align: left; + padding-left: 2em; + font-style: italic; + color: rgb(80,100,80); +} + +h2.formsemestre, .gtrcontent h2 { + margin-top: 2px; + font-size: 130%; +} + +.formsemestre_page_title table.semtitle, .formsemestre_page_title table.semtitle td { + padding: 0px; + margin-top: 0px; + margin-bottom: 0px; + border-width: 0; + border-collapse: collapse; +} +.formsemestre_page_title table.semtitle { + /* width: 100%; */ +} + +.formsemestre_page_title { + width: 100%; + padding-top:5px; + padding-bottom: 10px; +} + +.formsemestre_page_title table.semtitle td.infos table { + padding-top: 10px; +} +.formsemestre_menubar { + border-top: 3px solid #67A7E3; + background-color: #D6E9F8; + margin-top: 8px; +} + +.formsemestre_menubar .barrenav ul li a.menu { + font-size: 12px; +} + +.formsemestre_page_title .infos span { + padding-right: 25px; +} + +.formsemestre_page_title span.semtitle { + font-size: 110%; +} +.formsemestre_page_title span.resp { + color: red; + font-weight: bold; +} +.formsemestre_page_title span.nbinscrits { + text-align: right; + font-weight: bold; + padding-right: 1px; +} +.formsemestre_page_title span.lock { + +} + +div.formsemestre_status { + -moz-border-radius: 8px; + -khtml-border-radius: 8px; + border-radius: 8px; + padding: 2px 6px 2px 16px; + margin-right: 10px; +} + +table.formsemestre_status { + border-collapse: collapse; +} +tr.formsemestre_status { background-color: rgb(90%,90%,90%); } +tr.formsemestre_status_green { background-color: #EFF7F2; } +tr.formsemestre_status_ue { background-color: rgb(90%,90%,90%); } +table.formsemestre_status td { + border-top: 1px solid rgb(80%,80%,80%); + border-bottom: 1px solid rgb(80%,80%,80%); + border-left: 0px; +} +table.formsemestre_status td.evals, table.formsemestre_status th.evals, table.formsemestre_status td.resp, table.formsemestre_status th.resp { + padding-left: 1em; +} + +table.formsemestre_status th { + font-weight: bold; + text-align: left; +} +th.formsemestre_status_inscrits { + font-weight: bold; + text-align: center; +} +td.formsemestre_status_code { + width: 2em; +} +a.formsemestre_status_link { + text-decoration:none; + color: black; +} +a.formsemestre_status_link:hover { + color: rgb(153,51,51); + text-decoration: underline; +} + +td.formsemestre_status_inscrits { + text-align: center; +} +td.formsemestre_status_cell { + white-space: nowrap; +} + +span.status_ue_acro { font-weight: bold; } +span.status_ue_title { font-style: italic; padding-left: 1cm;} + +table.formsemestre_inscr td { + padding-right: 1.25em; +} + +ul.ue_inscr_list li span.tit { + font-weight: bold; +} +ul.ue_inscr_list li.tit { + padding-top: 1ex; +} +ul.ue_inscr_list li.etud { + padding-top: 0.7ex; +} + +/* Liste des groupes sur tableau bord semestre */ +.formsemestre_status h3 { + border: 0px solid black; + margin-bottom: 5px; +} + +#grouplists h4 { + font-style: italic; + margin-bottom: 0px; + margin-top: 5px; +} + +#grouplists table { + //border: 1px solid black; + border-spacing: 1px; +} + +#grouplists td { +} + + +/* Modules */ +div.moduleimpl_tableaubord { + padding: 7px; + border: 2px solid gray; +} + +table.moduleimpl_evaluations { + width: 100%; + border-spacing: 0px; +} + +th.moduleimpl_evaluations { + font-weight: normal; + text-align: left; + color: red; +} + +th.moduleimpl_evaluations a, th.moduleimpl_evaluations a:visited { + font-weight: normal; + color: red; + text-decoration: none; +} +th.moduleimpl_evaluations a:hover { + text-decoration: underline; +} + +tr.moduleimpl_evaluation_row { +} + +td.moduleimpl_evaluation_row { +} + +tr.mievr { + background-color:#eeeeee; +} + +tr.mievr_rattr { + background-color:#dddddd; +} +span.mievr_rattr { + font-weight: bold; + color: blue; + margin-left: 2em; + border: 1px solid red; + padding: 1px 3px 1px 3px; +} + +tr.mievr td.mievr_tit { + font-weight: bold; + background-color: #cccccc; +} +tr.mievr td { + text-align: left; + background-color:white; +} +tr.mievr th { + background-color:white; +} +tr.mievr td.mievr { + width: 90px; +} +tr.mievr td.mievr_menu { + width: 110px; +} +tr.mievr td.mievr_dur { + width: 60px; +} +tr.mievr td.mievr_coef { + width: 60px; +} +tr.mievr td.mievr_nbnotes { + width: 90px; +} +tr td.mievr_grtit { + vertical-align: top; + text-align: right; + font-weight: bold; +} +span.mievr_lastmodif { + padding-left: 2em; + font-weight: normal; + font-style: italic; +} +a.mievr_evalnodate { + color: rgb(215, 90, 0); + font-style: italic; + text-decoration: none; +} +a.mievr_evalnodate:hover { + color: rgb(153,51,51); + text-decoration: underline; +} +span.evalindex { + font-weight: normal; + padding-left: 4em; +} + +/* Formulaire edition des partitions */ +form#editpart table { + border: 1px solid gray; + border-collapse: collapse; +} +form#editpart tr.eptit th { + font-size: 110%; + border-bottom: 1px solid gray; +} +form#editpart td { + border-bottom: 1px dashed gray; +} +form#editpart table td { + padding-left: 1em; +} +form#editpart table td.epnav { + padding-left: 0; +} + +/* Liste des formations */ +ul.notes_formation_list { + list-style-type: none; + font-size: 110%; +} +li.notes_formation_list { + padding-top: 10px; +} + +/* Presentation formation (ue_list) */ +div.formation_descr { + background-color: rgb(250,250,240); + padding-left: 0.7em; + margin-right: 1em; +} +div.formation_descr span.fd_t { + font-weight: bold; + margin-right: 1em; +} + +div.ue_list_tit { + font-weight: bold; + margin-top: 5px; +} + +ul.notes_ue_list { + background-color: rgb(240,240,240); + font-weight: bold; + margin-top: 4px; + margin-right: 1em; +} + +span.ue_code { + font-family: Courier, monospace; + font-weight: normal; + color: black; + font-size: 80%; +} + +span.ue_type { + color: green; + margin-left: 1.5em; + margin-right: 1.5em; +} + +ul.notes_matiere_list { + background-color: rgb(220,220,220); + font-weight: normal; + font-style: italic; +} + +ul.notes_module_list { + background-color: rgb(210,210,210); + font-weight: normal; + font-style: normal; +} + +.notes_ue_list a.discretelink, .notes_ue_list a.stdlink { + color: #001084; + text-decoration: underline; +} +.notes_ue_list span.locked { + font-weight: normal; +} + +.notes_ue_list a.smallbutton img { + position: relative; + top: 2px; +} + +div#ue_list_code { + background-color: rgb(220,220,220); + font-size: small; + padding-left: 4px; + padding-bottom: 1px; + margin-top: 10px; + margin: 3ex; +} + +ul.notes_module_list { + list-style-type: none; +} + +div#ue_list_etud_validations { + background-color: rgb(220,250,220); + padding-left: 4px; + padding-bottom: 1px; + margin: 3ex; +} +div#ue_list_etud_validations span { + font-weight: bold; +} + +span.ue_share { + font-weight: bold; +} + +div.ue_warning { + border: 1px solid red; + background-color: rgb(250,220,220); + margin: 3ex; + padding-left: 1ex; + padding-right: 1ex; +} + +div.ue_warning span:before { + content: url(/ScoDoc/static/icons/warning_img.png); + vertical-align: -80%; +} + +div.ue_warning span { + font-weight: bold; +} + +/* tableau recap notes */ +table.notes_recapcomplet { + border: 2px solid blue; + border-spacing: 0px 0px; + border-collapse: collapse; + white-space: nowrap; +} +tr.recap_row_even { + background-color: rgb(210,210,210); +} +@media print { +tr.recap_row_even { /* bordures noires pour impression */ + border-top: 1px solid black; + border-bottom: 1px solid black; +} +} +tr.recap_row_moy { + border-top: 1px solid blue; + font-weight: bold; +} +tr.recap_row_min, tr.recap_row_max { + font-weight: normal; + font-style: italic; +} +tr.recap_row_max { + border-bottom: 1px solid blue; +} +td.recap_tit { + font-weight: bold; + text-align: left; + padding-right: 1.2em; +} +td.recap_tit_ue { + font-weight: bold; + text-align: left; + padding-right: 1.2em; + padding-left: 2px; + border-left: 1px solid blue; +} +td.recap_col { + padding-right: 1.2em; + text-align: left; +} +td.recap_col_moy { + padding-right: 1.5em; + text-align: left; + font-weight: bold; + color: rgb(80,0,0); +} +td.recap_col_moy_inf { + padding-right: 1.5em; + text-align: left; + font-weight: bold; + color: rgb(255,0,0); +} +td.recap_col_ue { + padding-right: 1.2em; + padding-left: 4px; + text-align: left; + font-weight: bold; + border-left: 1px solid blue; +} +td.recap_col_ue_inf { + padding-right: 1.2em; + padding-left: 4px; + text-align: left; + font-weight: bold; + color: rgb(255,0,0); + border-left: 1px solid blue; +} +td.recap_col_ue_val { + padding-right: 1.2em; + padding-left: 4px; + text-align: left; + font-weight: bold; + color: rgb(0,140,0); + border-left: 1px solid blue; +} +/* noms des etudiants sur recap complet */ +table.notes_recapcomplet a:link, table.notes_recapcomplet a:visited { + text-decoration: none; + color: black; +} + +table.notes_recapcomplet a:hover { + color: red; + text-decoration: underline; +} + +/* bulletin */ +table.notes_bulletin { + border-collapse: collapse; + border: 2px solid rgb(100,100,240); + width: 100%; + margin-right: 100px; + background-color: rgb(240,250,255); + font-family : arial, verdana, sans-serif ; + font-size: 13px; +} + +tr.notes_bulletin_row_gen { + border-top: 1px solid black; + font-weight: bold; +} +tr.notes_bulletin_row_rang { + font-weight: bold; +} + +tr.notes_bulletin_row_ue { + /* background-color: rgb(170,187,204); voir sco_utils.UE_COLORS */ + font-weight: bold; + border-top: 1px solid black; +} + +tr.bul_row_ue_cur { + background-color: rgb(180,180,180); + color: rgb(50,50,50); +} + +tr.bul_row_ue_cap { + background-color: rgb(150,170,200); + color: rgb(50,50,50); +} + +tr.notes_bulletin_row_mat { + border-top: 2px solid rgb(140,140,140); + color: blue; +} + +tr.notes_bulletin_row_mod { + border-top: 1px solid rgb(140,140,140); +} + +tr.notes_bulletin_row_mod td.titre, tr.notes_bulletin_row_mat td.titre { + padding-left: 1em; +} + +tr.notes_bulletin_row_eval { + font-style: italic; + color: rgb(60,60,80); +} +tr.notes_bulletin_row_eval_incomplete .discretelink { + color: rgb(200,0,0); +} + +tr.b_eval_first td { + border-top: 1px dashed rgb(170,170,170); +} +tr.b_eval_first td.titre { + border-top: 0px; +} +tr.notes_bulletin_row_eval td.module { + padding-left: 5px; + border-left: 1px dashed rgb(170,170,170); +} + +table.notes_bulletin td.note { + padding-left: 1em; +} +table.notes_bulletin td.min, table.notes_bulletin td.max { + font-size: 80%; +} + +table.notes_bulletin tr.notes_bulletin_row_ue_cur td.note, table.notes_bulletin tr.notes_bulletin_row_ue_cur td.min, table.notes_bulletin tr.notes_bulletin_row_ue_cur td.max { + color: rgb(80,80,80); + font-style: italic; +} + +.note_bold { + font-weight: bold; +} +td.bull_coef_eval, td.bull_nom_eval { + font-style: italic; + color: rgb(60,60,80); +} +tr.notes_bulletin_row_eval td.note { + font-style: italic; + color: rgb(40,40,40); + font-size: 90%; +} + +tr.notes_bulletin_row_eval td.note .note_nd { + font-weight: bold; + color: red; +} + +/* --- Bulletins UCAC */ +tr.bul_ucac_row_tit, tr.bul_ucac_row_ue, tr.bul_ucac_row_total, tr.bul_ucac_row_decision, tr.bul_ucac_row_mention { + font-weight: bold; + border: 1px solid black; +} +tr.bul_ucac_row_tit { + background-color: rgb(170,187,204); +} +tr.bul_ucac_row_total, tr.bul_ucac_row_decision, tr.bul_ucac_row_mention { + background-color: rgb(220,220,220); +} + +/* ---- /ucac */ + +span.bul_minmax { + font-weight: normal; + font-size: 66%; +} +span.bul_minmax:before { + content: " "; +} + +a.bull_link { + text-decoration:none; + color: rgb(20,30,30); +} +a.bull_link:hover { + color: rgb(153,51,51); + text-decoration: underline; +} + +table.bull_head { + width: 100%; +} +td.bull_photo { + text-align: right; +} + +div.bulletin_menubar { + padding-left: 25px; +} + +.bull_liensemestre { + font-weight: bold; +} +.bull_liensemestre a { + color: rgb(255,0,0); + text-decoration: none; +} +.bull_liensemestre a:hover { + color: rgb(153,51,51); + text-decoration: underline; +} + +div.bull_appreciations { +} + +.bull_appreciations p { + margin: 0; + font-style: italic; +} +.bull_appreciations_link { + margin-left: 1em; +} +span.bull_appreciations_date { + margin-right: 1em; + font-style: normal; + font-size: 75%; +} + +div.eval_description { + color: rgb(20,20,20); + /* border: 1px solid rgb(30,100,0); */ + padding: 3px; +} + + +/* Saisie des notes */ +div.saisienote_etape1 { + border: 2px solid blue; + padding: 5px; + background-color: rgb( 231, 234, 218 ); /* E7EADA */ +} +div.saisienote_etape2 { + border: 2px solid green; + margin-top: 1em; + padding: 5px; + background-color: rgb(234,221,218); /* EADDDA */ +} +span.titredivsaisienote { + font-weight: bold; + font-size: 115%; +} + +/* ----- Absences ------ */ +td.matin { + background-color: rgb(203,242,255); +} + +td.absent { + background-color: rgb(255,156,156); +} + +td.present { + background-color: rgb(230,230,230); +} + +span.capstr { + color: red; +} + +/* ----- Formulator ------- */ +ul.tf-msg { + color: rgb(6,80,18); + border: 1px solid red; +} + +li.tf-msg { + list-style-image: url(/ScoDoc/static/icons/warning16_img.png); + padding-top: 5px; + padding-bottom: 5px; +} + +p.warning { + font-weight: bold; + color: red; +} +/* ---- ? ne fonctionne pas : p.warning:before { /ScoDoc/static/icons/warning16_img"); } */ + +form.sco_pref table.tf { + border-spacing: 5px 15px; +} + +td.tf-ro-fieldlabel { + /* font-weight: bold; */ + vertical-align:top; + margin-top: 20px; +} +td.tf-ro-fieldlabel:after { + content: ' :'; +} +td.tf-ro-field { + vertical-align:top; +} +div.tf-ro-textarea { + border: 1px solid grey; + padding-left: 8px; +} +select.tf-selglobal { + margin-left: 10px; +} + +td.tf-fieldlabel { + /* font-weight: bold; */ + vertical-align:top; +} + +.tf-comment { + font-size: 80%; + font-style: italic; +} + +.tf-explanation { + font-style: italic; +} + +.radio_green { + background-color: green; +} + +.radio_red { + background-color: red; +} + +td.fvs_val { + border-left: 1px solid rgb(80,80,80); + text-align: center; + padding-left: 1em; + padding-right: 1em; +} + +td.fvs_val_inf { + border-left: 1px solid rgb(80,80,80); + text-align: center; + padding-left: 1em; + padding-right: 1em; + color: red; +} + +td.fvs_chk { +} + +td.fvs_tit { + font-weight: bold; + text-align: left; + border-left: 1px solid rgb(80,80,80); + text-align: center; + padding-left: 1em; + padding-right: 1em; +} +td.fvs_tit_chk { + font-weight: bold; +} + +/* ----- Entreprises ------- */ + +table.entreprise_list, table.corr_list, table.contact_list { + width : 100%; + border-width: 0px; + /* border-style: solid; */ + border-spacing: 0px 0px; + padding: 0px; +} +tr.entreprise_list_even, tr.corr_list_even, tr.contact_list_even { + background-color: rgb(85%,85%,95%); +} +tr.entreprise_list_odd, tr.corr_list_odd, tr.contact_list_odd { + background-color: rgb(90%,90%, 90%); +} + +td.entreprise_descr, td.corr_descr, td.contact_descr { + padding-left: 2em; +} +td.entreprise_descr_link { + padding-left: 2em; + white-space: nowrap; +} +td.entreprise_descr_name { white-space: nowrap; } + +a.entreprise_delete { color: black; text-decoration: underline; } +a.entreprise_delete:visited { color: black; } + +a.entreprise_edit { + text-decoration: underline; + color : rgb(0,0,204); + font-weight: normal; +} +a.entreprise_edit:visited { color : rgb(0,0,204); } +a.entreprise_edit:hover { + color: rgb(153,51,51); + text-decoration: underline; +} + +p.entreprise_create { padding-top: 2em; } +a.entreprise_create { color : black; font-weight: bold; } +a.entreprise_create:visited { color : black; } + +table.entreprise_list_title { + width: 100%; + border-top: 1px solid rgb(51,102,204); + border-spacing: 0px 0px; + padding: 0px; +} +tr.entreprise_list_title { + background-color: rgb(229,236,249); + font-weight: bold; +} +td.entreprise_list_title { + padding-left: 1em; +} +td.entreprise_list_title_res { + font-weight: normal; + text-align : right; +} + +h2.entreprise { + color: rgb(6,102,18); + border: 1px solid blue; +} + +h2.entreprise_contact:before { + content: url(/ScoDoc/static/icons/contact_img.png); + vertical-align: -80%; + padding-right: 1em; +} +h2.entreprise_correspondant:before { + content: url(/ScoDoc/static/icons/correspondant_img.png); + vertical-align: -80%; + padding-right: 1em; +} + +h2.entreprise_new:before { + content: url(/ScoDoc/static/icons/entreprise_img.png); + vertical-align: -80%; + padding-right: 2em; +} + +p.entreprise_warning, p.gtr_warning, p.gtr_interdit, p.gtr_devel { + color: red; + font-style: italic; + margin-top: -1em; +} +P.entreprise_warning:before { + content: url(/ScoDoc/static/icons/warning_img.png); + vertical-align: -80%; +} +P.gtr_interdit:before { + content: url(/ScoDoc/static/icons/interdit_img.png); + vertical-align: -80%; +} +P.gtr_devel:before { + content: url(/ScoDoc/static/icons/devel_img.png); + vertical-align: -80%; +} +div.entreprise-insidebar { + border: 1px solid blue; +} + +/* ---- Sortable tables --- */ +/* Sortable tables */ +table.sortable a.sortheader { + background-color:#E6E6E6; + color: black; + font-weight: bold; + text-decoration: none; + display: block; +} +table.sortable span.sortarrow { + color: black; + text-decoration: none; +} + +/* Horizontal bar graph */ +.graph { + width: 100px; + height: 12px; + /* background-color: rgb(200, 200, 250); */ + padding-bottom: 0px; + margin-bottom: 0; + margin-right: 0px; + margin-top: 3px; + margin-left: 10px; + border:1px solid black; + position: absolute; +} + +.bar { + background-color: rgb(100, 150, 255); + margin: 0; + padding: 0; + position: absolute; + left: 0; + top: 2px; + height: 8px; + z-index: 2; +} + +.mark { + background-color: rgb(0, 150, 0); + margin: 0; + padding: 0; + position: absolute; + top: 0; + width: 2px; + height: 100%; + z-index: 2; +} + +td.cell_graph { + width: 170px; +} + +/* ------------------ Formulaire validation semestre ---------- */ +table.recap_parcours { + color: black; + border-collapse: collapse; +} + +table.recap_parcours td { + padding-left: 8px; + padding-right: 8px; +} + +.recap_parcours tr.sem_courant { + background-color: rgb(245, 243, 116); +} + +.recap_parcours tr.sem_precedent { + background-color: rgb(90%,95%,90%); +} + +.recap_parcours tr.sem_autre { + background-color: rgb(90%,90%,90%); +} + +.rcp_l1 td { + padding-top: 5px; + border-top: 3px solid rgb(50%,50%,50%); + border-right: 0px; + border-left: 0px; + color: blue; + vertical-align: top; +} +td.rcp_dec { + color: rgb(0%,0%,50%);; +} +td.rcp_nonass { + color: red; +} + +.recap_hide_details tr.rcp_l2 { display: none; } +table.recap_hide_details td.ue_acro span { display: none; } + +.sco_hide { display: none; } + +table.recap_hide_details tr.sem_courant, table.recap_hide_details tr.sem_precedent { + display: table-row; +} +table.recap_hide_details tr.sem_courant td.ue_acro span, table.recap_hide_details tr.sem_precedent td.ue_acro span { + display: inline; +} + +.recap_parcours tr.sem_courant td.rcp_type_sem { + font-weight: bold; +} +.recap_parcours tr.sem_autre td.rcp_type_sem { + color: rgb(100%,70%,70%); +} + +.rcp_l2 td { + padding-bottom: 5px; +} + +td.rcp_moy { +} +table.recap_parcours td.datedebut { + color: rgb(0,0,128); +} +table.recap_parcours td.datefin { + color: rgb(0,0,128); +} +table.recap_parcours td.rcp_type_sem { + padding-left: 4px; + padding-right: 4px; + color: red; +} +td.ue_adm { + color: green; + font-weight: bold; +} + +td.ue_cmp { + color: green; +} + +td.ue { +} + +h3.sfv { + margin-top: 0px; +} + +form.sfv_decisions { + border:1px solid blue; + padding: 6px; + margin-right: 2px; +} +form.sfv_decisions_manuelles { + margin-top: 10px; +} +th.sfv_subtitle { + text-align: left; + font-style: italic; +} + + +tr.sfv_ass { + background-color: rgb(90%,90%,80%); +} + +tr.sfv_pbass { + background-color: rgb(90%,70%,80%); +} + +div.pas_sembox { + margin-top: 10px; + border: 2px solid #a0522d; + padding: 5px; + margin-right: 10px; + font-family: arial,verdana,sans-serif; +} + +div.pas_empty_sems { +} + +.inscrit { +/* font-weight: bold; */ +} + +.inscrailleurs { + font-weight: bold; + color: red !important; +} + +span.paspaye, span.paspaye a { + color: #9400d3 !important; +} + +.pas_sembox_title a { + font-weight: bold; + font-size: 100%; + color: #1C721C; +} + +.pas_sembox_subtitle { + font-weight: normal; + font-size: 100%; + color: blue; + border-bottom: 1px solid rgb(50%,50%,50%); + margin-bottom: 8px; +} + +.pas_recap { + font-weight: bold; + font-size: 110%; + margin-top: 10px; +} + +div.pas_help { + float: left; +} + +/* ---- check absences / evaluations ---- */ +div.module_check_absences h2 { + font-size: 100%; + color: blue; + margin-bottom:0px; +} + +div.module_check_absences h2.eval_check_absences { + font-size: 80%; + color: black; + margin-left: 20px; + margin-top:0px; + margin-bottom:5px; +} + +div.module_check_absences h3 { + font-size: 80%; + color: rgb(133,0,0); + margin-left: 40px; + margin-top:0px; + margin-bottom:0px; +} + +div.module_check_absences ul { + margin-left: 60px; + font-size: 80%; + margin-top:0px; + margin-bottom:0px; +} + +/* ----------------------------------------------- */ +/* Help bubbles (aka tooltips) */ +/* ----------------------------------------------- */ +.tooltip{ + width: 200px; + color:#000; + font:lighter 11px/1.3 Arial,sans-serif; + text-decoration:none; + text-align:center; +} + +.tooltip span.top{ + padding: 30px 8px 0; + background: url(/ScoDoc/static/icons/bt_gif.png) no-repeat top; +} + +.tooltip b.bottom{ + padding:3px 8px 15px; + color: #548912; + background: url(/ScoDoc/static/icons/bt_gif.png) no-repeat bottom; +} +/* ----------------------------------------------- */ + +/* ----------------------------- */ +/* TABLES generees par gen_table */ +/* ----------------------------- */ +/* style pour les tables generiques ne specifiant pas de classes */ +table.gt_table { + border-collapse: collapse; + background-color: rgb(90%,90%,90%); + /* border: 1px solid black; */ + padding: 10px; + margin-right: 1em; +} + +table.gt_table tr { + background-color: rgb(90%,90%,90%); +} + +table.gt_table tr.gt_hl { + background-color: rgb(90%,95%,90%); +} +table.gt_table td, table.gt_table th { + border: 1px solid rgb(80%,80%,80%); +} +table.gt_table th { + padding-left: 5px; + padding-right: 5px; +} +table.gt_table td { + text-align: right; + padding-left: 2px; + padding-right: 4px; +} + +table.table_cohorte tr.sortbottom { + background-color: rgb(85%,90%,100%); +} + +table.table_leftalign tr td { + text-align: left; +} +table.table_coldate tr td:first-child { /* largeur col. date/time */ + width: 12em; + color: rgb(0%,0%,50%); +} + + +table.table_listegroupe tr td { + padding-left: 0.5em; + padding-right: 0.5em; +} + +table.list_users td.roles { + width: 22em; +} + +table.list_users td.date_modif_passwd { + white-space: nowrap; +} + +/* ----------------------------- */ + +div.form_rename_partition { + margin-top: 2em; + margin-bottom: 2em; +} + + +td.calday { + text-align: right; + vertical-align: top; +} + +div.cal_evaluations table.monthcalendar td.calcol { +} + +div.cal_evaluations table.monthcalendar td.calcell { + padding-left: 0.6em; + width: 6em; +} + + +div.cal_evaluations table.monthcalendar td a { + color: rgb(128,0,0); +} + +#lyc_map_canvas { + width: 900px; + height: 600px; +} + +/* Mise en garde -------------------------------*/ +.attention{ + background-color:#CCCC99; + border: 2px solid #CCCC99; + position:relative; + z-index:1; + overflow:hidden; + list-style:none; + padding:0; + margin:0 0 0.25em; + display:table; + margin:10px 10px 5px 10px; + padding:5px 5px 5px 25px; + border-radius: 10px; +} +.attention:before { + content:"i"; + left:6px; + width:16px; + height:16px; + margin-top:-8px; + font-size:14px; + font-weight:bold; + font-style:italic; + line-height:15px; + text-align:center; + color:#fff; + background-color:#2385BA; + /* css3 */ + -webkit-border-radius:16px; + -moz-border-radius:16px; + border-radius:16px; + position:absolute; + top:13px; +} \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/deconnexion.php b/misc/PublicationBulletins/Portail-LeHavre/deconnexion.php new file mode 100755 index 00000000..eb906aab --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/deconnexion.php @@ -0,0 +1,27 @@ + +Merci de votre visite.

      Pensez fermer votre navigateur en partant et ne jamais enregistrer votre mot de passe.

      " +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/courier.php b/misc/PublicationBulletins/Portail-LeHavre/font/courier.php new file mode 100755 index 00000000..913f9a45 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/courier.php @@ -0,0 +1,7 @@ + diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/helvetica.php b/misc/PublicationBulletins/Portail-LeHavre/font/helvetica.php new file mode 100755 index 00000000..ca94cdf2 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/helvetica.php @@ -0,0 +1,15 @@ +278,chr(1)=>278,chr(2)=>278,chr(3)=>278,chr(4)=>278,chr(5)=>278,chr(6)=>278,chr(7)=>278,chr(8)=>278,chr(9)=>278,chr(10)=>278,chr(11)=>278,chr(12)=>278,chr(13)=>278,chr(14)=>278,chr(15)=>278,chr(16)=>278,chr(17)=>278,chr(18)=>278,chr(19)=>278,chr(20)=>278,chr(21)=>278, + chr(22)=>278,chr(23)=>278,chr(24)=>278,chr(25)=>278,chr(26)=>278,chr(27)=>278,chr(28)=>278,chr(29)=>278,chr(30)=>278,chr(31)=>278,' '=>278,'!'=>278,'"'=>355,'#'=>556,'$'=>556,'%'=>889,'&'=>667,'\''=>191,'('=>333,')'=>333,'*'=>389,'+'=>584, + ','=>278,'-'=>333,'.'=>278,'/'=>278,'0'=>556,'1'=>556,'2'=>556,'3'=>556,'4'=>556,'5'=>556,'6'=>556,'7'=>556,'8'=>556,'9'=>556,':'=>278,';'=>278,'<'=>584,'='=>584,'>'=>584,'?'=>556,'@'=>1015,'A'=>667, + 'B'=>667,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>722,'I'=>278,'J'=>500,'K'=>667,'L'=>556,'M'=>833,'N'=>722,'O'=>778,'P'=>667,'Q'=>778,'R'=>722,'S'=>667,'T'=>611,'U'=>722,'V'=>667,'W'=>944, + 'X'=>667,'Y'=>667,'Z'=>611,'['=>278,'\\'=>278,']'=>278,'^'=>469,'_'=>556,'`'=>333,'a'=>556,'b'=>556,'c'=>500,'d'=>556,'e'=>556,'f'=>278,'g'=>556,'h'=>556,'i'=>222,'j'=>222,'k'=>500,'l'=>222,'m'=>833, + 'n'=>556,'o'=>556,'p'=>556,'q'=>556,'r'=>333,'s'=>500,'t'=>278,'u'=>556,'v'=>500,'w'=>722,'x'=>500,'y'=>500,'z'=>500,'{'=>334,'|'=>260,'}'=>334,'~'=>584,chr(127)=>350,chr(128)=>556,chr(129)=>350,chr(130)=>222,chr(131)=>556, + chr(132)=>333,chr(133)=>1000,chr(134)=>556,chr(135)=>556,chr(136)=>333,chr(137)=>1000,chr(138)=>667,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>222,chr(146)=>222,chr(147)=>333,chr(148)=>333,chr(149)=>350,chr(150)=>556,chr(151)=>1000,chr(152)=>333,chr(153)=>1000, + chr(154)=>500,chr(155)=>333,chr(156)=>944,chr(157)=>350,chr(158)=>500,chr(159)=>667,chr(160)=>278,chr(161)=>333,chr(162)=>556,chr(163)=>556,chr(164)=>556,chr(165)=>556,chr(166)=>260,chr(167)=>556,chr(168)=>333,chr(169)=>737,chr(170)=>370,chr(171)=>556,chr(172)=>584,chr(173)=>333,chr(174)=>737,chr(175)=>333, + chr(176)=>400,chr(177)=>584,chr(178)=>333,chr(179)=>333,chr(180)=>333,chr(181)=>556,chr(182)=>537,chr(183)=>278,chr(184)=>333,chr(185)=>333,chr(186)=>365,chr(187)=>556,chr(188)=>834,chr(189)=>834,chr(190)=>834,chr(191)=>611,chr(192)=>667,chr(193)=>667,chr(194)=>667,chr(195)=>667,chr(196)=>667,chr(197)=>667, + chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>278,chr(205)=>278,chr(206)=>278,chr(207)=>278,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>584,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>667,chr(222)=>667,chr(223)=>611,chr(224)=>556,chr(225)=>556,chr(226)=>556,chr(227)=>556,chr(228)=>556,chr(229)=>556,chr(230)=>889,chr(231)=>500,chr(232)=>556,chr(233)=>556,chr(234)=>556,chr(235)=>556,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>556,chr(241)=>556, + chr(242)=>556,chr(243)=>556,chr(244)=>556,chr(245)=>556,chr(246)=>556,chr(247)=>584,chr(248)=>611,chr(249)=>556,chr(250)=>556,chr(251)=>556,chr(252)=>556,chr(253)=>500,chr(254)=>556,chr(255)=>500); +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/helveticab.php b/misc/PublicationBulletins/Portail-LeHavre/font/helveticab.php new file mode 100755 index 00000000..276cfa8c --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/helveticab.php @@ -0,0 +1,15 @@ +278,chr(1)=>278,chr(2)=>278,chr(3)=>278,chr(4)=>278,chr(5)=>278,chr(6)=>278,chr(7)=>278,chr(8)=>278,chr(9)=>278,chr(10)=>278,chr(11)=>278,chr(12)=>278,chr(13)=>278,chr(14)=>278,chr(15)=>278,chr(16)=>278,chr(17)=>278,chr(18)=>278,chr(19)=>278,chr(20)=>278,chr(21)=>278, + chr(22)=>278,chr(23)=>278,chr(24)=>278,chr(25)=>278,chr(26)=>278,chr(27)=>278,chr(28)=>278,chr(29)=>278,chr(30)=>278,chr(31)=>278,' '=>278,'!'=>333,'"'=>474,'#'=>556,'$'=>556,'%'=>889,'&'=>722,'\''=>238,'('=>333,')'=>333,'*'=>389,'+'=>584, + ','=>278,'-'=>333,'.'=>278,'/'=>278,'0'=>556,'1'=>556,'2'=>556,'3'=>556,'4'=>556,'5'=>556,'6'=>556,'7'=>556,'8'=>556,'9'=>556,':'=>333,';'=>333,'<'=>584,'='=>584,'>'=>584,'?'=>611,'@'=>975,'A'=>722, + 'B'=>722,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>722,'I'=>278,'J'=>556,'K'=>722,'L'=>611,'M'=>833,'N'=>722,'O'=>778,'P'=>667,'Q'=>778,'R'=>722,'S'=>667,'T'=>611,'U'=>722,'V'=>667,'W'=>944, + 'X'=>667,'Y'=>667,'Z'=>611,'['=>333,'\\'=>278,']'=>333,'^'=>584,'_'=>556,'`'=>333,'a'=>556,'b'=>611,'c'=>556,'d'=>611,'e'=>556,'f'=>333,'g'=>611,'h'=>611,'i'=>278,'j'=>278,'k'=>556,'l'=>278,'m'=>889, + 'n'=>611,'o'=>611,'p'=>611,'q'=>611,'r'=>389,'s'=>556,'t'=>333,'u'=>611,'v'=>556,'w'=>778,'x'=>556,'y'=>556,'z'=>500,'{'=>389,'|'=>280,'}'=>389,'~'=>584,chr(127)=>350,chr(128)=>556,chr(129)=>350,chr(130)=>278,chr(131)=>556, + chr(132)=>500,chr(133)=>1000,chr(134)=>556,chr(135)=>556,chr(136)=>333,chr(137)=>1000,chr(138)=>667,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>278,chr(146)=>278,chr(147)=>500,chr(148)=>500,chr(149)=>350,chr(150)=>556,chr(151)=>1000,chr(152)=>333,chr(153)=>1000, + chr(154)=>556,chr(155)=>333,chr(156)=>944,chr(157)=>350,chr(158)=>500,chr(159)=>667,chr(160)=>278,chr(161)=>333,chr(162)=>556,chr(163)=>556,chr(164)=>556,chr(165)=>556,chr(166)=>280,chr(167)=>556,chr(168)=>333,chr(169)=>737,chr(170)=>370,chr(171)=>556,chr(172)=>584,chr(173)=>333,chr(174)=>737,chr(175)=>333, + chr(176)=>400,chr(177)=>584,chr(178)=>333,chr(179)=>333,chr(180)=>333,chr(181)=>611,chr(182)=>556,chr(183)=>278,chr(184)=>333,chr(185)=>333,chr(186)=>365,chr(187)=>556,chr(188)=>834,chr(189)=>834,chr(190)=>834,chr(191)=>611,chr(192)=>722,chr(193)=>722,chr(194)=>722,chr(195)=>722,chr(196)=>722,chr(197)=>722, + chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>278,chr(205)=>278,chr(206)=>278,chr(207)=>278,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>584,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>667,chr(222)=>667,chr(223)=>611,chr(224)=>556,chr(225)=>556,chr(226)=>556,chr(227)=>556,chr(228)=>556,chr(229)=>556,chr(230)=>889,chr(231)=>556,chr(232)=>556,chr(233)=>556,chr(234)=>556,chr(235)=>556,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>611,chr(241)=>611, + chr(242)=>611,chr(243)=>611,chr(244)=>611,chr(245)=>611,chr(246)=>611,chr(247)=>584,chr(248)=>611,chr(249)=>611,chr(250)=>611,chr(251)=>611,chr(252)=>611,chr(253)=>556,chr(254)=>611,chr(255)=>556); +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/helveticabi.php b/misc/PublicationBulletins/Portail-LeHavre/font/helveticabi.php new file mode 100755 index 00000000..8d217743 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/helveticabi.php @@ -0,0 +1,15 @@ +278,chr(1)=>278,chr(2)=>278,chr(3)=>278,chr(4)=>278,chr(5)=>278,chr(6)=>278,chr(7)=>278,chr(8)=>278,chr(9)=>278,chr(10)=>278,chr(11)=>278,chr(12)=>278,chr(13)=>278,chr(14)=>278,chr(15)=>278,chr(16)=>278,chr(17)=>278,chr(18)=>278,chr(19)=>278,chr(20)=>278,chr(21)=>278, + chr(22)=>278,chr(23)=>278,chr(24)=>278,chr(25)=>278,chr(26)=>278,chr(27)=>278,chr(28)=>278,chr(29)=>278,chr(30)=>278,chr(31)=>278,' '=>278,'!'=>333,'"'=>474,'#'=>556,'$'=>556,'%'=>889,'&'=>722,'\''=>238,'('=>333,')'=>333,'*'=>389,'+'=>584, + ','=>278,'-'=>333,'.'=>278,'/'=>278,'0'=>556,'1'=>556,'2'=>556,'3'=>556,'4'=>556,'5'=>556,'6'=>556,'7'=>556,'8'=>556,'9'=>556,':'=>333,';'=>333,'<'=>584,'='=>584,'>'=>584,'?'=>611,'@'=>975,'A'=>722, + 'B'=>722,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>722,'I'=>278,'J'=>556,'K'=>722,'L'=>611,'M'=>833,'N'=>722,'O'=>778,'P'=>667,'Q'=>778,'R'=>722,'S'=>667,'T'=>611,'U'=>722,'V'=>667,'W'=>944, + 'X'=>667,'Y'=>667,'Z'=>611,'['=>333,'\\'=>278,']'=>333,'^'=>584,'_'=>556,'`'=>333,'a'=>556,'b'=>611,'c'=>556,'d'=>611,'e'=>556,'f'=>333,'g'=>611,'h'=>611,'i'=>278,'j'=>278,'k'=>556,'l'=>278,'m'=>889, + 'n'=>611,'o'=>611,'p'=>611,'q'=>611,'r'=>389,'s'=>556,'t'=>333,'u'=>611,'v'=>556,'w'=>778,'x'=>556,'y'=>556,'z'=>500,'{'=>389,'|'=>280,'}'=>389,'~'=>584,chr(127)=>350,chr(128)=>556,chr(129)=>350,chr(130)=>278,chr(131)=>556, + chr(132)=>500,chr(133)=>1000,chr(134)=>556,chr(135)=>556,chr(136)=>333,chr(137)=>1000,chr(138)=>667,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>278,chr(146)=>278,chr(147)=>500,chr(148)=>500,chr(149)=>350,chr(150)=>556,chr(151)=>1000,chr(152)=>333,chr(153)=>1000, + chr(154)=>556,chr(155)=>333,chr(156)=>944,chr(157)=>350,chr(158)=>500,chr(159)=>667,chr(160)=>278,chr(161)=>333,chr(162)=>556,chr(163)=>556,chr(164)=>556,chr(165)=>556,chr(166)=>280,chr(167)=>556,chr(168)=>333,chr(169)=>737,chr(170)=>370,chr(171)=>556,chr(172)=>584,chr(173)=>333,chr(174)=>737,chr(175)=>333, + chr(176)=>400,chr(177)=>584,chr(178)=>333,chr(179)=>333,chr(180)=>333,chr(181)=>611,chr(182)=>556,chr(183)=>278,chr(184)=>333,chr(185)=>333,chr(186)=>365,chr(187)=>556,chr(188)=>834,chr(189)=>834,chr(190)=>834,chr(191)=>611,chr(192)=>722,chr(193)=>722,chr(194)=>722,chr(195)=>722,chr(196)=>722,chr(197)=>722, + chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>278,chr(205)=>278,chr(206)=>278,chr(207)=>278,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>584,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>667,chr(222)=>667,chr(223)=>611,chr(224)=>556,chr(225)=>556,chr(226)=>556,chr(227)=>556,chr(228)=>556,chr(229)=>556,chr(230)=>889,chr(231)=>556,chr(232)=>556,chr(233)=>556,chr(234)=>556,chr(235)=>556,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>611,chr(241)=>611, + chr(242)=>611,chr(243)=>611,chr(244)=>611,chr(245)=>611,chr(246)=>611,chr(247)=>584,chr(248)=>611,chr(249)=>611,chr(250)=>611,chr(251)=>611,chr(252)=>611,chr(253)=>556,chr(254)=>611,chr(255)=>556); +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/helveticai.php b/misc/PublicationBulletins/Portail-LeHavre/font/helveticai.php new file mode 100755 index 00000000..88bf4371 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/helveticai.php @@ -0,0 +1,15 @@ +278,chr(1)=>278,chr(2)=>278,chr(3)=>278,chr(4)=>278,chr(5)=>278,chr(6)=>278,chr(7)=>278,chr(8)=>278,chr(9)=>278,chr(10)=>278,chr(11)=>278,chr(12)=>278,chr(13)=>278,chr(14)=>278,chr(15)=>278,chr(16)=>278,chr(17)=>278,chr(18)=>278,chr(19)=>278,chr(20)=>278,chr(21)=>278, + chr(22)=>278,chr(23)=>278,chr(24)=>278,chr(25)=>278,chr(26)=>278,chr(27)=>278,chr(28)=>278,chr(29)=>278,chr(30)=>278,chr(31)=>278,' '=>278,'!'=>278,'"'=>355,'#'=>556,'$'=>556,'%'=>889,'&'=>667,'\''=>191,'('=>333,')'=>333,'*'=>389,'+'=>584, + ','=>278,'-'=>333,'.'=>278,'/'=>278,'0'=>556,'1'=>556,'2'=>556,'3'=>556,'4'=>556,'5'=>556,'6'=>556,'7'=>556,'8'=>556,'9'=>556,':'=>278,';'=>278,'<'=>584,'='=>584,'>'=>584,'?'=>556,'@'=>1015,'A'=>667, + 'B'=>667,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>722,'I'=>278,'J'=>500,'K'=>667,'L'=>556,'M'=>833,'N'=>722,'O'=>778,'P'=>667,'Q'=>778,'R'=>722,'S'=>667,'T'=>611,'U'=>722,'V'=>667,'W'=>944, + 'X'=>667,'Y'=>667,'Z'=>611,'['=>278,'\\'=>278,']'=>278,'^'=>469,'_'=>556,'`'=>333,'a'=>556,'b'=>556,'c'=>500,'d'=>556,'e'=>556,'f'=>278,'g'=>556,'h'=>556,'i'=>222,'j'=>222,'k'=>500,'l'=>222,'m'=>833, + 'n'=>556,'o'=>556,'p'=>556,'q'=>556,'r'=>333,'s'=>500,'t'=>278,'u'=>556,'v'=>500,'w'=>722,'x'=>500,'y'=>500,'z'=>500,'{'=>334,'|'=>260,'}'=>334,'~'=>584,chr(127)=>350,chr(128)=>556,chr(129)=>350,chr(130)=>222,chr(131)=>556, + chr(132)=>333,chr(133)=>1000,chr(134)=>556,chr(135)=>556,chr(136)=>333,chr(137)=>1000,chr(138)=>667,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>222,chr(146)=>222,chr(147)=>333,chr(148)=>333,chr(149)=>350,chr(150)=>556,chr(151)=>1000,chr(152)=>333,chr(153)=>1000, + chr(154)=>500,chr(155)=>333,chr(156)=>944,chr(157)=>350,chr(158)=>500,chr(159)=>667,chr(160)=>278,chr(161)=>333,chr(162)=>556,chr(163)=>556,chr(164)=>556,chr(165)=>556,chr(166)=>260,chr(167)=>556,chr(168)=>333,chr(169)=>737,chr(170)=>370,chr(171)=>556,chr(172)=>584,chr(173)=>333,chr(174)=>737,chr(175)=>333, + chr(176)=>400,chr(177)=>584,chr(178)=>333,chr(179)=>333,chr(180)=>333,chr(181)=>556,chr(182)=>537,chr(183)=>278,chr(184)=>333,chr(185)=>333,chr(186)=>365,chr(187)=>556,chr(188)=>834,chr(189)=>834,chr(190)=>834,chr(191)=>611,chr(192)=>667,chr(193)=>667,chr(194)=>667,chr(195)=>667,chr(196)=>667,chr(197)=>667, + chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>278,chr(205)=>278,chr(206)=>278,chr(207)=>278,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>584,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>667,chr(222)=>667,chr(223)=>611,chr(224)=>556,chr(225)=>556,chr(226)=>556,chr(227)=>556,chr(228)=>556,chr(229)=>556,chr(230)=>889,chr(231)=>500,chr(232)=>556,chr(233)=>556,chr(234)=>556,chr(235)=>556,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>556,chr(241)=>556, + chr(242)=>556,chr(243)=>556,chr(244)=>556,chr(245)=>556,chr(246)=>556,chr(247)=>584,chr(248)=>611,chr(249)=>556,chr(250)=>556,chr(251)=>556,chr(252)=>556,chr(253)=>500,chr(254)=>556,chr(255)=>500); +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/index.html b/misc/PublicationBulletins/Portail-LeHavre/font/index.html new file mode 100755 index 00000000..3b408bc9 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/index.html @@ -0,0 +1 @@ +


      Colloque GEII Angoulme 2011
      \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1250.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1250.map new file mode 100755 index 00000000..ec110af0 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1250.map @@ -0,0 +1,251 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+20AC Euro +!82 U+201A quotesinglbase +!84 U+201E quotedblbase +!85 U+2026 ellipsis +!86 U+2020 dagger +!87 U+2021 daggerdbl +!89 U+2030 perthousand +!8A U+0160 Scaron +!8B U+2039 guilsinglleft +!8C U+015A Sacute +!8D U+0164 Tcaron +!8E U+017D Zcaron +!8F U+0179 Zacute +!91 U+2018 quoteleft +!92 U+2019 quoteright +!93 U+201C quotedblleft +!94 U+201D quotedblright +!95 U+2022 bullet +!96 U+2013 endash +!97 U+2014 emdash +!99 U+2122 trademark +!9A U+0161 scaron +!9B U+203A guilsinglright +!9C U+015B sacute +!9D U+0165 tcaron +!9E U+017E zcaron +!9F U+017A zacute +!A0 U+00A0 space +!A1 U+02C7 caron +!A2 U+02D8 breve +!A3 U+0141 Lslash +!A4 U+00A4 currency +!A5 U+0104 Aogonek +!A6 U+00A6 brokenbar +!A7 U+00A7 section +!A8 U+00A8 dieresis +!A9 U+00A9 copyright +!AA U+015E Scedilla +!AB U+00AB guillemotleft +!AC U+00AC logicalnot +!AD U+00AD hyphen +!AE U+00AE registered +!AF U+017B Zdotaccent +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+02DB ogonek +!B3 U+0142 lslash +!B4 U+00B4 acute +!B5 U+00B5 mu +!B6 U+00B6 paragraph +!B7 U+00B7 periodcentered +!B8 U+00B8 cedilla +!B9 U+0105 aogonek +!BA U+015F scedilla +!BB U+00BB guillemotright +!BC U+013D Lcaron +!BD U+02DD hungarumlaut +!BE U+013E lcaron +!BF U+017C zdotaccent +!C0 U+0154 Racute +!C1 U+00C1 Aacute +!C2 U+00C2 Acircumflex +!C3 U+0102 Abreve +!C4 U+00C4 Adieresis +!C5 U+0139 Lacute +!C6 U+0106 Cacute +!C7 U+00C7 Ccedilla +!C8 U+010C Ccaron +!C9 U+00C9 Eacute +!CA U+0118 Eogonek +!CB U+00CB Edieresis +!CC U+011A Ecaron +!CD U+00CD Iacute +!CE U+00CE Icircumflex +!CF U+010E Dcaron +!D0 U+0110 Dcroat +!D1 U+0143 Nacute +!D2 U+0147 Ncaron +!D3 U+00D3 Oacute +!D4 U+00D4 Ocircumflex +!D5 U+0150 Ohungarumlaut +!D6 U+00D6 Odieresis +!D7 U+00D7 multiply +!D8 U+0158 Rcaron +!D9 U+016E Uring +!DA U+00DA Uacute +!DB U+0170 Uhungarumlaut +!DC U+00DC Udieresis +!DD U+00DD Yacute +!DE U+0162 Tcommaaccent +!DF U+00DF germandbls +!E0 U+0155 racute +!E1 U+00E1 aacute +!E2 U+00E2 acircumflex +!E3 U+0103 abreve +!E4 U+00E4 adieresis +!E5 U+013A lacute +!E6 U+0107 cacute +!E7 U+00E7 ccedilla +!E8 U+010D ccaron +!E9 U+00E9 eacute +!EA U+0119 eogonek +!EB U+00EB edieresis +!EC U+011B ecaron +!ED U+00ED iacute +!EE U+00EE icircumflex +!EF U+010F dcaron +!F0 U+0111 dcroat +!F1 U+0144 nacute +!F2 U+0148 ncaron +!F3 U+00F3 oacute +!F4 U+00F4 ocircumflex +!F5 U+0151 ohungarumlaut +!F6 U+00F6 odieresis +!F7 U+00F7 divide +!F8 U+0159 rcaron +!F9 U+016F uring +!FA U+00FA uacute +!FB U+0171 uhungarumlaut +!FC U+00FC udieresis +!FD U+00FD yacute +!FE U+0163 tcommaaccent +!FF U+02D9 dotaccent diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1251.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1251.map new file mode 100755 index 00000000..de6a198d --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1251.map @@ -0,0 +1,255 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+0402 afii10051 +!81 U+0403 afii10052 +!82 U+201A quotesinglbase +!83 U+0453 afii10100 +!84 U+201E quotedblbase +!85 U+2026 ellipsis +!86 U+2020 dagger +!87 U+2021 daggerdbl +!88 U+20AC Euro +!89 U+2030 perthousand +!8A U+0409 afii10058 +!8B U+2039 guilsinglleft +!8C U+040A afii10059 +!8D U+040C afii10061 +!8E U+040B afii10060 +!8F U+040F afii10145 +!90 U+0452 afii10099 +!91 U+2018 quoteleft +!92 U+2019 quoteright +!93 U+201C quotedblleft +!94 U+201D quotedblright +!95 U+2022 bullet +!96 U+2013 endash +!97 U+2014 emdash +!99 U+2122 trademark +!9A U+0459 afii10106 +!9B U+203A guilsinglright +!9C U+045A afii10107 +!9D U+045C afii10109 +!9E U+045B afii10108 +!9F U+045F afii10193 +!A0 U+00A0 space +!A1 U+040E afii10062 +!A2 U+045E afii10110 +!A3 U+0408 afii10057 +!A4 U+00A4 currency +!A5 U+0490 afii10050 +!A6 U+00A6 brokenbar +!A7 U+00A7 section +!A8 U+0401 afii10023 +!A9 U+00A9 copyright +!AA U+0404 afii10053 +!AB U+00AB guillemotleft +!AC U+00AC logicalnot +!AD U+00AD hyphen +!AE U+00AE registered +!AF U+0407 afii10056 +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+0406 afii10055 +!B3 U+0456 afii10103 +!B4 U+0491 afii10098 +!B5 U+00B5 mu +!B6 U+00B6 paragraph +!B7 U+00B7 periodcentered +!B8 U+0451 afii10071 +!B9 U+2116 afii61352 +!BA U+0454 afii10101 +!BB U+00BB guillemotright +!BC U+0458 afii10105 +!BD U+0405 afii10054 +!BE U+0455 afii10102 +!BF U+0457 afii10104 +!C0 U+0410 afii10017 +!C1 U+0411 afii10018 +!C2 U+0412 afii10019 +!C3 U+0413 afii10020 +!C4 U+0414 afii10021 +!C5 U+0415 afii10022 +!C6 U+0416 afii10024 +!C7 U+0417 afii10025 +!C8 U+0418 afii10026 +!C9 U+0419 afii10027 +!CA U+041A afii10028 +!CB U+041B afii10029 +!CC U+041C afii10030 +!CD U+041D afii10031 +!CE U+041E afii10032 +!CF U+041F afii10033 +!D0 U+0420 afii10034 +!D1 U+0421 afii10035 +!D2 U+0422 afii10036 +!D3 U+0423 afii10037 +!D4 U+0424 afii10038 +!D5 U+0425 afii10039 +!D6 U+0426 afii10040 +!D7 U+0427 afii10041 +!D8 U+0428 afii10042 +!D9 U+0429 afii10043 +!DA U+042A afii10044 +!DB U+042B afii10045 +!DC U+042C afii10046 +!DD U+042D afii10047 +!DE U+042E afii10048 +!DF U+042F afii10049 +!E0 U+0430 afii10065 +!E1 U+0431 afii10066 +!E2 U+0432 afii10067 +!E3 U+0433 afii10068 +!E4 U+0434 afii10069 +!E5 U+0435 afii10070 +!E6 U+0436 afii10072 +!E7 U+0437 afii10073 +!E8 U+0438 afii10074 +!E9 U+0439 afii10075 +!EA U+043A afii10076 +!EB U+043B afii10077 +!EC U+043C afii10078 +!ED U+043D afii10079 +!EE U+043E afii10080 +!EF U+043F afii10081 +!F0 U+0440 afii10082 +!F1 U+0441 afii10083 +!F2 U+0442 afii10084 +!F3 U+0443 afii10085 +!F4 U+0444 afii10086 +!F5 U+0445 afii10087 +!F6 U+0446 afii10088 +!F7 U+0447 afii10089 +!F8 U+0448 afii10090 +!F9 U+0449 afii10091 +!FA U+044A afii10092 +!FB U+044B afii10093 +!FC U+044C afii10094 +!FD U+044D afii10095 +!FE U+044E afii10096 +!FF U+044F afii10097 diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1252.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1252.map new file mode 100755 index 00000000..dd490e59 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1252.map @@ -0,0 +1,251 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+20AC Euro +!82 U+201A quotesinglbase +!83 U+0192 florin +!84 U+201E quotedblbase +!85 U+2026 ellipsis +!86 U+2020 dagger +!87 U+2021 daggerdbl +!88 U+02C6 circumflex +!89 U+2030 perthousand +!8A U+0160 Scaron +!8B U+2039 guilsinglleft +!8C U+0152 OE +!8E U+017D Zcaron +!91 U+2018 quoteleft +!92 U+2019 quoteright +!93 U+201C quotedblleft +!94 U+201D quotedblright +!95 U+2022 bullet +!96 U+2013 endash +!97 U+2014 emdash +!98 U+02DC tilde +!99 U+2122 trademark +!9A U+0161 scaron +!9B U+203A guilsinglright +!9C U+0153 oe +!9E U+017E zcaron +!9F U+0178 Ydieresis +!A0 U+00A0 space +!A1 U+00A1 exclamdown +!A2 U+00A2 cent +!A3 U+00A3 sterling +!A4 U+00A4 currency +!A5 U+00A5 yen +!A6 U+00A6 brokenbar +!A7 U+00A7 section +!A8 U+00A8 dieresis +!A9 U+00A9 copyright +!AA U+00AA ordfeminine +!AB U+00AB guillemotleft +!AC U+00AC logicalnot +!AD U+00AD hyphen +!AE U+00AE registered +!AF U+00AF macron +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+00B2 twosuperior +!B3 U+00B3 threesuperior +!B4 U+00B4 acute +!B5 U+00B5 mu +!B6 U+00B6 paragraph +!B7 U+00B7 periodcentered +!B8 U+00B8 cedilla +!B9 U+00B9 onesuperior +!BA U+00BA ordmasculine +!BB U+00BB guillemotright +!BC U+00BC onequarter +!BD U+00BD onehalf +!BE U+00BE threequarters +!BF U+00BF questiondown +!C0 U+00C0 Agrave +!C1 U+00C1 Aacute +!C2 U+00C2 Acircumflex +!C3 U+00C3 Atilde +!C4 U+00C4 Adieresis +!C5 U+00C5 Aring +!C6 U+00C6 AE +!C7 U+00C7 Ccedilla +!C8 U+00C8 Egrave +!C9 U+00C9 Eacute +!CA U+00CA Ecircumflex +!CB U+00CB Edieresis +!CC U+00CC Igrave +!CD U+00CD Iacute +!CE U+00CE Icircumflex +!CF U+00CF Idieresis +!D0 U+00D0 Eth +!D1 U+00D1 Ntilde +!D2 U+00D2 Ograve +!D3 U+00D3 Oacute +!D4 U+00D4 Ocircumflex +!D5 U+00D5 Otilde +!D6 U+00D6 Odieresis +!D7 U+00D7 multiply +!D8 U+00D8 Oslash +!D9 U+00D9 Ugrave +!DA U+00DA Uacute +!DB U+00DB Ucircumflex +!DC U+00DC Udieresis +!DD U+00DD Yacute +!DE U+00DE Thorn +!DF U+00DF germandbls +!E0 U+00E0 agrave +!E1 U+00E1 aacute +!E2 U+00E2 acircumflex +!E3 U+00E3 atilde +!E4 U+00E4 adieresis +!E5 U+00E5 aring +!E6 U+00E6 ae +!E7 U+00E7 ccedilla +!E8 U+00E8 egrave +!E9 U+00E9 eacute +!EA U+00EA ecircumflex +!EB U+00EB edieresis +!EC U+00EC igrave +!ED U+00ED iacute +!EE U+00EE icircumflex +!EF U+00EF idieresis +!F0 U+00F0 eth +!F1 U+00F1 ntilde +!F2 U+00F2 ograve +!F3 U+00F3 oacute +!F4 U+00F4 ocircumflex +!F5 U+00F5 otilde +!F6 U+00F6 odieresis +!F7 U+00F7 divide +!F8 U+00F8 oslash +!F9 U+00F9 ugrave +!FA U+00FA uacute +!FB U+00FB ucircumflex +!FC U+00FC udieresis +!FD U+00FD yacute +!FE U+00FE thorn +!FF U+00FF ydieresis diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1253.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1253.map new file mode 100755 index 00000000..4bd826fb --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1253.map @@ -0,0 +1,239 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+20AC Euro +!82 U+201A quotesinglbase +!83 U+0192 florin +!84 U+201E quotedblbase +!85 U+2026 ellipsis +!86 U+2020 dagger +!87 U+2021 daggerdbl +!89 U+2030 perthousand +!8B U+2039 guilsinglleft +!91 U+2018 quoteleft +!92 U+2019 quoteright +!93 U+201C quotedblleft +!94 U+201D quotedblright +!95 U+2022 bullet +!96 U+2013 endash +!97 U+2014 emdash +!99 U+2122 trademark +!9B U+203A guilsinglright +!A0 U+00A0 space +!A1 U+0385 dieresistonos +!A2 U+0386 Alphatonos +!A3 U+00A3 sterling +!A4 U+00A4 currency +!A5 U+00A5 yen +!A6 U+00A6 brokenbar +!A7 U+00A7 section +!A8 U+00A8 dieresis +!A9 U+00A9 copyright +!AB U+00AB guillemotleft +!AC U+00AC logicalnot +!AD U+00AD hyphen +!AE U+00AE registered +!AF U+2015 afii00208 +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+00B2 twosuperior +!B3 U+00B3 threesuperior +!B4 U+0384 tonos +!B5 U+00B5 mu +!B6 U+00B6 paragraph +!B7 U+00B7 periodcentered +!B8 U+0388 Epsilontonos +!B9 U+0389 Etatonos +!BA U+038A Iotatonos +!BB U+00BB guillemotright +!BC U+038C Omicrontonos +!BD U+00BD onehalf +!BE U+038E Upsilontonos +!BF U+038F Omegatonos +!C0 U+0390 iotadieresistonos +!C1 U+0391 Alpha +!C2 U+0392 Beta +!C3 U+0393 Gamma +!C4 U+0394 Delta +!C5 U+0395 Epsilon +!C6 U+0396 Zeta +!C7 U+0397 Eta +!C8 U+0398 Theta +!C9 U+0399 Iota +!CA U+039A Kappa +!CB U+039B Lambda +!CC U+039C Mu +!CD U+039D Nu +!CE U+039E Xi +!CF U+039F Omicron +!D0 U+03A0 Pi +!D1 U+03A1 Rho +!D3 U+03A3 Sigma +!D4 U+03A4 Tau +!D5 U+03A5 Upsilon +!D6 U+03A6 Phi +!D7 U+03A7 Chi +!D8 U+03A8 Psi +!D9 U+03A9 Omega +!DA U+03AA Iotadieresis +!DB U+03AB Upsilondieresis +!DC U+03AC alphatonos +!DD U+03AD epsilontonos +!DE U+03AE etatonos +!DF U+03AF iotatonos +!E0 U+03B0 upsilondieresistonos +!E1 U+03B1 alpha +!E2 U+03B2 beta +!E3 U+03B3 gamma +!E4 U+03B4 delta +!E5 U+03B5 epsilon +!E6 U+03B6 zeta +!E7 U+03B7 eta +!E8 U+03B8 theta +!E9 U+03B9 iota +!EA U+03BA kappa +!EB U+03BB lambda +!EC U+03BC mu +!ED U+03BD nu +!EE U+03BE xi +!EF U+03BF omicron +!F0 U+03C0 pi +!F1 U+03C1 rho +!F2 U+03C2 sigma1 +!F3 U+03C3 sigma +!F4 U+03C4 tau +!F5 U+03C5 upsilon +!F6 U+03C6 phi +!F7 U+03C7 chi +!F8 U+03C8 psi +!F9 U+03C9 omega +!FA U+03CA iotadieresis +!FB U+03CB upsilondieresis +!FC U+03CC omicrontonos +!FD U+03CD upsilontonos +!FE U+03CE omegatonos diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1254.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1254.map new file mode 100755 index 00000000..829473b2 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1254.map @@ -0,0 +1,249 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+20AC Euro +!82 U+201A quotesinglbase +!83 U+0192 florin +!84 U+201E quotedblbase +!85 U+2026 ellipsis +!86 U+2020 dagger +!87 U+2021 daggerdbl +!88 U+02C6 circumflex +!89 U+2030 perthousand +!8A U+0160 Scaron +!8B U+2039 guilsinglleft +!8C U+0152 OE +!91 U+2018 quoteleft +!92 U+2019 quoteright +!93 U+201C quotedblleft +!94 U+201D quotedblright +!95 U+2022 bullet +!96 U+2013 endash +!97 U+2014 emdash +!98 U+02DC tilde +!99 U+2122 trademark +!9A U+0161 scaron +!9B U+203A guilsinglright +!9C U+0153 oe +!9F U+0178 Ydieresis +!A0 U+00A0 space +!A1 U+00A1 exclamdown +!A2 U+00A2 cent +!A3 U+00A3 sterling +!A4 U+00A4 currency +!A5 U+00A5 yen +!A6 U+00A6 brokenbar +!A7 U+00A7 section +!A8 U+00A8 dieresis +!A9 U+00A9 copyright +!AA U+00AA ordfeminine +!AB U+00AB guillemotleft +!AC U+00AC logicalnot +!AD U+00AD hyphen +!AE U+00AE registered +!AF U+00AF macron +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+00B2 twosuperior +!B3 U+00B3 threesuperior +!B4 U+00B4 acute +!B5 U+00B5 mu +!B6 U+00B6 paragraph +!B7 U+00B7 periodcentered +!B8 U+00B8 cedilla +!B9 U+00B9 onesuperior +!BA U+00BA ordmasculine +!BB U+00BB guillemotright +!BC U+00BC onequarter +!BD U+00BD onehalf +!BE U+00BE threequarters +!BF U+00BF questiondown +!C0 U+00C0 Agrave +!C1 U+00C1 Aacute +!C2 U+00C2 Acircumflex +!C3 U+00C3 Atilde +!C4 U+00C4 Adieresis +!C5 U+00C5 Aring +!C6 U+00C6 AE +!C7 U+00C7 Ccedilla +!C8 U+00C8 Egrave +!C9 U+00C9 Eacute +!CA U+00CA Ecircumflex +!CB U+00CB Edieresis +!CC U+00CC Igrave +!CD U+00CD Iacute +!CE U+00CE Icircumflex +!CF U+00CF Idieresis +!D0 U+011E Gbreve +!D1 U+00D1 Ntilde +!D2 U+00D2 Ograve +!D3 U+00D3 Oacute +!D4 U+00D4 Ocircumflex +!D5 U+00D5 Otilde +!D6 U+00D6 Odieresis +!D7 U+00D7 multiply +!D8 U+00D8 Oslash +!D9 U+00D9 Ugrave +!DA U+00DA Uacute +!DB U+00DB Ucircumflex +!DC U+00DC Udieresis +!DD U+0130 Idotaccent +!DE U+015E Scedilla +!DF U+00DF germandbls +!E0 U+00E0 agrave +!E1 U+00E1 aacute +!E2 U+00E2 acircumflex +!E3 U+00E3 atilde +!E4 U+00E4 adieresis +!E5 U+00E5 aring +!E6 U+00E6 ae +!E7 U+00E7 ccedilla +!E8 U+00E8 egrave +!E9 U+00E9 eacute +!EA U+00EA ecircumflex +!EB U+00EB edieresis +!EC U+00EC igrave +!ED U+00ED iacute +!EE U+00EE icircumflex +!EF U+00EF idieresis +!F0 U+011F gbreve +!F1 U+00F1 ntilde +!F2 U+00F2 ograve +!F3 U+00F3 oacute +!F4 U+00F4 ocircumflex +!F5 U+00F5 otilde +!F6 U+00F6 odieresis +!F7 U+00F7 divide +!F8 U+00F8 oslash +!F9 U+00F9 ugrave +!FA U+00FA uacute +!FB U+00FB ucircumflex +!FC U+00FC udieresis +!FD U+0131 dotlessi +!FE U+015F scedilla +!FF U+00FF ydieresis diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1255.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1255.map new file mode 100755 index 00000000..079e10c6 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1255.map @@ -0,0 +1,233 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+20AC Euro +!82 U+201A quotesinglbase +!83 U+0192 florin +!84 U+201E quotedblbase +!85 U+2026 ellipsis +!86 U+2020 dagger +!87 U+2021 daggerdbl +!88 U+02C6 circumflex +!89 U+2030 perthousand +!8B U+2039 guilsinglleft +!91 U+2018 quoteleft +!92 U+2019 quoteright +!93 U+201C quotedblleft +!94 U+201D quotedblright +!95 U+2022 bullet +!96 U+2013 endash +!97 U+2014 emdash +!98 U+02DC tilde +!99 U+2122 trademark +!9B U+203A guilsinglright +!A0 U+00A0 space +!A1 U+00A1 exclamdown +!A2 U+00A2 cent +!A3 U+00A3 sterling +!A4 U+20AA afii57636 +!A5 U+00A5 yen +!A6 U+00A6 brokenbar +!A7 U+00A7 section +!A8 U+00A8 dieresis +!A9 U+00A9 copyright +!AA U+00D7 multiply +!AB U+00AB guillemotleft +!AC U+00AC logicalnot +!AD U+00AD sfthyphen +!AE U+00AE registered +!AF U+00AF macron +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+00B2 twosuperior +!B3 U+00B3 threesuperior +!B4 U+00B4 acute +!B5 U+00B5 mu +!B6 U+00B6 paragraph +!B7 U+00B7 middot +!B8 U+00B8 cedilla +!B9 U+00B9 onesuperior +!BA U+00F7 divide +!BB U+00BB guillemotright +!BC U+00BC onequarter +!BD U+00BD onehalf +!BE U+00BE threequarters +!BF U+00BF questiondown +!C0 U+05B0 afii57799 +!C1 U+05B1 afii57801 +!C2 U+05B2 afii57800 +!C3 U+05B3 afii57802 +!C4 U+05B4 afii57793 +!C5 U+05B5 afii57794 +!C6 U+05B6 afii57795 +!C7 U+05B7 afii57798 +!C8 U+05B8 afii57797 +!C9 U+05B9 afii57806 +!CB U+05BB afii57796 +!CC U+05BC afii57807 +!CD U+05BD afii57839 +!CE U+05BE afii57645 +!CF U+05BF afii57841 +!D0 U+05C0 afii57842 +!D1 U+05C1 afii57804 +!D2 U+05C2 afii57803 +!D3 U+05C3 afii57658 +!D4 U+05F0 afii57716 +!D5 U+05F1 afii57717 +!D6 U+05F2 afii57718 +!D7 U+05F3 gereshhebrew +!D8 U+05F4 gershayimhebrew +!E0 U+05D0 afii57664 +!E1 U+05D1 afii57665 +!E2 U+05D2 afii57666 +!E3 U+05D3 afii57667 +!E4 U+05D4 afii57668 +!E5 U+05D5 afii57669 +!E6 U+05D6 afii57670 +!E7 U+05D7 afii57671 +!E8 U+05D8 afii57672 +!E9 U+05D9 afii57673 +!EA U+05DA afii57674 +!EB U+05DB afii57675 +!EC U+05DC afii57676 +!ED U+05DD afii57677 +!EE U+05DE afii57678 +!EF U+05DF afii57679 +!F0 U+05E0 afii57680 +!F1 U+05E1 afii57681 +!F2 U+05E2 afii57682 +!F3 U+05E3 afii57683 +!F4 U+05E4 afii57684 +!F5 U+05E5 afii57685 +!F6 U+05E6 afii57686 +!F7 U+05E7 afii57687 +!F8 U+05E8 afii57688 +!F9 U+05E9 afii57689 +!FA U+05EA afii57690 +!FD U+200E afii299 +!FE U+200F afii300 diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1257.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1257.map new file mode 100755 index 00000000..2f2ecfa2 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1257.map @@ -0,0 +1,244 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+20AC Euro +!82 U+201A quotesinglbase +!84 U+201E quotedblbase +!85 U+2026 ellipsis +!86 U+2020 dagger +!87 U+2021 daggerdbl +!89 U+2030 perthousand +!8B U+2039 guilsinglleft +!8D U+00A8 dieresis +!8E U+02C7 caron +!8F U+00B8 cedilla +!91 U+2018 quoteleft +!92 U+2019 quoteright +!93 U+201C quotedblleft +!94 U+201D quotedblright +!95 U+2022 bullet +!96 U+2013 endash +!97 U+2014 emdash +!99 U+2122 trademark +!9B U+203A guilsinglright +!9D U+00AF macron +!9E U+02DB ogonek +!A0 U+00A0 space +!A2 U+00A2 cent +!A3 U+00A3 sterling +!A4 U+00A4 currency +!A6 U+00A6 brokenbar +!A7 U+00A7 section +!A8 U+00D8 Oslash +!A9 U+00A9 copyright +!AA U+0156 Rcommaaccent +!AB U+00AB guillemotleft +!AC U+00AC logicalnot +!AD U+00AD hyphen +!AE U+00AE registered +!AF U+00C6 AE +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+00B2 twosuperior +!B3 U+00B3 threesuperior +!B4 U+00B4 acute +!B5 U+00B5 mu +!B6 U+00B6 paragraph +!B7 U+00B7 periodcentered +!B8 U+00F8 oslash +!B9 U+00B9 onesuperior +!BA U+0157 rcommaaccent +!BB U+00BB guillemotright +!BC U+00BC onequarter +!BD U+00BD onehalf +!BE U+00BE threequarters +!BF U+00E6 ae +!C0 U+0104 Aogonek +!C1 U+012E Iogonek +!C2 U+0100 Amacron +!C3 U+0106 Cacute +!C4 U+00C4 Adieresis +!C5 U+00C5 Aring +!C6 U+0118 Eogonek +!C7 U+0112 Emacron +!C8 U+010C Ccaron +!C9 U+00C9 Eacute +!CA U+0179 Zacute +!CB U+0116 Edotaccent +!CC U+0122 Gcommaaccent +!CD U+0136 Kcommaaccent +!CE U+012A Imacron +!CF U+013B Lcommaaccent +!D0 U+0160 Scaron +!D1 U+0143 Nacute +!D2 U+0145 Ncommaaccent +!D3 U+00D3 Oacute +!D4 U+014C Omacron +!D5 U+00D5 Otilde +!D6 U+00D6 Odieresis +!D7 U+00D7 multiply +!D8 U+0172 Uogonek +!D9 U+0141 Lslash +!DA U+015A Sacute +!DB U+016A Umacron +!DC U+00DC Udieresis +!DD U+017B Zdotaccent +!DE U+017D Zcaron +!DF U+00DF germandbls +!E0 U+0105 aogonek +!E1 U+012F iogonek +!E2 U+0101 amacron +!E3 U+0107 cacute +!E4 U+00E4 adieresis +!E5 U+00E5 aring +!E6 U+0119 eogonek +!E7 U+0113 emacron +!E8 U+010D ccaron +!E9 U+00E9 eacute +!EA U+017A zacute +!EB U+0117 edotaccent +!EC U+0123 gcommaaccent +!ED U+0137 kcommaaccent +!EE U+012B imacron +!EF U+013C lcommaaccent +!F0 U+0161 scaron +!F1 U+0144 nacute +!F2 U+0146 ncommaaccent +!F3 U+00F3 oacute +!F4 U+014D omacron +!F5 U+00F5 otilde +!F6 U+00F6 odieresis +!F7 U+00F7 divide +!F8 U+0173 uogonek +!F9 U+0142 lslash +!FA U+015B sacute +!FB U+016B umacron +!FC U+00FC udieresis +!FD U+017C zdotaccent +!FE U+017E zcaron +!FF U+02D9 dotaccent diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1258.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1258.map new file mode 100755 index 00000000..fed915f7 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp1258.map @@ -0,0 +1,247 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+20AC Euro +!82 U+201A quotesinglbase +!83 U+0192 florin +!84 U+201E quotedblbase +!85 U+2026 ellipsis +!86 U+2020 dagger +!87 U+2021 daggerdbl +!88 U+02C6 circumflex +!89 U+2030 perthousand +!8B U+2039 guilsinglleft +!8C U+0152 OE +!91 U+2018 quoteleft +!92 U+2019 quoteright +!93 U+201C quotedblleft +!94 U+201D quotedblright +!95 U+2022 bullet +!96 U+2013 endash +!97 U+2014 emdash +!98 U+02DC tilde +!99 U+2122 trademark +!9B U+203A guilsinglright +!9C U+0153 oe +!9F U+0178 Ydieresis +!A0 U+00A0 space +!A1 U+00A1 exclamdown +!A2 U+00A2 cent +!A3 U+00A3 sterling +!A4 U+00A4 currency +!A5 U+00A5 yen +!A6 U+00A6 brokenbar +!A7 U+00A7 section +!A8 U+00A8 dieresis +!A9 U+00A9 copyright +!AA U+00AA ordfeminine +!AB U+00AB guillemotleft +!AC U+00AC logicalnot +!AD U+00AD hyphen +!AE U+00AE registered +!AF U+00AF macron +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+00B2 twosuperior +!B3 U+00B3 threesuperior +!B4 U+00B4 acute +!B5 U+00B5 mu +!B6 U+00B6 paragraph +!B7 U+00B7 periodcentered +!B8 U+00B8 cedilla +!B9 U+00B9 onesuperior +!BA U+00BA ordmasculine +!BB U+00BB guillemotright +!BC U+00BC onequarter +!BD U+00BD onehalf +!BE U+00BE threequarters +!BF U+00BF questiondown +!C0 U+00C0 Agrave +!C1 U+00C1 Aacute +!C2 U+00C2 Acircumflex +!C3 U+0102 Abreve +!C4 U+00C4 Adieresis +!C5 U+00C5 Aring +!C6 U+00C6 AE +!C7 U+00C7 Ccedilla +!C8 U+00C8 Egrave +!C9 U+00C9 Eacute +!CA U+00CA Ecircumflex +!CB U+00CB Edieresis +!CC U+0300 gravecomb +!CD U+00CD Iacute +!CE U+00CE Icircumflex +!CF U+00CF Idieresis +!D0 U+0110 Dcroat +!D1 U+00D1 Ntilde +!D2 U+0309 hookabovecomb +!D3 U+00D3 Oacute +!D4 U+00D4 Ocircumflex +!D5 U+01A0 Ohorn +!D6 U+00D6 Odieresis +!D7 U+00D7 multiply +!D8 U+00D8 Oslash +!D9 U+00D9 Ugrave +!DA U+00DA Uacute +!DB U+00DB Ucircumflex +!DC U+00DC Udieresis +!DD U+01AF Uhorn +!DE U+0303 tildecomb +!DF U+00DF germandbls +!E0 U+00E0 agrave +!E1 U+00E1 aacute +!E2 U+00E2 acircumflex +!E3 U+0103 abreve +!E4 U+00E4 adieresis +!E5 U+00E5 aring +!E6 U+00E6 ae +!E7 U+00E7 ccedilla +!E8 U+00E8 egrave +!E9 U+00E9 eacute +!EA U+00EA ecircumflex +!EB U+00EB edieresis +!EC U+0301 acutecomb +!ED U+00ED iacute +!EE U+00EE icircumflex +!EF U+00EF idieresis +!F0 U+0111 dcroat +!F1 U+00F1 ntilde +!F2 U+0323 dotbelowcomb +!F3 U+00F3 oacute +!F4 U+00F4 ocircumflex +!F5 U+01A1 ohorn +!F6 U+00F6 odieresis +!F7 U+00F7 divide +!F8 U+00F8 oslash +!F9 U+00F9 ugrave +!FA U+00FA uacute +!FB U+00FB ucircumflex +!FC U+00FC udieresis +!FD U+01B0 uhorn +!FE U+20AB dong +!FF U+00FF ydieresis diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp874.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp874.map new file mode 100755 index 00000000..1006e6b1 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/cp874.map @@ -0,0 +1,225 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+20AC Euro +!85 U+2026 ellipsis +!91 U+2018 quoteleft +!92 U+2019 quoteright +!93 U+201C quotedblleft +!94 U+201D quotedblright +!95 U+2022 bullet +!96 U+2013 endash +!97 U+2014 emdash +!A0 U+00A0 space +!A1 U+0E01 kokaithai +!A2 U+0E02 khokhaithai +!A3 U+0E03 khokhuatthai +!A4 U+0E04 khokhwaithai +!A5 U+0E05 khokhonthai +!A6 U+0E06 khorakhangthai +!A7 U+0E07 ngonguthai +!A8 U+0E08 chochanthai +!A9 U+0E09 chochingthai +!AA U+0E0A chochangthai +!AB U+0E0B sosothai +!AC U+0E0C chochoethai +!AD U+0E0D yoyingthai +!AE U+0E0E dochadathai +!AF U+0E0F topatakthai +!B0 U+0E10 thothanthai +!B1 U+0E11 thonangmonthothai +!B2 U+0E12 thophuthaothai +!B3 U+0E13 nonenthai +!B4 U+0E14 dodekthai +!B5 U+0E15 totaothai +!B6 U+0E16 thothungthai +!B7 U+0E17 thothahanthai +!B8 U+0E18 thothongthai +!B9 U+0E19 nonuthai +!BA U+0E1A bobaimaithai +!BB U+0E1B poplathai +!BC U+0E1C phophungthai +!BD U+0E1D fofathai +!BE U+0E1E phophanthai +!BF U+0E1F fofanthai +!C0 U+0E20 phosamphaothai +!C1 U+0E21 momathai +!C2 U+0E22 yoyakthai +!C3 U+0E23 roruathai +!C4 U+0E24 ruthai +!C5 U+0E25 lolingthai +!C6 U+0E26 luthai +!C7 U+0E27 wowaenthai +!C8 U+0E28 sosalathai +!C9 U+0E29 sorusithai +!CA U+0E2A sosuathai +!CB U+0E2B hohipthai +!CC U+0E2C lochulathai +!CD U+0E2D oangthai +!CE U+0E2E honokhukthai +!CF U+0E2F paiyannoithai +!D0 U+0E30 saraathai +!D1 U+0E31 maihanakatthai +!D2 U+0E32 saraaathai +!D3 U+0E33 saraamthai +!D4 U+0E34 saraithai +!D5 U+0E35 saraiithai +!D6 U+0E36 sarauethai +!D7 U+0E37 saraueethai +!D8 U+0E38 sarauthai +!D9 U+0E39 sarauuthai +!DA U+0E3A phinthuthai +!DF U+0E3F bahtthai +!E0 U+0E40 saraethai +!E1 U+0E41 saraaethai +!E2 U+0E42 saraothai +!E3 U+0E43 saraaimaimuanthai +!E4 U+0E44 saraaimaimalaithai +!E5 U+0E45 lakkhangyaothai +!E6 U+0E46 maiyamokthai +!E7 U+0E47 maitaikhuthai +!E8 U+0E48 maiekthai +!E9 U+0E49 maithothai +!EA U+0E4A maitrithai +!EB U+0E4B maichattawathai +!EC U+0E4C thanthakhatthai +!ED U+0E4D nikhahitthai +!EE U+0E4E yamakkanthai +!EF U+0E4F fongmanthai +!F0 U+0E50 zerothai +!F1 U+0E51 onethai +!F2 U+0E52 twothai +!F3 U+0E53 threethai +!F4 U+0E54 fourthai +!F5 U+0E55 fivethai +!F6 U+0E56 sixthai +!F7 U+0E57 seventhai +!F8 U+0E58 eightthai +!F9 U+0E59 ninethai +!FA U+0E5A angkhankhuthai +!FB U+0E5B khomutthai diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/index.html b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/index.html new file mode 100755 index 00000000..3b408bc9 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/index.html @@ -0,0 +1 @@ +


      Colloque GEII Angoulme 2011
      \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-1.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-1.map new file mode 100755 index 00000000..61740a38 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-1.map @@ -0,0 +1,256 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+0080 .notdef +!81 U+0081 .notdef +!82 U+0082 .notdef +!83 U+0083 .notdef +!84 U+0084 .notdef +!85 U+0085 .notdef +!86 U+0086 .notdef +!87 U+0087 .notdef +!88 U+0088 .notdef +!89 U+0089 .notdef +!8A U+008A .notdef +!8B U+008B .notdef +!8C U+008C .notdef +!8D U+008D .notdef +!8E U+008E .notdef +!8F U+008F .notdef +!90 U+0090 .notdef +!91 U+0091 .notdef +!92 U+0092 .notdef +!93 U+0093 .notdef +!94 U+0094 .notdef +!95 U+0095 .notdef +!96 U+0096 .notdef +!97 U+0097 .notdef +!98 U+0098 .notdef +!99 U+0099 .notdef +!9A U+009A .notdef +!9B U+009B .notdef +!9C U+009C .notdef +!9D U+009D .notdef +!9E U+009E .notdef +!9F U+009F .notdef +!A0 U+00A0 space +!A1 U+00A1 exclamdown +!A2 U+00A2 cent +!A3 U+00A3 sterling +!A4 U+00A4 currency +!A5 U+00A5 yen +!A6 U+00A6 brokenbar +!A7 U+00A7 section +!A8 U+00A8 dieresis +!A9 U+00A9 copyright +!AA U+00AA ordfeminine +!AB U+00AB guillemotleft +!AC U+00AC logicalnot +!AD U+00AD hyphen +!AE U+00AE registered +!AF U+00AF macron +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+00B2 twosuperior +!B3 U+00B3 threesuperior +!B4 U+00B4 acute +!B5 U+00B5 mu +!B6 U+00B6 paragraph +!B7 U+00B7 periodcentered +!B8 U+00B8 cedilla +!B9 U+00B9 onesuperior +!BA U+00BA ordmasculine +!BB U+00BB guillemotright +!BC U+00BC onequarter +!BD U+00BD onehalf +!BE U+00BE threequarters +!BF U+00BF questiondown +!C0 U+00C0 Agrave +!C1 U+00C1 Aacute +!C2 U+00C2 Acircumflex +!C3 U+00C3 Atilde +!C4 U+00C4 Adieresis +!C5 U+00C5 Aring +!C6 U+00C6 AE +!C7 U+00C7 Ccedilla +!C8 U+00C8 Egrave +!C9 U+00C9 Eacute +!CA U+00CA Ecircumflex +!CB U+00CB Edieresis +!CC U+00CC Igrave +!CD U+00CD Iacute +!CE U+00CE Icircumflex +!CF U+00CF Idieresis +!D0 U+00D0 Eth +!D1 U+00D1 Ntilde +!D2 U+00D2 Ograve +!D3 U+00D3 Oacute +!D4 U+00D4 Ocircumflex +!D5 U+00D5 Otilde +!D6 U+00D6 Odieresis +!D7 U+00D7 multiply +!D8 U+00D8 Oslash +!D9 U+00D9 Ugrave +!DA U+00DA Uacute +!DB U+00DB Ucircumflex +!DC U+00DC Udieresis +!DD U+00DD Yacute +!DE U+00DE Thorn +!DF U+00DF germandbls +!E0 U+00E0 agrave +!E1 U+00E1 aacute +!E2 U+00E2 acircumflex +!E3 U+00E3 atilde +!E4 U+00E4 adieresis +!E5 U+00E5 aring +!E6 U+00E6 ae +!E7 U+00E7 ccedilla +!E8 U+00E8 egrave +!E9 U+00E9 eacute +!EA U+00EA ecircumflex +!EB U+00EB edieresis +!EC U+00EC igrave +!ED U+00ED iacute +!EE U+00EE icircumflex +!EF U+00EF idieresis +!F0 U+00F0 eth +!F1 U+00F1 ntilde +!F2 U+00F2 ograve +!F3 U+00F3 oacute +!F4 U+00F4 ocircumflex +!F5 U+00F5 otilde +!F6 U+00F6 odieresis +!F7 U+00F7 divide +!F8 U+00F8 oslash +!F9 U+00F9 ugrave +!FA U+00FA uacute +!FB U+00FB ucircumflex +!FC U+00FC udieresis +!FD U+00FD yacute +!FE U+00FE thorn +!FF U+00FF ydieresis diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-11.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-11.map new file mode 100755 index 00000000..91688120 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-11.map @@ -0,0 +1,248 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+0080 .notdef +!81 U+0081 .notdef +!82 U+0082 .notdef +!83 U+0083 .notdef +!84 U+0084 .notdef +!85 U+0085 .notdef +!86 U+0086 .notdef +!87 U+0087 .notdef +!88 U+0088 .notdef +!89 U+0089 .notdef +!8A U+008A .notdef +!8B U+008B .notdef +!8C U+008C .notdef +!8D U+008D .notdef +!8E U+008E .notdef +!8F U+008F .notdef +!90 U+0090 .notdef +!91 U+0091 .notdef +!92 U+0092 .notdef +!93 U+0093 .notdef +!94 U+0094 .notdef +!95 U+0095 .notdef +!96 U+0096 .notdef +!97 U+0097 .notdef +!98 U+0098 .notdef +!99 U+0099 .notdef +!9A U+009A .notdef +!9B U+009B .notdef +!9C U+009C .notdef +!9D U+009D .notdef +!9E U+009E .notdef +!9F U+009F .notdef +!A0 U+00A0 space +!A1 U+0E01 kokaithai +!A2 U+0E02 khokhaithai +!A3 U+0E03 khokhuatthai +!A4 U+0E04 khokhwaithai +!A5 U+0E05 khokhonthai +!A6 U+0E06 khorakhangthai +!A7 U+0E07 ngonguthai +!A8 U+0E08 chochanthai +!A9 U+0E09 chochingthai +!AA U+0E0A chochangthai +!AB U+0E0B sosothai +!AC U+0E0C chochoethai +!AD U+0E0D yoyingthai +!AE U+0E0E dochadathai +!AF U+0E0F topatakthai +!B0 U+0E10 thothanthai +!B1 U+0E11 thonangmonthothai +!B2 U+0E12 thophuthaothai +!B3 U+0E13 nonenthai +!B4 U+0E14 dodekthai +!B5 U+0E15 totaothai +!B6 U+0E16 thothungthai +!B7 U+0E17 thothahanthai +!B8 U+0E18 thothongthai +!B9 U+0E19 nonuthai +!BA U+0E1A bobaimaithai +!BB U+0E1B poplathai +!BC U+0E1C phophungthai +!BD U+0E1D fofathai +!BE U+0E1E phophanthai +!BF U+0E1F fofanthai +!C0 U+0E20 phosamphaothai +!C1 U+0E21 momathai +!C2 U+0E22 yoyakthai +!C3 U+0E23 roruathai +!C4 U+0E24 ruthai +!C5 U+0E25 lolingthai +!C6 U+0E26 luthai +!C7 U+0E27 wowaenthai +!C8 U+0E28 sosalathai +!C9 U+0E29 sorusithai +!CA U+0E2A sosuathai +!CB U+0E2B hohipthai +!CC U+0E2C lochulathai +!CD U+0E2D oangthai +!CE U+0E2E honokhukthai +!CF U+0E2F paiyannoithai +!D0 U+0E30 saraathai +!D1 U+0E31 maihanakatthai +!D2 U+0E32 saraaathai +!D3 U+0E33 saraamthai +!D4 U+0E34 saraithai +!D5 U+0E35 saraiithai +!D6 U+0E36 sarauethai +!D7 U+0E37 saraueethai +!D8 U+0E38 sarauthai +!D9 U+0E39 sarauuthai +!DA U+0E3A phinthuthai +!DF U+0E3F bahtthai +!E0 U+0E40 saraethai +!E1 U+0E41 saraaethai +!E2 U+0E42 saraothai +!E3 U+0E43 saraaimaimuanthai +!E4 U+0E44 saraaimaimalaithai +!E5 U+0E45 lakkhangyaothai +!E6 U+0E46 maiyamokthai +!E7 U+0E47 maitaikhuthai +!E8 U+0E48 maiekthai +!E9 U+0E49 maithothai +!EA U+0E4A maitrithai +!EB U+0E4B maichattawathai +!EC U+0E4C thanthakhatthai +!ED U+0E4D nikhahitthai +!EE U+0E4E yamakkanthai +!EF U+0E4F fongmanthai +!F0 U+0E50 zerothai +!F1 U+0E51 onethai +!F2 U+0E52 twothai +!F3 U+0E53 threethai +!F4 U+0E54 fourthai +!F5 U+0E55 fivethai +!F6 U+0E56 sixthai +!F7 U+0E57 seventhai +!F8 U+0E58 eightthai +!F9 U+0E59 ninethai +!FA U+0E5A angkhankhuthai +!FB U+0E5B khomutthai diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-15.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-15.map new file mode 100755 index 00000000..6c2b5712 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-15.map @@ -0,0 +1,256 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+0080 .notdef +!81 U+0081 .notdef +!82 U+0082 .notdef +!83 U+0083 .notdef +!84 U+0084 .notdef +!85 U+0085 .notdef +!86 U+0086 .notdef +!87 U+0087 .notdef +!88 U+0088 .notdef +!89 U+0089 .notdef +!8A U+008A .notdef +!8B U+008B .notdef +!8C U+008C .notdef +!8D U+008D .notdef +!8E U+008E .notdef +!8F U+008F .notdef +!90 U+0090 .notdef +!91 U+0091 .notdef +!92 U+0092 .notdef +!93 U+0093 .notdef +!94 U+0094 .notdef +!95 U+0095 .notdef +!96 U+0096 .notdef +!97 U+0097 .notdef +!98 U+0098 .notdef +!99 U+0099 .notdef +!9A U+009A .notdef +!9B U+009B .notdef +!9C U+009C .notdef +!9D U+009D .notdef +!9E U+009E .notdef +!9F U+009F .notdef +!A0 U+00A0 space +!A1 U+00A1 exclamdown +!A2 U+00A2 cent +!A3 U+00A3 sterling +!A4 U+20AC Euro +!A5 U+00A5 yen +!A6 U+0160 Scaron +!A7 U+00A7 section +!A8 U+0161 scaron +!A9 U+00A9 copyright +!AA U+00AA ordfeminine +!AB U+00AB guillemotleft +!AC U+00AC logicalnot +!AD U+00AD hyphen +!AE U+00AE registered +!AF U+00AF macron +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+00B2 twosuperior +!B3 U+00B3 threesuperior +!B4 U+017D Zcaron +!B5 U+00B5 mu +!B6 U+00B6 paragraph +!B7 U+00B7 periodcentered +!B8 U+017E zcaron +!B9 U+00B9 onesuperior +!BA U+00BA ordmasculine +!BB U+00BB guillemotright +!BC U+0152 OE +!BD U+0153 oe +!BE U+0178 Ydieresis +!BF U+00BF questiondown +!C0 U+00C0 Agrave +!C1 U+00C1 Aacute +!C2 U+00C2 Acircumflex +!C3 U+00C3 Atilde +!C4 U+00C4 Adieresis +!C5 U+00C5 Aring +!C6 U+00C6 AE +!C7 U+00C7 Ccedilla +!C8 U+00C8 Egrave +!C9 U+00C9 Eacute +!CA U+00CA Ecircumflex +!CB U+00CB Edieresis +!CC U+00CC Igrave +!CD U+00CD Iacute +!CE U+00CE Icircumflex +!CF U+00CF Idieresis +!D0 U+00D0 Eth +!D1 U+00D1 Ntilde +!D2 U+00D2 Ograve +!D3 U+00D3 Oacute +!D4 U+00D4 Ocircumflex +!D5 U+00D5 Otilde +!D6 U+00D6 Odieresis +!D7 U+00D7 multiply +!D8 U+00D8 Oslash +!D9 U+00D9 Ugrave +!DA U+00DA Uacute +!DB U+00DB Ucircumflex +!DC U+00DC Udieresis +!DD U+00DD Yacute +!DE U+00DE Thorn +!DF U+00DF germandbls +!E0 U+00E0 agrave +!E1 U+00E1 aacute +!E2 U+00E2 acircumflex +!E3 U+00E3 atilde +!E4 U+00E4 adieresis +!E5 U+00E5 aring +!E6 U+00E6 ae +!E7 U+00E7 ccedilla +!E8 U+00E8 egrave +!E9 U+00E9 eacute +!EA U+00EA ecircumflex +!EB U+00EB edieresis +!EC U+00EC igrave +!ED U+00ED iacute +!EE U+00EE icircumflex +!EF U+00EF idieresis +!F0 U+00F0 eth +!F1 U+00F1 ntilde +!F2 U+00F2 ograve +!F3 U+00F3 oacute +!F4 U+00F4 ocircumflex +!F5 U+00F5 otilde +!F6 U+00F6 odieresis +!F7 U+00F7 divide +!F8 U+00F8 oslash +!F9 U+00F9 ugrave +!FA U+00FA uacute +!FB U+00FB ucircumflex +!FC U+00FC udieresis +!FD U+00FD yacute +!FE U+00FE thorn +!FF U+00FF ydieresis diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-16.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-16.map new file mode 100755 index 00000000..202c8fe5 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-16.map @@ -0,0 +1,256 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+0080 .notdef +!81 U+0081 .notdef +!82 U+0082 .notdef +!83 U+0083 .notdef +!84 U+0084 .notdef +!85 U+0085 .notdef +!86 U+0086 .notdef +!87 U+0087 .notdef +!88 U+0088 .notdef +!89 U+0089 .notdef +!8A U+008A .notdef +!8B U+008B .notdef +!8C U+008C .notdef +!8D U+008D .notdef +!8E U+008E .notdef +!8F U+008F .notdef +!90 U+0090 .notdef +!91 U+0091 .notdef +!92 U+0092 .notdef +!93 U+0093 .notdef +!94 U+0094 .notdef +!95 U+0095 .notdef +!96 U+0096 .notdef +!97 U+0097 .notdef +!98 U+0098 .notdef +!99 U+0099 .notdef +!9A U+009A .notdef +!9B U+009B .notdef +!9C U+009C .notdef +!9D U+009D .notdef +!9E U+009E .notdef +!9F U+009F .notdef +!A0 U+00A0 space +!A1 U+0104 Aogonek +!A2 U+0105 aogonek +!A3 U+0141 Lslash +!A4 U+20AC Euro +!A5 U+201E quotedblbase +!A6 U+0160 Scaron +!A7 U+00A7 section +!A8 U+0161 scaron +!A9 U+00A9 copyright +!AA U+0218 Scommaaccent +!AB U+00AB guillemotleft +!AC U+0179 Zacute +!AD U+00AD hyphen +!AE U+017A zacute +!AF U+017B Zdotaccent +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+010C Ccaron +!B3 U+0142 lslash +!B4 U+017D Zcaron +!B5 U+201D quotedblright +!B6 U+00B6 paragraph +!B7 U+00B7 periodcentered +!B8 U+017E zcaron +!B9 U+010D ccaron +!BA U+0219 scommaaccent +!BB U+00BB guillemotright +!BC U+0152 OE +!BD U+0153 oe +!BE U+0178 Ydieresis +!BF U+017C zdotaccent +!C0 U+00C0 Agrave +!C1 U+00C1 Aacute +!C2 U+00C2 Acircumflex +!C3 U+0102 Abreve +!C4 U+00C4 Adieresis +!C5 U+0106 Cacute +!C6 U+00C6 AE +!C7 U+00C7 Ccedilla +!C8 U+00C8 Egrave +!C9 U+00C9 Eacute +!CA U+00CA Ecircumflex +!CB U+00CB Edieresis +!CC U+00CC Igrave +!CD U+00CD Iacute +!CE U+00CE Icircumflex +!CF U+00CF Idieresis +!D0 U+0110 Dcroat +!D1 U+0143 Nacute +!D2 U+00D2 Ograve +!D3 U+00D3 Oacute +!D4 U+00D4 Ocircumflex +!D5 U+0150 Ohungarumlaut +!D6 U+00D6 Odieresis +!D7 U+015A Sacute +!D8 U+0170 Uhungarumlaut +!D9 U+00D9 Ugrave +!DA U+00DA Uacute +!DB U+00DB Ucircumflex +!DC U+00DC Udieresis +!DD U+0118 Eogonek +!DE U+021A Tcommaaccent +!DF U+00DF germandbls +!E0 U+00E0 agrave +!E1 U+00E1 aacute +!E2 U+00E2 acircumflex +!E3 U+0103 abreve +!E4 U+00E4 adieresis +!E5 U+0107 cacute +!E6 U+00E6 ae +!E7 U+00E7 ccedilla +!E8 U+00E8 egrave +!E9 U+00E9 eacute +!EA U+00EA ecircumflex +!EB U+00EB edieresis +!EC U+00EC igrave +!ED U+00ED iacute +!EE U+00EE icircumflex +!EF U+00EF idieresis +!F0 U+0111 dcroat +!F1 U+0144 nacute +!F2 U+00F2 ograve +!F3 U+00F3 oacute +!F4 U+00F4 ocircumflex +!F5 U+0151 ohungarumlaut +!F6 U+00F6 odieresis +!F7 U+015B sacute +!F8 U+0171 uhungarumlaut +!F9 U+00F9 ugrave +!FA U+00FA uacute +!FB U+00FB ucircumflex +!FC U+00FC udieresis +!FD U+0119 eogonek +!FE U+021B tcommaaccent +!FF U+00FF ydieresis diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-2.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-2.map new file mode 100755 index 00000000..65ae09f9 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-2.map @@ -0,0 +1,256 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+0080 .notdef +!81 U+0081 .notdef +!82 U+0082 .notdef +!83 U+0083 .notdef +!84 U+0084 .notdef +!85 U+0085 .notdef +!86 U+0086 .notdef +!87 U+0087 .notdef +!88 U+0088 .notdef +!89 U+0089 .notdef +!8A U+008A .notdef +!8B U+008B .notdef +!8C U+008C .notdef +!8D U+008D .notdef +!8E U+008E .notdef +!8F U+008F .notdef +!90 U+0090 .notdef +!91 U+0091 .notdef +!92 U+0092 .notdef +!93 U+0093 .notdef +!94 U+0094 .notdef +!95 U+0095 .notdef +!96 U+0096 .notdef +!97 U+0097 .notdef +!98 U+0098 .notdef +!99 U+0099 .notdef +!9A U+009A .notdef +!9B U+009B .notdef +!9C U+009C .notdef +!9D U+009D .notdef +!9E U+009E .notdef +!9F U+009F .notdef +!A0 U+00A0 space +!A1 U+0104 Aogonek +!A2 U+02D8 breve +!A3 U+0141 Lslash +!A4 U+00A4 currency +!A5 U+013D Lcaron +!A6 U+015A Sacute +!A7 U+00A7 section +!A8 U+00A8 dieresis +!A9 U+0160 Scaron +!AA U+015E Scedilla +!AB U+0164 Tcaron +!AC U+0179 Zacute +!AD U+00AD hyphen +!AE U+017D Zcaron +!AF U+017B Zdotaccent +!B0 U+00B0 degree +!B1 U+0105 aogonek +!B2 U+02DB ogonek +!B3 U+0142 lslash +!B4 U+00B4 acute +!B5 U+013E lcaron +!B6 U+015B sacute +!B7 U+02C7 caron +!B8 U+00B8 cedilla +!B9 U+0161 scaron +!BA U+015F scedilla +!BB U+0165 tcaron +!BC U+017A zacute +!BD U+02DD hungarumlaut +!BE U+017E zcaron +!BF U+017C zdotaccent +!C0 U+0154 Racute +!C1 U+00C1 Aacute +!C2 U+00C2 Acircumflex +!C3 U+0102 Abreve +!C4 U+00C4 Adieresis +!C5 U+0139 Lacute +!C6 U+0106 Cacute +!C7 U+00C7 Ccedilla +!C8 U+010C Ccaron +!C9 U+00C9 Eacute +!CA U+0118 Eogonek +!CB U+00CB Edieresis +!CC U+011A Ecaron +!CD U+00CD Iacute +!CE U+00CE Icircumflex +!CF U+010E Dcaron +!D0 U+0110 Dcroat +!D1 U+0143 Nacute +!D2 U+0147 Ncaron +!D3 U+00D3 Oacute +!D4 U+00D4 Ocircumflex +!D5 U+0150 Ohungarumlaut +!D6 U+00D6 Odieresis +!D7 U+00D7 multiply +!D8 U+0158 Rcaron +!D9 U+016E Uring +!DA U+00DA Uacute +!DB U+0170 Uhungarumlaut +!DC U+00DC Udieresis +!DD U+00DD Yacute +!DE U+0162 Tcommaaccent +!DF U+00DF germandbls +!E0 U+0155 racute +!E1 U+00E1 aacute +!E2 U+00E2 acircumflex +!E3 U+0103 abreve +!E4 U+00E4 adieresis +!E5 U+013A lacute +!E6 U+0107 cacute +!E7 U+00E7 ccedilla +!E8 U+010D ccaron +!E9 U+00E9 eacute +!EA U+0119 eogonek +!EB U+00EB edieresis +!EC U+011B ecaron +!ED U+00ED iacute +!EE U+00EE icircumflex +!EF U+010F dcaron +!F0 U+0111 dcroat +!F1 U+0144 nacute +!F2 U+0148 ncaron +!F3 U+00F3 oacute +!F4 U+00F4 ocircumflex +!F5 U+0151 ohungarumlaut +!F6 U+00F6 odieresis +!F7 U+00F7 divide +!F8 U+0159 rcaron +!F9 U+016F uring +!FA U+00FA uacute +!FB U+0171 uhungarumlaut +!FC U+00FC udieresis +!FD U+00FD yacute +!FE U+0163 tcommaaccent +!FF U+02D9 dotaccent diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-4.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-4.map new file mode 100755 index 00000000..a7d87bf3 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-4.map @@ -0,0 +1,256 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+0080 .notdef +!81 U+0081 .notdef +!82 U+0082 .notdef +!83 U+0083 .notdef +!84 U+0084 .notdef +!85 U+0085 .notdef +!86 U+0086 .notdef +!87 U+0087 .notdef +!88 U+0088 .notdef +!89 U+0089 .notdef +!8A U+008A .notdef +!8B U+008B .notdef +!8C U+008C .notdef +!8D U+008D .notdef +!8E U+008E .notdef +!8F U+008F .notdef +!90 U+0090 .notdef +!91 U+0091 .notdef +!92 U+0092 .notdef +!93 U+0093 .notdef +!94 U+0094 .notdef +!95 U+0095 .notdef +!96 U+0096 .notdef +!97 U+0097 .notdef +!98 U+0098 .notdef +!99 U+0099 .notdef +!9A U+009A .notdef +!9B U+009B .notdef +!9C U+009C .notdef +!9D U+009D .notdef +!9E U+009E .notdef +!9F U+009F .notdef +!A0 U+00A0 space +!A1 U+0104 Aogonek +!A2 U+0138 kgreenlandic +!A3 U+0156 Rcommaaccent +!A4 U+00A4 currency +!A5 U+0128 Itilde +!A6 U+013B Lcommaaccent +!A7 U+00A7 section +!A8 U+00A8 dieresis +!A9 U+0160 Scaron +!AA U+0112 Emacron +!AB U+0122 Gcommaaccent +!AC U+0166 Tbar +!AD U+00AD hyphen +!AE U+017D Zcaron +!AF U+00AF macron +!B0 U+00B0 degree +!B1 U+0105 aogonek +!B2 U+02DB ogonek +!B3 U+0157 rcommaaccent +!B4 U+00B4 acute +!B5 U+0129 itilde +!B6 U+013C lcommaaccent +!B7 U+02C7 caron +!B8 U+00B8 cedilla +!B9 U+0161 scaron +!BA U+0113 emacron +!BB U+0123 gcommaaccent +!BC U+0167 tbar +!BD U+014A Eng +!BE U+017E zcaron +!BF U+014B eng +!C0 U+0100 Amacron +!C1 U+00C1 Aacute +!C2 U+00C2 Acircumflex +!C3 U+00C3 Atilde +!C4 U+00C4 Adieresis +!C5 U+00C5 Aring +!C6 U+00C6 AE +!C7 U+012E Iogonek +!C8 U+010C Ccaron +!C9 U+00C9 Eacute +!CA U+0118 Eogonek +!CB U+00CB Edieresis +!CC U+0116 Edotaccent +!CD U+00CD Iacute +!CE U+00CE Icircumflex +!CF U+012A Imacron +!D0 U+0110 Dcroat +!D1 U+0145 Ncommaaccent +!D2 U+014C Omacron +!D3 U+0136 Kcommaaccent +!D4 U+00D4 Ocircumflex +!D5 U+00D5 Otilde +!D6 U+00D6 Odieresis +!D7 U+00D7 multiply +!D8 U+00D8 Oslash +!D9 U+0172 Uogonek +!DA U+00DA Uacute +!DB U+00DB Ucircumflex +!DC U+00DC Udieresis +!DD U+0168 Utilde +!DE U+016A Umacron +!DF U+00DF germandbls +!E0 U+0101 amacron +!E1 U+00E1 aacute +!E2 U+00E2 acircumflex +!E3 U+00E3 atilde +!E4 U+00E4 adieresis +!E5 U+00E5 aring +!E6 U+00E6 ae +!E7 U+012F iogonek +!E8 U+010D ccaron +!E9 U+00E9 eacute +!EA U+0119 eogonek +!EB U+00EB edieresis +!EC U+0117 edotaccent +!ED U+00ED iacute +!EE U+00EE icircumflex +!EF U+012B imacron +!F0 U+0111 dcroat +!F1 U+0146 ncommaaccent +!F2 U+014D omacron +!F3 U+0137 kcommaaccent +!F4 U+00F4 ocircumflex +!F5 U+00F5 otilde +!F6 U+00F6 odieresis +!F7 U+00F7 divide +!F8 U+00F8 oslash +!F9 U+0173 uogonek +!FA U+00FA uacute +!FB U+00FB ucircumflex +!FC U+00FC udieresis +!FD U+0169 utilde +!FE U+016B umacron +!FF U+02D9 dotaccent diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-5.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-5.map new file mode 100755 index 00000000..f9cd4edc --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-5.map @@ -0,0 +1,256 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+0080 .notdef +!81 U+0081 .notdef +!82 U+0082 .notdef +!83 U+0083 .notdef +!84 U+0084 .notdef +!85 U+0085 .notdef +!86 U+0086 .notdef +!87 U+0087 .notdef +!88 U+0088 .notdef +!89 U+0089 .notdef +!8A U+008A .notdef +!8B U+008B .notdef +!8C U+008C .notdef +!8D U+008D .notdef +!8E U+008E .notdef +!8F U+008F .notdef +!90 U+0090 .notdef +!91 U+0091 .notdef +!92 U+0092 .notdef +!93 U+0093 .notdef +!94 U+0094 .notdef +!95 U+0095 .notdef +!96 U+0096 .notdef +!97 U+0097 .notdef +!98 U+0098 .notdef +!99 U+0099 .notdef +!9A U+009A .notdef +!9B U+009B .notdef +!9C U+009C .notdef +!9D U+009D .notdef +!9E U+009E .notdef +!9F U+009F .notdef +!A0 U+00A0 space +!A1 U+0401 afii10023 +!A2 U+0402 afii10051 +!A3 U+0403 afii10052 +!A4 U+0404 afii10053 +!A5 U+0405 afii10054 +!A6 U+0406 afii10055 +!A7 U+0407 afii10056 +!A8 U+0408 afii10057 +!A9 U+0409 afii10058 +!AA U+040A afii10059 +!AB U+040B afii10060 +!AC U+040C afii10061 +!AD U+00AD hyphen +!AE U+040E afii10062 +!AF U+040F afii10145 +!B0 U+0410 afii10017 +!B1 U+0411 afii10018 +!B2 U+0412 afii10019 +!B3 U+0413 afii10020 +!B4 U+0414 afii10021 +!B5 U+0415 afii10022 +!B6 U+0416 afii10024 +!B7 U+0417 afii10025 +!B8 U+0418 afii10026 +!B9 U+0419 afii10027 +!BA U+041A afii10028 +!BB U+041B afii10029 +!BC U+041C afii10030 +!BD U+041D afii10031 +!BE U+041E afii10032 +!BF U+041F afii10033 +!C0 U+0420 afii10034 +!C1 U+0421 afii10035 +!C2 U+0422 afii10036 +!C3 U+0423 afii10037 +!C4 U+0424 afii10038 +!C5 U+0425 afii10039 +!C6 U+0426 afii10040 +!C7 U+0427 afii10041 +!C8 U+0428 afii10042 +!C9 U+0429 afii10043 +!CA U+042A afii10044 +!CB U+042B afii10045 +!CC U+042C afii10046 +!CD U+042D afii10047 +!CE U+042E afii10048 +!CF U+042F afii10049 +!D0 U+0430 afii10065 +!D1 U+0431 afii10066 +!D2 U+0432 afii10067 +!D3 U+0433 afii10068 +!D4 U+0434 afii10069 +!D5 U+0435 afii10070 +!D6 U+0436 afii10072 +!D7 U+0437 afii10073 +!D8 U+0438 afii10074 +!D9 U+0439 afii10075 +!DA U+043A afii10076 +!DB U+043B afii10077 +!DC U+043C afii10078 +!DD U+043D afii10079 +!DE U+043E afii10080 +!DF U+043F afii10081 +!E0 U+0440 afii10082 +!E1 U+0441 afii10083 +!E2 U+0442 afii10084 +!E3 U+0443 afii10085 +!E4 U+0444 afii10086 +!E5 U+0445 afii10087 +!E6 U+0446 afii10088 +!E7 U+0447 afii10089 +!E8 U+0448 afii10090 +!E9 U+0449 afii10091 +!EA U+044A afii10092 +!EB U+044B afii10093 +!EC U+044C afii10094 +!ED U+044D afii10095 +!EE U+044E afii10096 +!EF U+044F afii10097 +!F0 U+2116 afii61352 +!F1 U+0451 afii10071 +!F2 U+0452 afii10099 +!F3 U+0453 afii10100 +!F4 U+0454 afii10101 +!F5 U+0455 afii10102 +!F6 U+0456 afii10103 +!F7 U+0457 afii10104 +!F8 U+0458 afii10105 +!F9 U+0459 afii10106 +!FA U+045A afii10107 +!FB U+045B afii10108 +!FC U+045C afii10109 +!FD U+00A7 section +!FE U+045E afii10110 +!FF U+045F afii10193 diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-7.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-7.map new file mode 100755 index 00000000..e163796b --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-7.map @@ -0,0 +1,250 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+0080 .notdef +!81 U+0081 .notdef +!82 U+0082 .notdef +!83 U+0083 .notdef +!84 U+0084 .notdef +!85 U+0085 .notdef +!86 U+0086 .notdef +!87 U+0087 .notdef +!88 U+0088 .notdef +!89 U+0089 .notdef +!8A U+008A .notdef +!8B U+008B .notdef +!8C U+008C .notdef +!8D U+008D .notdef +!8E U+008E .notdef +!8F U+008F .notdef +!90 U+0090 .notdef +!91 U+0091 .notdef +!92 U+0092 .notdef +!93 U+0093 .notdef +!94 U+0094 .notdef +!95 U+0095 .notdef +!96 U+0096 .notdef +!97 U+0097 .notdef +!98 U+0098 .notdef +!99 U+0099 .notdef +!9A U+009A .notdef +!9B U+009B .notdef +!9C U+009C .notdef +!9D U+009D .notdef +!9E U+009E .notdef +!9F U+009F .notdef +!A0 U+00A0 space +!A1 U+2018 quoteleft +!A2 U+2019 quoteright +!A3 U+00A3 sterling +!A6 U+00A6 brokenbar +!A7 U+00A7 section +!A8 U+00A8 dieresis +!A9 U+00A9 copyright +!AB U+00AB guillemotleft +!AC U+00AC logicalnot +!AD U+00AD hyphen +!AF U+2015 afii00208 +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+00B2 twosuperior +!B3 U+00B3 threesuperior +!B4 U+0384 tonos +!B5 U+0385 dieresistonos +!B6 U+0386 Alphatonos +!B7 U+00B7 periodcentered +!B8 U+0388 Epsilontonos +!B9 U+0389 Etatonos +!BA U+038A Iotatonos +!BB U+00BB guillemotright +!BC U+038C Omicrontonos +!BD U+00BD onehalf +!BE U+038E Upsilontonos +!BF U+038F Omegatonos +!C0 U+0390 iotadieresistonos +!C1 U+0391 Alpha +!C2 U+0392 Beta +!C3 U+0393 Gamma +!C4 U+0394 Delta +!C5 U+0395 Epsilon +!C6 U+0396 Zeta +!C7 U+0397 Eta +!C8 U+0398 Theta +!C9 U+0399 Iota +!CA U+039A Kappa +!CB U+039B Lambda +!CC U+039C Mu +!CD U+039D Nu +!CE U+039E Xi +!CF U+039F Omicron +!D0 U+03A0 Pi +!D1 U+03A1 Rho +!D3 U+03A3 Sigma +!D4 U+03A4 Tau +!D5 U+03A5 Upsilon +!D6 U+03A6 Phi +!D7 U+03A7 Chi +!D8 U+03A8 Psi +!D9 U+03A9 Omega +!DA U+03AA Iotadieresis +!DB U+03AB Upsilondieresis +!DC U+03AC alphatonos +!DD U+03AD epsilontonos +!DE U+03AE etatonos +!DF U+03AF iotatonos +!E0 U+03B0 upsilondieresistonos +!E1 U+03B1 alpha +!E2 U+03B2 beta +!E3 U+03B3 gamma +!E4 U+03B4 delta +!E5 U+03B5 epsilon +!E6 U+03B6 zeta +!E7 U+03B7 eta +!E8 U+03B8 theta +!E9 U+03B9 iota +!EA U+03BA kappa +!EB U+03BB lambda +!EC U+03BC mu +!ED U+03BD nu +!EE U+03BE xi +!EF U+03BF omicron +!F0 U+03C0 pi +!F1 U+03C1 rho +!F2 U+03C2 sigma1 +!F3 U+03C3 sigma +!F4 U+03C4 tau +!F5 U+03C5 upsilon +!F6 U+03C6 phi +!F7 U+03C7 chi +!F8 U+03C8 psi +!F9 U+03C9 omega +!FA U+03CA iotadieresis +!FB U+03CB upsilondieresis +!FC U+03CC omicrontonos +!FD U+03CD upsilontonos +!FE U+03CE omegatonos diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-9.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-9.map new file mode 100755 index 00000000..48c123ae --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/iso-8859-9.map @@ -0,0 +1,256 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+0080 .notdef +!81 U+0081 .notdef +!82 U+0082 .notdef +!83 U+0083 .notdef +!84 U+0084 .notdef +!85 U+0085 .notdef +!86 U+0086 .notdef +!87 U+0087 .notdef +!88 U+0088 .notdef +!89 U+0089 .notdef +!8A U+008A .notdef +!8B U+008B .notdef +!8C U+008C .notdef +!8D U+008D .notdef +!8E U+008E .notdef +!8F U+008F .notdef +!90 U+0090 .notdef +!91 U+0091 .notdef +!92 U+0092 .notdef +!93 U+0093 .notdef +!94 U+0094 .notdef +!95 U+0095 .notdef +!96 U+0096 .notdef +!97 U+0097 .notdef +!98 U+0098 .notdef +!99 U+0099 .notdef +!9A U+009A .notdef +!9B U+009B .notdef +!9C U+009C .notdef +!9D U+009D .notdef +!9E U+009E .notdef +!9F U+009F .notdef +!A0 U+00A0 space +!A1 U+00A1 exclamdown +!A2 U+00A2 cent +!A3 U+00A3 sterling +!A4 U+00A4 currency +!A5 U+00A5 yen +!A6 U+00A6 brokenbar +!A7 U+00A7 section +!A8 U+00A8 dieresis +!A9 U+00A9 copyright +!AA U+00AA ordfeminine +!AB U+00AB guillemotleft +!AC U+00AC logicalnot +!AD U+00AD hyphen +!AE U+00AE registered +!AF U+00AF macron +!B0 U+00B0 degree +!B1 U+00B1 plusminus +!B2 U+00B2 twosuperior +!B3 U+00B3 threesuperior +!B4 U+00B4 acute +!B5 U+00B5 mu +!B6 U+00B6 paragraph +!B7 U+00B7 periodcentered +!B8 U+00B8 cedilla +!B9 U+00B9 onesuperior +!BA U+00BA ordmasculine +!BB U+00BB guillemotright +!BC U+00BC onequarter +!BD U+00BD onehalf +!BE U+00BE threequarters +!BF U+00BF questiondown +!C0 U+00C0 Agrave +!C1 U+00C1 Aacute +!C2 U+00C2 Acircumflex +!C3 U+00C3 Atilde +!C4 U+00C4 Adieresis +!C5 U+00C5 Aring +!C6 U+00C6 AE +!C7 U+00C7 Ccedilla +!C8 U+00C8 Egrave +!C9 U+00C9 Eacute +!CA U+00CA Ecircumflex +!CB U+00CB Edieresis +!CC U+00CC Igrave +!CD U+00CD Iacute +!CE U+00CE Icircumflex +!CF U+00CF Idieresis +!D0 U+011E Gbreve +!D1 U+00D1 Ntilde +!D2 U+00D2 Ograve +!D3 U+00D3 Oacute +!D4 U+00D4 Ocircumflex +!D5 U+00D5 Otilde +!D6 U+00D6 Odieresis +!D7 U+00D7 multiply +!D8 U+00D8 Oslash +!D9 U+00D9 Ugrave +!DA U+00DA Uacute +!DB U+00DB Ucircumflex +!DC U+00DC Udieresis +!DD U+0130 Idotaccent +!DE U+015E Scedilla +!DF U+00DF germandbls +!E0 U+00E0 agrave +!E1 U+00E1 aacute +!E2 U+00E2 acircumflex +!E3 U+00E3 atilde +!E4 U+00E4 adieresis +!E5 U+00E5 aring +!E6 U+00E6 ae +!E7 U+00E7 ccedilla +!E8 U+00E8 egrave +!E9 U+00E9 eacute +!EA U+00EA ecircumflex +!EB U+00EB edieresis +!EC U+00EC igrave +!ED U+00ED iacute +!EE U+00EE icircumflex +!EF U+00EF idieresis +!F0 U+011F gbreve +!F1 U+00F1 ntilde +!F2 U+00F2 ograve +!F3 U+00F3 oacute +!F4 U+00F4 ocircumflex +!F5 U+00F5 otilde +!F6 U+00F6 odieresis +!F7 U+00F7 divide +!F8 U+00F8 oslash +!F9 U+00F9 ugrave +!FA U+00FA uacute +!FB U+00FB ucircumflex +!FC U+00FC udieresis +!FD U+0131 dotlessi +!FE U+015F scedilla +!FF U+00FF ydieresis diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/koi8-r.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/koi8-r.map new file mode 100755 index 00000000..6ad5d05d --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/koi8-r.map @@ -0,0 +1,256 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+2500 SF100000 +!81 U+2502 SF110000 +!82 U+250C SF010000 +!83 U+2510 SF030000 +!84 U+2514 SF020000 +!85 U+2518 SF040000 +!86 U+251C SF080000 +!87 U+2524 SF090000 +!88 U+252C SF060000 +!89 U+2534 SF070000 +!8A U+253C SF050000 +!8B U+2580 upblock +!8C U+2584 dnblock +!8D U+2588 block +!8E U+258C lfblock +!8F U+2590 rtblock +!90 U+2591 ltshade +!91 U+2592 shade +!92 U+2593 dkshade +!93 U+2320 integraltp +!94 U+25A0 filledbox +!95 U+2219 periodcentered +!96 U+221A radical +!97 U+2248 approxequal +!98 U+2264 lessequal +!99 U+2265 greaterequal +!9A U+00A0 space +!9B U+2321 integralbt +!9C U+00B0 degree +!9D U+00B2 twosuperior +!9E U+00B7 periodcentered +!9F U+00F7 divide +!A0 U+2550 SF430000 +!A1 U+2551 SF240000 +!A2 U+2552 SF510000 +!A3 U+0451 afii10071 +!A4 U+2553 SF520000 +!A5 U+2554 SF390000 +!A6 U+2555 SF220000 +!A7 U+2556 SF210000 +!A8 U+2557 SF250000 +!A9 U+2558 SF500000 +!AA U+2559 SF490000 +!AB U+255A SF380000 +!AC U+255B SF280000 +!AD U+255C SF270000 +!AE U+255D SF260000 +!AF U+255E SF360000 +!B0 U+255F SF370000 +!B1 U+2560 SF420000 +!B2 U+2561 SF190000 +!B3 U+0401 afii10023 +!B4 U+2562 SF200000 +!B5 U+2563 SF230000 +!B6 U+2564 SF470000 +!B7 U+2565 SF480000 +!B8 U+2566 SF410000 +!B9 U+2567 SF450000 +!BA U+2568 SF460000 +!BB U+2569 SF400000 +!BC U+256A SF540000 +!BD U+256B SF530000 +!BE U+256C SF440000 +!BF U+00A9 copyright +!C0 U+044E afii10096 +!C1 U+0430 afii10065 +!C2 U+0431 afii10066 +!C3 U+0446 afii10088 +!C4 U+0434 afii10069 +!C5 U+0435 afii10070 +!C6 U+0444 afii10086 +!C7 U+0433 afii10068 +!C8 U+0445 afii10087 +!C9 U+0438 afii10074 +!CA U+0439 afii10075 +!CB U+043A afii10076 +!CC U+043B afii10077 +!CD U+043C afii10078 +!CE U+043D afii10079 +!CF U+043E afii10080 +!D0 U+043F afii10081 +!D1 U+044F afii10097 +!D2 U+0440 afii10082 +!D3 U+0441 afii10083 +!D4 U+0442 afii10084 +!D5 U+0443 afii10085 +!D6 U+0436 afii10072 +!D7 U+0432 afii10067 +!D8 U+044C afii10094 +!D9 U+044B afii10093 +!DA U+0437 afii10073 +!DB U+0448 afii10090 +!DC U+044D afii10095 +!DD U+0449 afii10091 +!DE U+0447 afii10089 +!DF U+044A afii10092 +!E0 U+042E afii10048 +!E1 U+0410 afii10017 +!E2 U+0411 afii10018 +!E3 U+0426 afii10040 +!E4 U+0414 afii10021 +!E5 U+0415 afii10022 +!E6 U+0424 afii10038 +!E7 U+0413 afii10020 +!E8 U+0425 afii10039 +!E9 U+0418 afii10026 +!EA U+0419 afii10027 +!EB U+041A afii10028 +!EC U+041B afii10029 +!ED U+041C afii10030 +!EE U+041D afii10031 +!EF U+041E afii10032 +!F0 U+041F afii10033 +!F1 U+042F afii10049 +!F2 U+0420 afii10034 +!F3 U+0421 afii10035 +!F4 U+0422 afii10036 +!F5 U+0423 afii10037 +!F6 U+0416 afii10024 +!F7 U+0412 afii10019 +!F8 U+042C afii10046 +!F9 U+042B afii10045 +!FA U+0417 afii10025 +!FB U+0428 afii10042 +!FC U+042D afii10047 +!FD U+0429 afii10043 +!FE U+0427 afii10041 +!FF U+042A afii10044 diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/koi8-u.map b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/koi8-u.map new file mode 100755 index 00000000..40a7e4fd --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/koi8-u.map @@ -0,0 +1,256 @@ +!00 U+0000 .notdef +!01 U+0001 .notdef +!02 U+0002 .notdef +!03 U+0003 .notdef +!04 U+0004 .notdef +!05 U+0005 .notdef +!06 U+0006 .notdef +!07 U+0007 .notdef +!08 U+0008 .notdef +!09 U+0009 .notdef +!0A U+000A .notdef +!0B U+000B .notdef +!0C U+000C .notdef +!0D U+000D .notdef +!0E U+000E .notdef +!0F U+000F .notdef +!10 U+0010 .notdef +!11 U+0011 .notdef +!12 U+0012 .notdef +!13 U+0013 .notdef +!14 U+0014 .notdef +!15 U+0015 .notdef +!16 U+0016 .notdef +!17 U+0017 .notdef +!18 U+0018 .notdef +!19 U+0019 .notdef +!1A U+001A .notdef +!1B U+001B .notdef +!1C U+001C .notdef +!1D U+001D .notdef +!1E U+001E .notdef +!1F U+001F .notdef +!20 U+0020 space +!21 U+0021 exclam +!22 U+0022 quotedbl +!23 U+0023 numbersign +!24 U+0024 dollar +!25 U+0025 percent +!26 U+0026 ampersand +!27 U+0027 quotesingle +!28 U+0028 parenleft +!29 U+0029 parenright +!2A U+002A asterisk +!2B U+002B plus +!2C U+002C comma +!2D U+002D hyphen +!2E U+002E period +!2F U+002F slash +!30 U+0030 zero +!31 U+0031 one +!32 U+0032 two +!33 U+0033 three +!34 U+0034 four +!35 U+0035 five +!36 U+0036 six +!37 U+0037 seven +!38 U+0038 eight +!39 U+0039 nine +!3A U+003A colon +!3B U+003B semicolon +!3C U+003C less +!3D U+003D equal +!3E U+003E greater +!3F U+003F question +!40 U+0040 at +!41 U+0041 A +!42 U+0042 B +!43 U+0043 C +!44 U+0044 D +!45 U+0045 E +!46 U+0046 F +!47 U+0047 G +!48 U+0048 H +!49 U+0049 I +!4A U+004A J +!4B U+004B K +!4C U+004C L +!4D U+004D M +!4E U+004E N +!4F U+004F O +!50 U+0050 P +!51 U+0051 Q +!52 U+0052 R +!53 U+0053 S +!54 U+0054 T +!55 U+0055 U +!56 U+0056 V +!57 U+0057 W +!58 U+0058 X +!59 U+0059 Y +!5A U+005A Z +!5B U+005B bracketleft +!5C U+005C backslash +!5D U+005D bracketright +!5E U+005E asciicircum +!5F U+005F underscore +!60 U+0060 grave +!61 U+0061 a +!62 U+0062 b +!63 U+0063 c +!64 U+0064 d +!65 U+0065 e +!66 U+0066 f +!67 U+0067 g +!68 U+0068 h +!69 U+0069 i +!6A U+006A j +!6B U+006B k +!6C U+006C l +!6D U+006D m +!6E U+006E n +!6F U+006F o +!70 U+0070 p +!71 U+0071 q +!72 U+0072 r +!73 U+0073 s +!74 U+0074 t +!75 U+0075 u +!76 U+0076 v +!77 U+0077 w +!78 U+0078 x +!79 U+0079 y +!7A U+007A z +!7B U+007B braceleft +!7C U+007C bar +!7D U+007D braceright +!7E U+007E asciitilde +!7F U+007F .notdef +!80 U+2500 SF100000 +!81 U+2502 SF110000 +!82 U+250C SF010000 +!83 U+2510 SF030000 +!84 U+2514 SF020000 +!85 U+2518 SF040000 +!86 U+251C SF080000 +!87 U+2524 SF090000 +!88 U+252C SF060000 +!89 U+2534 SF070000 +!8A U+253C SF050000 +!8B U+2580 upblock +!8C U+2584 dnblock +!8D U+2588 block +!8E U+258C lfblock +!8F U+2590 rtblock +!90 U+2591 ltshade +!91 U+2592 shade +!92 U+2593 dkshade +!93 U+2320 integraltp +!94 U+25A0 filledbox +!95 U+2022 bullet +!96 U+221A radical +!97 U+2248 approxequal +!98 U+2264 lessequal +!99 U+2265 greaterequal +!9A U+00A0 space +!9B U+2321 integralbt +!9C U+00B0 degree +!9D U+00B2 twosuperior +!9E U+00B7 periodcentered +!9F U+00F7 divide +!A0 U+2550 SF430000 +!A1 U+2551 SF240000 +!A2 U+2552 SF510000 +!A3 U+0451 afii10071 +!A4 U+0454 afii10101 +!A5 U+2554 SF390000 +!A6 U+0456 afii10103 +!A7 U+0457 afii10104 +!A8 U+2557 SF250000 +!A9 U+2558 SF500000 +!AA U+2559 SF490000 +!AB U+255A SF380000 +!AC U+255B SF280000 +!AD U+0491 afii10098 +!AE U+255D SF260000 +!AF U+255E SF360000 +!B0 U+255F SF370000 +!B1 U+2560 SF420000 +!B2 U+2561 SF190000 +!B3 U+0401 afii10023 +!B4 U+0404 afii10053 +!B5 U+2563 SF230000 +!B6 U+0406 afii10055 +!B7 U+0407 afii10056 +!B8 U+2566 SF410000 +!B9 U+2567 SF450000 +!BA U+2568 SF460000 +!BB U+2569 SF400000 +!BC U+256A SF540000 +!BD U+0490 afii10050 +!BE U+256C SF440000 +!BF U+00A9 copyright +!C0 U+044E afii10096 +!C1 U+0430 afii10065 +!C2 U+0431 afii10066 +!C3 U+0446 afii10088 +!C4 U+0434 afii10069 +!C5 U+0435 afii10070 +!C6 U+0444 afii10086 +!C7 U+0433 afii10068 +!C8 U+0445 afii10087 +!C9 U+0438 afii10074 +!CA U+0439 afii10075 +!CB U+043A afii10076 +!CC U+043B afii10077 +!CD U+043C afii10078 +!CE U+043D afii10079 +!CF U+043E afii10080 +!D0 U+043F afii10081 +!D1 U+044F afii10097 +!D2 U+0440 afii10082 +!D3 U+0441 afii10083 +!D4 U+0442 afii10084 +!D5 U+0443 afii10085 +!D6 U+0436 afii10072 +!D7 U+0432 afii10067 +!D8 U+044C afii10094 +!D9 U+044B afii10093 +!DA U+0437 afii10073 +!DB U+0448 afii10090 +!DC U+044D afii10095 +!DD U+0449 afii10091 +!DE U+0447 afii10089 +!DF U+044A afii10092 +!E0 U+042E afii10048 +!E1 U+0410 afii10017 +!E2 U+0411 afii10018 +!E3 U+0426 afii10040 +!E4 U+0414 afii10021 +!E5 U+0415 afii10022 +!E6 U+0424 afii10038 +!E7 U+0413 afii10020 +!E8 U+0425 afii10039 +!E9 U+0418 afii10026 +!EA U+0419 afii10027 +!EB U+041A afii10028 +!EC U+041B afii10029 +!ED U+041C afii10030 +!EE U+041D afii10031 +!EF U+041E afii10032 +!F0 U+041F afii10033 +!F1 U+042F afii10049 +!F2 U+0420 afii10034 +!F3 U+0421 afii10035 +!F4 U+0422 afii10036 +!F5 U+0423 afii10037 +!F6 U+0416 afii10024 +!F7 U+0412 afii10019 +!F8 U+042C afii10046 +!F9 U+042B afii10045 +!FA U+0417 afii10025 +!FB U+0428 afii10042 +!FC U+042D afii10047 +!FD U+0429 afii10043 +!FE U+0427 afii10041 +!FF U+042A afii10044 diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/makefont/makefont.php b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/makefont.php new file mode 100755 index 00000000..0baa933d --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/makefont/makefont.php @@ -0,0 +1,419 @@ +Error: encoding not found: '.$enc); + $cc2gn=array(); + foreach($a as $l) + { + if($l[0]=='!') + { + $e=preg_split('/[ \\t]+/',rtrim($l)); + $cc=hexdec(substr($e[0],1)); + $gn=$e[2]; + $cc2gn[$cc]=$gn; + } + } + for($i=0;$i<=255;$i++) + { + if(!isset($cc2gn[$i])) + $cc2gn[$i]='.notdef'; + } + return $cc2gn; +} + +function ReadAFM($file, &$map) +{ + //Read a font metric file + $a=file($file); + if(empty($a)) + die('File not found'); + $widths=array(); + $fm=array(); + $fix=array('Edot'=>'Edotaccent','edot'=>'edotaccent','Idot'=>'Idotaccent','Zdot'=>'Zdotaccent','zdot'=>'zdotaccent', + 'Odblacute'=>'Ohungarumlaut','odblacute'=>'ohungarumlaut','Udblacute'=>'Uhungarumlaut','udblacute'=>'uhungarumlaut', + 'Gcedilla'=>'Gcommaaccent','gcedilla'=>'gcommaaccent','Kcedilla'=>'Kcommaaccent','kcedilla'=>'kcommaaccent', + 'Lcedilla'=>'Lcommaaccent','lcedilla'=>'lcommaaccent','Ncedilla'=>'Ncommaaccent','ncedilla'=>'ncommaaccent', + 'Rcedilla'=>'Rcommaaccent','rcedilla'=>'rcommaaccent','Scedilla'=>'Scommaaccent','scedilla'=>'scommaaccent', + 'Tcedilla'=>'Tcommaaccent','tcedilla'=>'tcommaaccent','Dslash'=>'Dcroat','dslash'=>'dcroat','Dmacron'=>'Dcroat','dmacron'=>'dcroat', + 'combininggraveaccent'=>'gravecomb','combininghookabove'=>'hookabovecomb','combiningtildeaccent'=>'tildecomb', + 'combiningacuteaccent'=>'acutecomb','combiningdotbelow'=>'dotbelowcomb','dongsign'=>'dong'); + foreach($a as $l) + { + $e=explode(' ',rtrim($l)); + if(count($e)<2) + continue; + $code=$e[0]; + $param=$e[1]; + if($code=='C') + { + //Character metrics + $cc=(int)$e[1]; + $w=$e[4]; + $gn=$e[7]; + if(substr($gn,-4)=='20AC') + $gn='Euro'; + if(isset($fix[$gn])) + { + //Fix incorrect glyph name + foreach($map as $c=>$n) + { + if($n==$fix[$gn]) + $map[$c]=$gn; + } + } + if(empty($map)) + { + //Symbolic font: use built-in encoding + $widths[$cc]=$w; + } + else + { + $widths[$gn]=$w; + if($gn=='X') + $fm['CapXHeight']=$e[13]; + } + if($gn=='.notdef') + $fm['MissingWidth']=$w; + } + elseif($code=='FontName') + $fm['FontName']=$param; + elseif($code=='Weight') + $fm['Weight']=$param; + elseif($code=='ItalicAngle') + $fm['ItalicAngle']=(double)$param; + elseif($code=='Ascender') + $fm['Ascender']=(int)$param; + elseif($code=='Descender') + $fm['Descender']=(int)$param; + elseif($code=='UnderlineThickness') + $fm['UnderlineThickness']=(int)$param; + elseif($code=='UnderlinePosition') + $fm['UnderlinePosition']=(int)$param; + elseif($code=='IsFixedPitch') + $fm['IsFixedPitch']=($param=='true'); + elseif($code=='FontBBox') + $fm['FontBBox']=array($e[1],$e[2],$e[3],$e[4]); + elseif($code=='CapHeight') + $fm['CapHeight']=(int)$param; + elseif($code=='StdVW') + $fm['StdVW']=(int)$param; + } + if(!isset($fm['FontName'])) + die('FontName not found'); + if(!empty($map)) + { + if(!isset($widths['.notdef'])) + $widths['.notdef']=600; + if(!isset($widths['Delta']) && isset($widths['increment'])) + $widths['Delta']=$widths['increment']; + //Order widths according to map + for($i=0;$i<=255;$i++) + { + if(!isset($widths[$map[$i]])) + { + echo 'Warning: character '.$map[$i].' is missing
      '; + $widths[$i]=$widths['.notdef']; + } + else + $widths[$i]=$widths[$map[$i]]; + } + } + $fm['Widths']=$widths; + return $fm; +} + +function MakeFontDescriptor($fm, $symbolic) +{ + //Ascent + $asc=(isset($fm['Ascender']) ? $fm['Ascender'] : 1000); + $fd="array('Ascent'=>".$asc; + //Descent + $desc=(isset($fm['Descender']) ? $fm['Descender'] : -200); + $fd.=",'Descent'=>".$desc; + //CapHeight + if(isset($fm['CapHeight'])) + $ch=$fm['CapHeight']; + elseif(isset($fm['CapXHeight'])) + $ch=$fm['CapXHeight']; + else + $ch=$asc; + $fd.=",'CapHeight'=>".$ch; + //Flags + $flags=0; + if(isset($fm['IsFixedPitch']) && $fm['IsFixedPitch']) + $flags+=1<<0; + if($symbolic) + $flags+=1<<2; + if(!$symbolic) + $flags+=1<<5; + if(isset($fm['ItalicAngle']) && $fm['ItalicAngle']!=0) + $flags+=1<<6; + $fd.=",'Flags'=>".$flags; + //FontBBox + if(isset($fm['FontBBox'])) + $fbb=$fm['FontBBox']; + else + $fbb=array(0,$desc-100,1000,$asc+100); + $fd.=",'FontBBox'=>'[".$fbb[0].' '.$fbb[1].' '.$fbb[2].' '.$fbb[3]."]'"; + //ItalicAngle + $ia=(isset($fm['ItalicAngle']) ? $fm['ItalicAngle'] : 0); + $fd.=",'ItalicAngle'=>".$ia; + //StemV + if(isset($fm['StdVW'])) + $stemv=$fm['StdVW']; + elseif(isset($fm['Weight']) && preg_match('/bold|black/i',$fm['Weight'])) + $stemv=120; + else + $stemv=70; + $fd.=",'StemV'=>".$stemv; + //MissingWidth + if(isset($fm['MissingWidth'])) + $fd.=",'MissingWidth'=>".$fm['MissingWidth']; + $fd.=')'; + return $fd; +} + +function MakeWidthArray($fm) +{ + //Make character width array + $s="array(\n\t"; + $cw=$fm['Widths']; + for($i=0;$i<=255;$i++) + { + if(chr($i)=="'") + $s.="'\\''"; + elseif(chr($i)=="\\") + $s.="'\\\\'"; + elseif($i>=32 && $i<=126) + $s.="'".chr($i)."'"; + else + $s.="chr($i)"; + $s.='=>'.$fm['Widths'][$i]; + if($i<255) + $s.=','; + if(($i+1)%22==0) + $s.="\n\t"; + } + $s.=')'; + return $s; +} + +function MakeFontEncoding($map) +{ + //Build differences from reference encoding + $ref=ReadMap('cp1252'); + $s=''; + $last=0; + for($i=32;$i<=255;$i++) + { + if($map[$i]!=$ref[$i]) + { + if($i!=$last+1) + $s.=$i.' '; + $last=$i; + $s.='/'.$map[$i].' '; + } + } + return rtrim($s); +} + +function SaveToFile($file, $s, $mode) +{ + $f=fopen($file,'w'.$mode); + if(!$f) + die('Can\'t write to file '.$file); + fwrite($f,$s,strlen($s)); + fclose($f); +} + +function ReadShort($f) +{ + $a=unpack('n1n',fread($f,2)); + return $a['n']; +} + +function ReadLong($f) +{ + $a=unpack('N1N',fread($f,4)); + return $a['N']; +} + +function CheckTTF($file) +{ + //Check if font license allows embedding + $f=fopen($file,'rb'); + if(!$f) + die('Error: Can\'t open '.$file); + //Extract number of tables + fseek($f,4,SEEK_CUR); + $nb=ReadShort($f); + fseek($f,6,SEEK_CUR); + //Seek OS/2 table + $found=false; + for($i=0;$i<$nb;$i++) + { + if(fread($f,4)=='OS/2') + { + $found=true; + break; + } + fseek($f,12,SEEK_CUR); + } + if(!$found) + { + fclose($f); + return; + } + fseek($f,4,SEEK_CUR); + $offset=ReadLong($f); + fseek($f,$offset,SEEK_SET); + //Extract fsType flags + fseek($f,8,SEEK_CUR); + $fsType=ReadShort($f); + $rl=($fsType & 0x02)!=0; + $pp=($fsType & 0x04)!=0; + $e=($fsType & 0x08)!=0; + fclose($f); + if($rl && !$pp && !$e) + echo 'Warning: font license does not allow embedding'; +} + +/******************************************************************************* +* fontfile : chemin du fichier TTF (ou chane vide si pas d'incorporation) * +* afmfile : chemin du fichier AFM * +* enc : encodage (ou chane vide si la police est symbolique) * +* patch : patch optionnel pour l'encodage * +* type : type de la police si fontfile est vide * +*******************************************************************************/ +function MakeFont($fontfile, $afmfile, $enc='cp1252', $patch=array(), $type='TrueType') +{ + //Generate a font definition file + if(get_magic_quotes_runtime()) + @set_magic_quotes_runtime(0); + ini_set('auto_detect_line_endings','1'); + if($enc) + { + $map=ReadMap($enc); + foreach($patch as $cc=>$gn) + $map[$cc]=$gn; + } + else + $map=array(); + if(!file_exists($afmfile)) + die('Error: AFM file not found: '.$afmfile); + $fm=ReadAFM($afmfile,$map); + if($enc) + $diff=MakeFontEncoding($map); + else + $diff=''; + $fd=MakeFontDescriptor($fm,empty($map)); + //Find font type + if($fontfile) + { + $ext=strtolower(substr($fontfile,-3)); + if($ext=='ttf') + $type='TrueType'; + elseif($ext=='pfb') + $type='Type1'; + else + die('Error: unrecognized font file extension: '.$ext); + } + else + { + if($type!='TrueType' && $type!='Type1') + die('Error: incorrect font type: '.$type); + } + //Start generation + $s='Error: font file not found: '.$fontfile); + if($type=='TrueType') + CheckTTF($fontfile); + $f=fopen($fontfile,'rb'); + if(!$f) + die('Error: Can\'t open '.$fontfile); + $file=fread($f,filesize($fontfile)); + fclose($f); + if($type=='Type1') + { + //Find first two sections and discard third one + $header=(ord($file[0])==128); + if($header) + { + //Strip first binary header + $file=substr($file,6); + } + $pos=strpos($file,'eexec'); + if(!$pos) + die('Error: font file does not seem to be valid Type1'); + $size1=$pos+6; + if($header && ord($file[$size1])==128) + { + //Strip second binary header + $file=substr($file,0,$size1).substr($file,$size1+6); + } + $pos=strpos($file,'00000000'); + if(!$pos) + die('Error: font file does not seem to be valid Type1'); + $size2=$pos-$size1; + $file=substr($file,0,$size1+$size2); + } + if(function_exists('gzcompress')) + { + $cmp=$basename.'.z'; + SaveToFile($cmp,gzcompress($file),'b'); + $s.='$file=\''.$cmp."';\n"; + echo 'Font file compressed ('.$cmp.')
      '; + } + else + { + $s.='$file=\''.basename($fontfile)."';\n"; + echo 'Notice: font file could not be compressed (zlib extension not available)
      '; + } + if($type=='Type1') + { + $s.='$size1='.$size1.";\n"; + $s.='$size2='.$size2.";\n"; + } + else + $s.='$originalsize='.filesize($fontfile).";\n"; + } + else + { + //Not embedded font + $s.='$file='."'';\n"; + } + $s.="?>\n"; + SaveToFile($basename.'.php',$s,'t'); + echo 'Font definition file generated ('.$basename.'.php'.')
      '; +} +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/symbol.php b/misc/PublicationBulletins/Portail-LeHavre/font/symbol.php new file mode 100755 index 00000000..43b50e45 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/symbol.php @@ -0,0 +1,15 @@ +250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250, + chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>333,'"'=>713,'#'=>500,'$'=>549,'%'=>833,'&'=>778,'\''=>439,'('=>333,')'=>333,'*'=>500,'+'=>549, + ','=>250,'-'=>549,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>278,';'=>278,'<'=>549,'='=>549,'>'=>549,'?'=>444,'@'=>549,'A'=>722, + 'B'=>667,'C'=>722,'D'=>612,'E'=>611,'F'=>763,'G'=>603,'H'=>722,'I'=>333,'J'=>631,'K'=>722,'L'=>686,'M'=>889,'N'=>722,'O'=>722,'P'=>768,'Q'=>741,'R'=>556,'S'=>592,'T'=>611,'U'=>690,'V'=>439,'W'=>768, + 'X'=>645,'Y'=>795,'Z'=>611,'['=>333,'\\'=>863,']'=>333,'^'=>658,'_'=>500,'`'=>500,'a'=>631,'b'=>549,'c'=>549,'d'=>494,'e'=>439,'f'=>521,'g'=>411,'h'=>603,'i'=>329,'j'=>603,'k'=>549,'l'=>549,'m'=>576, + 'n'=>521,'o'=>549,'p'=>549,'q'=>521,'r'=>549,'s'=>603,'t'=>439,'u'=>576,'v'=>713,'w'=>686,'x'=>493,'y'=>686,'z'=>494,'{'=>480,'|'=>200,'}'=>480,'~'=>549,chr(127)=>0,chr(128)=>0,chr(129)=>0,chr(130)=>0,chr(131)=>0, + chr(132)=>0,chr(133)=>0,chr(134)=>0,chr(135)=>0,chr(136)=>0,chr(137)=>0,chr(138)=>0,chr(139)=>0,chr(140)=>0,chr(141)=>0,chr(142)=>0,chr(143)=>0,chr(144)=>0,chr(145)=>0,chr(146)=>0,chr(147)=>0,chr(148)=>0,chr(149)=>0,chr(150)=>0,chr(151)=>0,chr(152)=>0,chr(153)=>0, + chr(154)=>0,chr(155)=>0,chr(156)=>0,chr(157)=>0,chr(158)=>0,chr(159)=>0,chr(160)=>750,chr(161)=>620,chr(162)=>247,chr(163)=>549,chr(164)=>167,chr(165)=>713,chr(166)=>500,chr(167)=>753,chr(168)=>753,chr(169)=>753,chr(170)=>753,chr(171)=>1042,chr(172)=>987,chr(173)=>603,chr(174)=>987,chr(175)=>603, + chr(176)=>400,chr(177)=>549,chr(178)=>411,chr(179)=>549,chr(180)=>549,chr(181)=>713,chr(182)=>494,chr(183)=>460,chr(184)=>549,chr(185)=>549,chr(186)=>549,chr(187)=>549,chr(188)=>1000,chr(189)=>603,chr(190)=>1000,chr(191)=>658,chr(192)=>823,chr(193)=>686,chr(194)=>795,chr(195)=>987,chr(196)=>768,chr(197)=>768, + chr(198)=>823,chr(199)=>768,chr(200)=>768,chr(201)=>713,chr(202)=>713,chr(203)=>713,chr(204)=>713,chr(205)=>713,chr(206)=>713,chr(207)=>713,chr(208)=>768,chr(209)=>713,chr(210)=>790,chr(211)=>790,chr(212)=>890,chr(213)=>823,chr(214)=>549,chr(215)=>250,chr(216)=>713,chr(217)=>603,chr(218)=>603,chr(219)=>1042, + chr(220)=>987,chr(221)=>603,chr(222)=>987,chr(223)=>603,chr(224)=>494,chr(225)=>329,chr(226)=>790,chr(227)=>790,chr(228)=>786,chr(229)=>713,chr(230)=>384,chr(231)=>384,chr(232)=>384,chr(233)=>384,chr(234)=>384,chr(235)=>384,chr(236)=>494,chr(237)=>494,chr(238)=>494,chr(239)=>494,chr(240)=>0,chr(241)=>329, + chr(242)=>274,chr(243)=>686,chr(244)=>686,chr(245)=>686,chr(246)=>384,chr(247)=>384,chr(248)=>384,chr(249)=>384,chr(250)=>384,chr(251)=>384,chr(252)=>494,chr(253)=>494,chr(254)=>494,chr(255)=>0); +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/times.php b/misc/PublicationBulletins/Portail-LeHavre/font/times.php new file mode 100755 index 00000000..837c706e --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/times.php @@ -0,0 +1,15 @@ +250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250, + chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>333,'"'=>408,'#'=>500,'$'=>500,'%'=>833,'&'=>778,'\''=>180,'('=>333,')'=>333,'*'=>500,'+'=>564, + ','=>250,'-'=>333,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>278,';'=>278,'<'=>564,'='=>564,'>'=>564,'?'=>444,'@'=>921,'A'=>722, + 'B'=>667,'C'=>667,'D'=>722,'E'=>611,'F'=>556,'G'=>722,'H'=>722,'I'=>333,'J'=>389,'K'=>722,'L'=>611,'M'=>889,'N'=>722,'O'=>722,'P'=>556,'Q'=>722,'R'=>667,'S'=>556,'T'=>611,'U'=>722,'V'=>722,'W'=>944, + 'X'=>722,'Y'=>722,'Z'=>611,'['=>333,'\\'=>278,']'=>333,'^'=>469,'_'=>500,'`'=>333,'a'=>444,'b'=>500,'c'=>444,'d'=>500,'e'=>444,'f'=>333,'g'=>500,'h'=>500,'i'=>278,'j'=>278,'k'=>500,'l'=>278,'m'=>778, + 'n'=>500,'o'=>500,'p'=>500,'q'=>500,'r'=>333,'s'=>389,'t'=>278,'u'=>500,'v'=>500,'w'=>722,'x'=>500,'y'=>500,'z'=>444,'{'=>480,'|'=>200,'}'=>480,'~'=>541,chr(127)=>350,chr(128)=>500,chr(129)=>350,chr(130)=>333,chr(131)=>500, + chr(132)=>444,chr(133)=>1000,chr(134)=>500,chr(135)=>500,chr(136)=>333,chr(137)=>1000,chr(138)=>556,chr(139)=>333,chr(140)=>889,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>333,chr(146)=>333,chr(147)=>444,chr(148)=>444,chr(149)=>350,chr(150)=>500,chr(151)=>1000,chr(152)=>333,chr(153)=>980, + chr(154)=>389,chr(155)=>333,chr(156)=>722,chr(157)=>350,chr(158)=>444,chr(159)=>722,chr(160)=>250,chr(161)=>333,chr(162)=>500,chr(163)=>500,chr(164)=>500,chr(165)=>500,chr(166)=>200,chr(167)=>500,chr(168)=>333,chr(169)=>760,chr(170)=>276,chr(171)=>500,chr(172)=>564,chr(173)=>333,chr(174)=>760,chr(175)=>333, + chr(176)=>400,chr(177)=>564,chr(178)=>300,chr(179)=>300,chr(180)=>333,chr(181)=>500,chr(182)=>453,chr(183)=>250,chr(184)=>333,chr(185)=>300,chr(186)=>310,chr(187)=>500,chr(188)=>750,chr(189)=>750,chr(190)=>750,chr(191)=>444,chr(192)=>722,chr(193)=>722,chr(194)=>722,chr(195)=>722,chr(196)=>722,chr(197)=>722, + chr(198)=>889,chr(199)=>667,chr(200)=>611,chr(201)=>611,chr(202)=>611,chr(203)=>611,chr(204)=>333,chr(205)=>333,chr(206)=>333,chr(207)=>333,chr(208)=>722,chr(209)=>722,chr(210)=>722,chr(211)=>722,chr(212)=>722,chr(213)=>722,chr(214)=>722,chr(215)=>564,chr(216)=>722,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>722,chr(222)=>556,chr(223)=>500,chr(224)=>444,chr(225)=>444,chr(226)=>444,chr(227)=>444,chr(228)=>444,chr(229)=>444,chr(230)=>667,chr(231)=>444,chr(232)=>444,chr(233)=>444,chr(234)=>444,chr(235)=>444,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>500,chr(241)=>500, + chr(242)=>500,chr(243)=>500,chr(244)=>500,chr(245)=>500,chr(246)=>500,chr(247)=>564,chr(248)=>500,chr(249)=>500,chr(250)=>500,chr(251)=>500,chr(252)=>500,chr(253)=>500,chr(254)=>500,chr(255)=>500); +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/timesb.php b/misc/PublicationBulletins/Portail-LeHavre/font/timesb.php new file mode 100755 index 00000000..09cff86a --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/timesb.php @@ -0,0 +1,15 @@ +250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250, + chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>333,'"'=>555,'#'=>500,'$'=>500,'%'=>1000,'&'=>833,'\''=>278,'('=>333,')'=>333,'*'=>500,'+'=>570, + ','=>250,'-'=>333,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>333,';'=>333,'<'=>570,'='=>570,'>'=>570,'?'=>500,'@'=>930,'A'=>722, + 'B'=>667,'C'=>722,'D'=>722,'E'=>667,'F'=>611,'G'=>778,'H'=>778,'I'=>389,'J'=>500,'K'=>778,'L'=>667,'M'=>944,'N'=>722,'O'=>778,'P'=>611,'Q'=>778,'R'=>722,'S'=>556,'T'=>667,'U'=>722,'V'=>722,'W'=>1000, + 'X'=>722,'Y'=>722,'Z'=>667,'['=>333,'\\'=>278,']'=>333,'^'=>581,'_'=>500,'`'=>333,'a'=>500,'b'=>556,'c'=>444,'d'=>556,'e'=>444,'f'=>333,'g'=>500,'h'=>556,'i'=>278,'j'=>333,'k'=>556,'l'=>278,'m'=>833, + 'n'=>556,'o'=>500,'p'=>556,'q'=>556,'r'=>444,'s'=>389,'t'=>333,'u'=>556,'v'=>500,'w'=>722,'x'=>500,'y'=>500,'z'=>444,'{'=>394,'|'=>220,'}'=>394,'~'=>520,chr(127)=>350,chr(128)=>500,chr(129)=>350,chr(130)=>333,chr(131)=>500, + chr(132)=>500,chr(133)=>1000,chr(134)=>500,chr(135)=>500,chr(136)=>333,chr(137)=>1000,chr(138)=>556,chr(139)=>333,chr(140)=>1000,chr(141)=>350,chr(142)=>667,chr(143)=>350,chr(144)=>350,chr(145)=>333,chr(146)=>333,chr(147)=>500,chr(148)=>500,chr(149)=>350,chr(150)=>500,chr(151)=>1000,chr(152)=>333,chr(153)=>1000, + chr(154)=>389,chr(155)=>333,chr(156)=>722,chr(157)=>350,chr(158)=>444,chr(159)=>722,chr(160)=>250,chr(161)=>333,chr(162)=>500,chr(163)=>500,chr(164)=>500,chr(165)=>500,chr(166)=>220,chr(167)=>500,chr(168)=>333,chr(169)=>747,chr(170)=>300,chr(171)=>500,chr(172)=>570,chr(173)=>333,chr(174)=>747,chr(175)=>333, + chr(176)=>400,chr(177)=>570,chr(178)=>300,chr(179)=>300,chr(180)=>333,chr(181)=>556,chr(182)=>540,chr(183)=>250,chr(184)=>333,chr(185)=>300,chr(186)=>330,chr(187)=>500,chr(188)=>750,chr(189)=>750,chr(190)=>750,chr(191)=>500,chr(192)=>722,chr(193)=>722,chr(194)=>722,chr(195)=>722,chr(196)=>722,chr(197)=>722, + chr(198)=>1000,chr(199)=>722,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>389,chr(205)=>389,chr(206)=>389,chr(207)=>389,chr(208)=>722,chr(209)=>722,chr(210)=>778,chr(211)=>778,chr(212)=>778,chr(213)=>778,chr(214)=>778,chr(215)=>570,chr(216)=>778,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>722,chr(222)=>611,chr(223)=>556,chr(224)=>500,chr(225)=>500,chr(226)=>500,chr(227)=>500,chr(228)=>500,chr(229)=>500,chr(230)=>722,chr(231)=>444,chr(232)=>444,chr(233)=>444,chr(234)=>444,chr(235)=>444,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>500,chr(241)=>556, + chr(242)=>500,chr(243)=>500,chr(244)=>500,chr(245)=>500,chr(246)=>500,chr(247)=>570,chr(248)=>500,chr(249)=>556,chr(250)=>556,chr(251)=>556,chr(252)=>556,chr(253)=>500,chr(254)=>556,chr(255)=>500); +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/timesbi.php b/misc/PublicationBulletins/Portail-LeHavre/font/timesbi.php new file mode 100755 index 00000000..b4e38d76 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/timesbi.php @@ -0,0 +1,15 @@ +250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250, + chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>389,'"'=>555,'#'=>500,'$'=>500,'%'=>833,'&'=>778,'\''=>278,'('=>333,')'=>333,'*'=>500,'+'=>570, + ','=>250,'-'=>333,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>333,';'=>333,'<'=>570,'='=>570,'>'=>570,'?'=>500,'@'=>832,'A'=>667, + 'B'=>667,'C'=>667,'D'=>722,'E'=>667,'F'=>667,'G'=>722,'H'=>778,'I'=>389,'J'=>500,'K'=>667,'L'=>611,'M'=>889,'N'=>722,'O'=>722,'P'=>611,'Q'=>722,'R'=>667,'S'=>556,'T'=>611,'U'=>722,'V'=>667,'W'=>889, + 'X'=>667,'Y'=>611,'Z'=>611,'['=>333,'\\'=>278,']'=>333,'^'=>570,'_'=>500,'`'=>333,'a'=>500,'b'=>500,'c'=>444,'d'=>500,'e'=>444,'f'=>333,'g'=>500,'h'=>556,'i'=>278,'j'=>278,'k'=>500,'l'=>278,'m'=>778, + 'n'=>556,'o'=>500,'p'=>500,'q'=>500,'r'=>389,'s'=>389,'t'=>278,'u'=>556,'v'=>444,'w'=>667,'x'=>500,'y'=>444,'z'=>389,'{'=>348,'|'=>220,'}'=>348,'~'=>570,chr(127)=>350,chr(128)=>500,chr(129)=>350,chr(130)=>333,chr(131)=>500, + chr(132)=>500,chr(133)=>1000,chr(134)=>500,chr(135)=>500,chr(136)=>333,chr(137)=>1000,chr(138)=>556,chr(139)=>333,chr(140)=>944,chr(141)=>350,chr(142)=>611,chr(143)=>350,chr(144)=>350,chr(145)=>333,chr(146)=>333,chr(147)=>500,chr(148)=>500,chr(149)=>350,chr(150)=>500,chr(151)=>1000,chr(152)=>333,chr(153)=>1000, + chr(154)=>389,chr(155)=>333,chr(156)=>722,chr(157)=>350,chr(158)=>389,chr(159)=>611,chr(160)=>250,chr(161)=>389,chr(162)=>500,chr(163)=>500,chr(164)=>500,chr(165)=>500,chr(166)=>220,chr(167)=>500,chr(168)=>333,chr(169)=>747,chr(170)=>266,chr(171)=>500,chr(172)=>606,chr(173)=>333,chr(174)=>747,chr(175)=>333, + chr(176)=>400,chr(177)=>570,chr(178)=>300,chr(179)=>300,chr(180)=>333,chr(181)=>576,chr(182)=>500,chr(183)=>250,chr(184)=>333,chr(185)=>300,chr(186)=>300,chr(187)=>500,chr(188)=>750,chr(189)=>750,chr(190)=>750,chr(191)=>500,chr(192)=>667,chr(193)=>667,chr(194)=>667,chr(195)=>667,chr(196)=>667,chr(197)=>667, + chr(198)=>944,chr(199)=>667,chr(200)=>667,chr(201)=>667,chr(202)=>667,chr(203)=>667,chr(204)=>389,chr(205)=>389,chr(206)=>389,chr(207)=>389,chr(208)=>722,chr(209)=>722,chr(210)=>722,chr(211)=>722,chr(212)=>722,chr(213)=>722,chr(214)=>722,chr(215)=>570,chr(216)=>722,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>611,chr(222)=>611,chr(223)=>500,chr(224)=>500,chr(225)=>500,chr(226)=>500,chr(227)=>500,chr(228)=>500,chr(229)=>500,chr(230)=>722,chr(231)=>444,chr(232)=>444,chr(233)=>444,chr(234)=>444,chr(235)=>444,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>500,chr(241)=>556, + chr(242)=>500,chr(243)=>500,chr(244)=>500,chr(245)=>500,chr(246)=>500,chr(247)=>570,chr(248)=>500,chr(249)=>556,chr(250)=>556,chr(251)=>556,chr(252)=>556,chr(253)=>444,chr(254)=>500,chr(255)=>444); +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/timesi.php b/misc/PublicationBulletins/Portail-LeHavre/font/timesi.php new file mode 100755 index 00000000..0ba2b777 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/timesi.php @@ -0,0 +1,15 @@ +250,chr(1)=>250,chr(2)=>250,chr(3)=>250,chr(4)=>250,chr(5)=>250,chr(6)=>250,chr(7)=>250,chr(8)=>250,chr(9)=>250,chr(10)=>250,chr(11)=>250,chr(12)=>250,chr(13)=>250,chr(14)=>250,chr(15)=>250,chr(16)=>250,chr(17)=>250,chr(18)=>250,chr(19)=>250,chr(20)=>250,chr(21)=>250, + chr(22)=>250,chr(23)=>250,chr(24)=>250,chr(25)=>250,chr(26)=>250,chr(27)=>250,chr(28)=>250,chr(29)=>250,chr(30)=>250,chr(31)=>250,' '=>250,'!'=>333,'"'=>420,'#'=>500,'$'=>500,'%'=>833,'&'=>778,'\''=>214,'('=>333,')'=>333,'*'=>500,'+'=>675, + ','=>250,'-'=>333,'.'=>250,'/'=>278,'0'=>500,'1'=>500,'2'=>500,'3'=>500,'4'=>500,'5'=>500,'6'=>500,'7'=>500,'8'=>500,'9'=>500,':'=>333,';'=>333,'<'=>675,'='=>675,'>'=>675,'?'=>500,'@'=>920,'A'=>611, + 'B'=>611,'C'=>667,'D'=>722,'E'=>611,'F'=>611,'G'=>722,'H'=>722,'I'=>333,'J'=>444,'K'=>667,'L'=>556,'M'=>833,'N'=>667,'O'=>722,'P'=>611,'Q'=>722,'R'=>611,'S'=>500,'T'=>556,'U'=>722,'V'=>611,'W'=>833, + 'X'=>611,'Y'=>556,'Z'=>556,'['=>389,'\\'=>278,']'=>389,'^'=>422,'_'=>500,'`'=>333,'a'=>500,'b'=>500,'c'=>444,'d'=>500,'e'=>444,'f'=>278,'g'=>500,'h'=>500,'i'=>278,'j'=>278,'k'=>444,'l'=>278,'m'=>722, + 'n'=>500,'o'=>500,'p'=>500,'q'=>500,'r'=>389,'s'=>389,'t'=>278,'u'=>500,'v'=>444,'w'=>667,'x'=>444,'y'=>444,'z'=>389,'{'=>400,'|'=>275,'}'=>400,'~'=>541,chr(127)=>350,chr(128)=>500,chr(129)=>350,chr(130)=>333,chr(131)=>500, + chr(132)=>556,chr(133)=>889,chr(134)=>500,chr(135)=>500,chr(136)=>333,chr(137)=>1000,chr(138)=>500,chr(139)=>333,chr(140)=>944,chr(141)=>350,chr(142)=>556,chr(143)=>350,chr(144)=>350,chr(145)=>333,chr(146)=>333,chr(147)=>556,chr(148)=>556,chr(149)=>350,chr(150)=>500,chr(151)=>889,chr(152)=>333,chr(153)=>980, + chr(154)=>389,chr(155)=>333,chr(156)=>667,chr(157)=>350,chr(158)=>389,chr(159)=>556,chr(160)=>250,chr(161)=>389,chr(162)=>500,chr(163)=>500,chr(164)=>500,chr(165)=>500,chr(166)=>275,chr(167)=>500,chr(168)=>333,chr(169)=>760,chr(170)=>276,chr(171)=>500,chr(172)=>675,chr(173)=>333,chr(174)=>760,chr(175)=>333, + chr(176)=>400,chr(177)=>675,chr(178)=>300,chr(179)=>300,chr(180)=>333,chr(181)=>500,chr(182)=>523,chr(183)=>250,chr(184)=>333,chr(185)=>300,chr(186)=>310,chr(187)=>500,chr(188)=>750,chr(189)=>750,chr(190)=>750,chr(191)=>500,chr(192)=>611,chr(193)=>611,chr(194)=>611,chr(195)=>611,chr(196)=>611,chr(197)=>611, + chr(198)=>889,chr(199)=>667,chr(200)=>611,chr(201)=>611,chr(202)=>611,chr(203)=>611,chr(204)=>333,chr(205)=>333,chr(206)=>333,chr(207)=>333,chr(208)=>722,chr(209)=>667,chr(210)=>722,chr(211)=>722,chr(212)=>722,chr(213)=>722,chr(214)=>722,chr(215)=>675,chr(216)=>722,chr(217)=>722,chr(218)=>722,chr(219)=>722, + chr(220)=>722,chr(221)=>556,chr(222)=>611,chr(223)=>500,chr(224)=>500,chr(225)=>500,chr(226)=>500,chr(227)=>500,chr(228)=>500,chr(229)=>500,chr(230)=>667,chr(231)=>444,chr(232)=>444,chr(233)=>444,chr(234)=>444,chr(235)=>444,chr(236)=>278,chr(237)=>278,chr(238)=>278,chr(239)=>278,chr(240)=>500,chr(241)=>500, + chr(242)=>500,chr(243)=>500,chr(244)=>500,chr(245)=>500,chr(246)=>500,chr(247)=>675,chr(248)=>500,chr(249)=>500,chr(250)=>500,chr(251)=>500,chr(252)=>500,chr(253)=>444,chr(254)=>500,chr(255)=>444); +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/font/zapfdingbats.php b/misc/PublicationBulletins/Portail-LeHavre/font/zapfdingbats.php new file mode 100755 index 00000000..1f926a8c --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/font/zapfdingbats.php @@ -0,0 +1,15 @@ +0,chr(1)=>0,chr(2)=>0,chr(3)=>0,chr(4)=>0,chr(5)=>0,chr(6)=>0,chr(7)=>0,chr(8)=>0,chr(9)=>0,chr(10)=>0,chr(11)=>0,chr(12)=>0,chr(13)=>0,chr(14)=>0,chr(15)=>0,chr(16)=>0,chr(17)=>0,chr(18)=>0,chr(19)=>0,chr(20)=>0,chr(21)=>0, + chr(22)=>0,chr(23)=>0,chr(24)=>0,chr(25)=>0,chr(26)=>0,chr(27)=>0,chr(28)=>0,chr(29)=>0,chr(30)=>0,chr(31)=>0,' '=>278,'!'=>974,'"'=>961,'#'=>974,'$'=>980,'%'=>719,'&'=>789,'\''=>790,'('=>791,')'=>690,'*'=>960,'+'=>939, + ','=>549,'-'=>855,'.'=>911,'/'=>933,'0'=>911,'1'=>945,'2'=>974,'3'=>755,'4'=>846,'5'=>762,'6'=>761,'7'=>571,'8'=>677,'9'=>763,':'=>760,';'=>759,'<'=>754,'='=>494,'>'=>552,'?'=>537,'@'=>577,'A'=>692, + 'B'=>786,'C'=>788,'D'=>788,'E'=>790,'F'=>793,'G'=>794,'H'=>816,'I'=>823,'J'=>789,'K'=>841,'L'=>823,'M'=>833,'N'=>816,'O'=>831,'P'=>923,'Q'=>744,'R'=>723,'S'=>749,'T'=>790,'U'=>792,'V'=>695,'W'=>776, + 'X'=>768,'Y'=>792,'Z'=>759,'['=>707,'\\'=>708,']'=>682,'^'=>701,'_'=>826,'`'=>815,'a'=>789,'b'=>789,'c'=>707,'d'=>687,'e'=>696,'f'=>689,'g'=>786,'h'=>787,'i'=>713,'j'=>791,'k'=>785,'l'=>791,'m'=>873, + 'n'=>761,'o'=>762,'p'=>762,'q'=>759,'r'=>759,'s'=>892,'t'=>892,'u'=>788,'v'=>784,'w'=>438,'x'=>138,'y'=>277,'z'=>415,'{'=>392,'|'=>392,'}'=>668,'~'=>668,chr(127)=>0,chr(128)=>390,chr(129)=>390,chr(130)=>317,chr(131)=>317, + chr(132)=>276,chr(133)=>276,chr(134)=>509,chr(135)=>509,chr(136)=>410,chr(137)=>410,chr(138)=>234,chr(139)=>234,chr(140)=>334,chr(141)=>334,chr(142)=>0,chr(143)=>0,chr(144)=>0,chr(145)=>0,chr(146)=>0,chr(147)=>0,chr(148)=>0,chr(149)=>0,chr(150)=>0,chr(151)=>0,chr(152)=>0,chr(153)=>0, + chr(154)=>0,chr(155)=>0,chr(156)=>0,chr(157)=>0,chr(158)=>0,chr(159)=>0,chr(160)=>0,chr(161)=>732,chr(162)=>544,chr(163)=>544,chr(164)=>910,chr(165)=>667,chr(166)=>760,chr(167)=>760,chr(168)=>776,chr(169)=>595,chr(170)=>694,chr(171)=>626,chr(172)=>788,chr(173)=>788,chr(174)=>788,chr(175)=>788, + chr(176)=>788,chr(177)=>788,chr(178)=>788,chr(179)=>788,chr(180)=>788,chr(181)=>788,chr(182)=>788,chr(183)=>788,chr(184)=>788,chr(185)=>788,chr(186)=>788,chr(187)=>788,chr(188)=>788,chr(189)=>788,chr(190)=>788,chr(191)=>788,chr(192)=>788,chr(193)=>788,chr(194)=>788,chr(195)=>788,chr(196)=>788,chr(197)=>788, + chr(198)=>788,chr(199)=>788,chr(200)=>788,chr(201)=>788,chr(202)=>788,chr(203)=>788,chr(204)=>788,chr(205)=>788,chr(206)=>788,chr(207)=>788,chr(208)=>788,chr(209)=>788,chr(210)=>788,chr(211)=>788,chr(212)=>894,chr(213)=>838,chr(214)=>1016,chr(215)=>458,chr(216)=>748,chr(217)=>924,chr(218)=>748,chr(219)=>918, + chr(220)=>927,chr(221)=>928,chr(222)=>928,chr(223)=>834,chr(224)=>873,chr(225)=>828,chr(226)=>924,chr(227)=>924,chr(228)=>917,chr(229)=>930,chr(230)=>931,chr(231)=>463,chr(232)=>883,chr(233)=>836,chr(234)=>836,chr(235)=>867,chr(236)=>867,chr(237)=>696,chr(238)=>696,chr(239)=>874,chr(240)=>0,chr(241)=>874, + chr(242)=>760,chr(243)=>946,chr(244)=>771,chr(245)=>865,chr(246)=>771,chr(247)=>888,chr(248)=>967,chr(249)=>888,chr(250)=>831,chr(251)=>873,chr(252)=>927,chr(253)=>970,chr(254)=>918,chr(255)=>0); +?> diff --git a/misc/PublicationBulletins/Portail-LeHavre/fpdf.php b/misc/PublicationBulletins/Portail-LeHavre/fpdf.php new file mode 100755 index 00000000..cf642dd0 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/fpdf.php @@ -0,0 +1,2105 @@ + * +* License: LGPL * +*******************************************************************************/ + +define('FPDF_VERSION','1.03'); + +class FPDF +{ + +var $unifontSubset; +var $extraFontSubsets = 0; +var $t1asm; + +var $page; //current page number +var $n; //current object number +var $offsets; //array of object offsets +var $buffer; //buffer holding in-memory PDF +var $pages; //array containing pages +var $state; //current document state +var $compress; //compression flag +var $k; //scale factor (number of points in user unit) +var $DefOrientation; //default orientation +var $CurOrientation; //current orientation +var $PageFormats; //available page formats +var $DefPageFormat; //default page format +var $CurPageFormat; //current page format +var $PageSizes; //array storing non-default page sizes +var $wPt,$hPt; //dimensions of current page in points +var $w,$h; //dimensions of current page in user unit +var $lMargin; //left margin +var $tMargin; //top margin +var $rMargin; //right margin +var $bMargin; //page break margin +var $cMargin; //cell margin +var $x,$y; //current position in user unit +var $lasth; //height of last printed cell +var $LineWidth; //line width in user unit +var $CoreFonts; //array of standard font names +var $fonts; //array of used fonts +var $FontFiles; //array of font files +var $diffs; //array of encoding differences +var $FontFamily; //current font family +var $FontStyle; //current font style +var $underline; //underlining flag +var $CurrentFont; //current font info +var $FontSizePt; //current font size in points +var $FontSize; //current font size in user unit +var $DrawColor; //commands for drawing color +var $FillColor; //commands for filling color +var $TextColor; //commands for text color +var $ColorFlag; //indicates whether fill and text colors are different +var $ws; //word spacing +var $images; //array of used images +var $PageLinks; //array of links in pages +var $links; //array of internal links +var $AutoPageBreak; //automatic page breaking +var $PageBreakTrigger; //threshold used to trigger page breaks +var $InHeader; //flag set when processing header +var $InFooter; //flag set when processing footer +var $ZoomMode; //zoom display mode +var $LayoutMode; //layout display mode +var $title; //title +var $subject; //subject +var $author; //author +var $keywords; //keywords +var $creator; //creator +var $AliasNbPages; //alias for total number of pages +var $PDFVersion; //PDF version number + +/******************************************************************************* +* * +* Public methods * +* * +*******************************************************************************/ +function FPDF($orientation='P', $unit='mm', $format='A4') +{ + //Some checks + $this->_dochecks(); + //Initialization of properties + $this->page=0; + $this->n=2; + $this->buffer=''; + $this->pages=array(); + $this->PageSizes=array(); + $this->state=0; + $this->fonts=array(); + $this->FontFiles=array(); + $this->diffs=array(); + $this->images=array(); + $this->links=array(); + $this->InHeader=false; + $this->InFooter=false; + $this->lasth=0; + $this->FontFamily=''; + $this->FontStyle=''; + $this->FontSizePt=12; + $this->underline=false; + $this->DrawColor='0 G'; + $this->FillColor='0 g'; + $this->TextColor='0 g'; + $this->ColorFlag=false; + $this->ws=0; + //Standard fonts + $this->CoreFonts=array('courier'=>'Courier', 'courierB'=>'Courier-Bold', 'courierI'=>'Courier-Oblique', 'courierBI'=>'Courier-BoldOblique', + 'helvetica'=>'Helvetica', 'helveticaB'=>'Helvetica-Bold', 'helveticaI'=>'Helvetica-Oblique', 'helveticaBI'=>'Helvetica-BoldOblique', + 'times'=>'Times-Roman', 'timesB'=>'Times-Bold', 'timesI'=>'Times-Italic', 'timesBI'=>'Times-BoldItalic', + 'symbol'=>'Symbol', 'zapfdingbats'=>'ZapfDingbats'); + //Scale factor + if($unit=='pt') + $this->k=1; + elseif($unit=='mm') + $this->k=72/25.4; + elseif($unit=='cm') + $this->k=72/2.54; + elseif($unit=='in') + $this->k=72; + else + $this->Error('Incorrect unit: '.$unit); + + //Page format + $this->PageFormats=array('a3'=>array(841.89,1190.55), 'a4'=>array(595.28,841.89), 'a5'=>array(420.94,595.28), + 'letter'=>array(612,792), 'legal'=>array(612,1008)); + if(is_string($format)) + $format=$this->_getpageformat($format); + $this->DefPageFormat=$format; + $this->CurPageFormat=$format; + //Page orientation + $orientation=strtolower($orientation); + if($orientation=='p' || $orientation=='portrait') + { + $this->DefOrientation='P'; + $this->w=$this->DefPageFormat[0]; + $this->h=$this->DefPageFormat[1]; + } + elseif($orientation=='l' || $orientation=='landscape') + { + $this->DefOrientation='L'; + $this->w=$this->DefPageFormat[1]; + $this->h=$this->DefPageFormat[0]; + } + else + $this->Error('Incorrect orientation: '.$orientation); + $this->CurOrientation=$this->DefOrientation; + $this->wPt=$this->w*$this->k; + $this->hPt=$this->h*$this->k; + //Page margins (1 cm) + $margin=28.35/$this->k; + $this->SetMargins($margin,$margin); + //Interior cell margin (1 mm) + $this->cMargin=$margin/10; + //Line width (0.2 mm) + $this->LineWidth=.567/$this->k; + //Automatic page break + $this->SetAutoPageBreak(true,2*$margin); + //Full width display mode + $this->SetDisplayMode('fullwidth'); + //Enable compression + $this->SetCompression(true); + //Set default PDF version number + $this->PDFVersion='1.3'; +} + +function SetMargins($left, $top, $right=null) +{ + //Set left, top and right margins + $this->lMargin=$left; + $this->tMargin=$top; + if($right===null) + $right=$left; + $this->rMargin=$right; +} + +function SetLeftMargin($margin) +{ + //Set left margin + $this->lMargin=$margin; + if($this->page>0 && $this->x<$margin) + $this->x=$margin; +} + +function SetTopMargin($margin) +{ + //Set top margin + $this->tMargin=$margin; +} + +function SetRightMargin($margin) +{ + //Set right margin + $this->rMargin=$margin; +} + +function SetAutoPageBreak($auto, $margin=0) +{ + //Set auto page break mode and triggering margin + $this->AutoPageBreak=$auto; + $this->bMargin=$margin; + $this->PageBreakTrigger=$this->h-$margin; +} + +function SetDisplayMode($zoom, $layout='continuous') +{ + //Set display mode in viewer + if($zoom=='fullpage' || $zoom=='fullwidth' || $zoom=='real' || $zoom=='default' || !is_string($zoom)) + $this->ZoomMode=$zoom; + else + $this->Error('Incorrect zoom display mode: '.$zoom); + if($layout=='single' || $layout=='continuous' || $layout=='two' || $layout=='default') + $this->LayoutMode=$layout; + else + $this->Error('Incorrect layout display mode: '.$layout); +} + +function SetCompression($compress) +{ + //Set page compression + if(function_exists('gzcompress')) + $this->compress=$compress; + else + $this->compress=false; +} + +function SetTitle($title, $isUTF8=false) +{ + //Title of document + if($isUTF8) + $title=$this->_UTF8toUTF16($title); + $this->title=$title; +} + +function SetSubject($subject, $isUTF8=false) +{ + //Subject of document + if($isUTF8) + $subject=$this->_UTF8toUTF16($subject); + $this->subject=$subject; +} + +function SetAuthor($author, $isUTF8=false) +{ + //Author of document + if($isUTF8) + $author=$this->_UTF8toUTF16($author); + $this->author=$author; +} + +function SetKeywords($keywords, $isUTF8=false) +{ + //Keywords of document + if($isUTF8) + $keywords=$this->_UTF8toUTF16($keywords); + $this->keywords=$keywords; +} + +function SetCreator($creator, $isUTF8=false) +{ + //Creator of document + if($isUTF8) + $creator=$this->_UTF8toUTF16($creator); + $this->creator=$creator; +} + +function AliasNbPages($alias='{nb}') +{ + //Define an alias for total number of pages + $this->AliasNbPages=$alias; +} + +function Error($msg) +{ + //Fatal error + die('FPDF error: '.$msg); +} + +function Open() +{ + //Begin document + $this->state=1; +} + +function Close() +{ + //Terminate document + if($this->state==3) + return; + if($this->page==0) + $this->AddPage(); + //Page footer + $this->InFooter=true; + $this->Footer(); + $this->InFooter=false; + //Close page + $this->_endpage(); + //Close document + $this->_enddoc(); +} + +function AddPage($orientation='', $format='') +{ + //Start a new page + if($this->state==0) + $this->Open(); + $family=$this->FontFamily; + $style=$this->FontStyle.($this->underline ? 'U' : ''); + $size=$this->FontSizePt; + $lw=$this->LineWidth; + $dc=$this->DrawColor; + $fc=$this->FillColor; + $tc=$this->TextColor; + $cf=$this->ColorFlag; + if($this->page>0) + { + //Page footer + $this->InFooter=true; + $this->Footer(); + $this->InFooter=false; + //Close page + $this->_endpage(); + } + //Start new page + $this->_beginpage($orientation,$format); + //Set line cap style to square + $this->_out('2 J'); + //Set line width + $this->LineWidth=$lw; + $this->_out(sprintf('%.2F w',$lw*$this->k)); + //Set font + if($family) + $this->SetFont($family,$style,$size); + //Set colors + $this->DrawColor=$dc; + if($dc!='0 G') + $this->_out($dc); + $this->FillColor=$fc; + if($fc!='0 g') + $this->_out($fc); + $this->TextColor=$tc; + $this->ColorFlag=$cf; + //Page header + $this->InHeader=true; + $this->Header(); + $this->InHeader=false; + //Restore line width + if($this->LineWidth!=$lw) + { + $this->LineWidth=$lw; + $this->_out(sprintf('%.2F w',$lw*$this->k)); + } + //Restore font + if($family) + $this->SetFont($family,$style,$size); + //Restore colors + if($this->DrawColor!=$dc) + { + $this->DrawColor=$dc; + $this->_out($dc); + } + if($this->FillColor!=$fc) + { + $this->FillColor=$fc; + $this->_out($fc); + } + $this->TextColor=$tc; + $this->ColorFlag=$cf; +} + +function Header() +{ + //To be implemented in your own inherited class +} + +function Footer() +{ + //To be implemented in your own inherited class +} + +function PageNo() +{ + //Get current page number + return $this->page; +} + +function SetDrawColor($r, $g=null, $b=null) +{ + //Set color for all stroking operations + if(($r==0 && $g==0 && $b==0) || $g===null) + $this->DrawColor=sprintf('%.3F G',$r/255); + else + $this->DrawColor=sprintf('%.3F %.3F %.3F RG',$r/255,$g/255,$b/255); + if($this->page>0) + $this->_out($this->DrawColor); +} + +function SetFillColor($r, $g=null, $b=null) +{ + //Set color for all filling operations + if(($r==0 && $g==0 && $b==0) || $g===null) + $this->FillColor=sprintf('%.3F g',$r/255); + else + $this->FillColor=sprintf('%.3F %.3F %.3F rg',$r/255,$g/255,$b/255); + $this->ColorFlag=($this->FillColor!=$this->TextColor); + if($this->page>0) + $this->_out($this->FillColor); +} + +function SetTextColor($r, $g=null, $b=null) +{ + //Set color for text + if(($r==0 && $g==0 && $b==0) || $g===null) + $this->TextColor=sprintf('%.3F g',$r/255); + else + $this->TextColor=sprintf('%.3F %.3F %.3F rg',$r/255,$g/255,$b/255); + $this->ColorFlag=($this->FillColor!=$this->TextColor); +} + +function GetStringWidth($s) +{ + //Get width of a string in the current font + $s=(string)$s; + $cw=&$this->CurrentFont['cw']; + $w=0; + if ($this->unifontSubset) { + $unicode = $this->UTF8StringToArray($s); + foreach($unicode as $char) { + if (isset($cw[$char])) { $w+=$cw[$char]; } + else if($char>0 && $char<128 && isset($cw[chr($char)])) { $w+=$cw[chr($char)]; } + else if(isset($this->CurrentFont['desc']['MissingWidth'])) { $w += $this->CurrentFont['desc']['MissingWidth']; } + else if(isset($this->CurrentFont['MissingWidth'])) { $w += $this->CurrentFont['MissingWidth']; } + else { $w += 500; } + } + } + else { + $l=strlen($s); + for($i=0;$i<$l;$i++) + $w+=$cw[$s[$i]]; + } + return $w*$this->FontSize/1000; +} + +function SetLineWidth($width) +{ + //Set line width + $this->LineWidth=$width; + if($this->page>0) + $this->_out(sprintf('%.2F w',$width*$this->k)); +} + +function Line($x1, $y1, $x2, $y2) +{ + //Draw a line + $this->_out(sprintf('%.2F %.2F m %.2F %.2F l S',$x1*$this->k,($this->h-$y1)*$this->k,$x2*$this->k,($this->h-$y2)*$this->k)); +} + +function Rect($x, $y, $w, $h, $style='') +{ + //Draw a rectangle + if($style=='F') + $op='f'; + elseif($style=='FD' || $style=='DF') + $op='B'; + else + $op='S'; + $this->_out(sprintf('%.2F %.2F %.2F %.2F re %s',$x*$this->k,($this->h-$y)*$this->k,$w*$this->k,-$h*$this->k,$op)); +} + + +function AddFont($family, $style='', $file='', $uni=false) +{ + //Add a TrueType or Type1 font + $family=strtolower($family); + if($family=='arial') + $family='helvetica'; + $style=strtoupper($style); + if($style=='IB') + $style='BI'; + if($file=='') { + if ($uni) { + $file=str_replace(' ','',$family).strtolower($style).'.ttf'; + } + else { + $file=str_replace(' ','',$family).strtolower($style).'.php'; + } + } + $fontkey=$family.$style; + if(isset($this->fonts[$fontkey])) + return; + + if ($uni) { + if (defined("_SYSTEM_TTFONTS") && file_exists(_SYSTEM_TTFONTS.$file )) { $ttfilename = _SYSTEM_TTFONTS.$file ; } + else { $ttfilename = $this->_getfontpath().'unifont/'.$file ; } + $filename = $file; + $filename =str_replace(' ','',$filename ); + $filename =str_replace('-','',$filename ); + $unifilename = $this->_getfontpath().'unifont/'.strtolower(substr($filename ,0,(strpos($filename ,'.')))); + $diff = ''; + $enc = ''; + if (file_exists($unifilename.'.mtx.php')) { + include($unifilename.'.mtx.php'); + } + if (!isset($type) || $type != "TrueTypesubset") { + include_once($this->_getfontpath().'unifont/ttfonts.php'); + $ttf = new TTFontFile(); + $ttf->getMetrics($ttfilename); + $cw = $ttf->charWidths; + $type = "TrueTypesubset"; + $name = preg_replace('/ /','',$ttf->fullName); + $desc= array('Ascent'=>round($ttf->ascent), + 'Descent'=>round($ttf->descent), + 'CapHeight'=>round($ttf->capHeight), + 'Flags'=>$ttf->flags, + 'FontBBox'=>'['.round($ttf->bbox[0])." ".round($ttf->bbox[1])." ".round($ttf->bbox[2])." ".round($ttf->bbox[3]).']', + 'ItalicAngle'=>$ttf->italicAngle, + 'StemV'=>round($ttf->stemV), + 'MissingWidth'=>round($ttf->defaultWidth)); + $up = round($ttf->underlinePosition); + $ut = round($ttf->underlineThickness); + //Generate metrics .php file + $s='\n"; + if (is_writable($this->_getfontpath().'unifont')) { + $fh = fopen($unifilename.'.mtx.php',"w"); + fwrite($fh,$s,strlen($s)); + fclose($fh); + } + unset($ttf); + } + if(!isset($name)) { + $this->Error('Problem with the font definition file'); + } + $i = count($this->fonts)+$this->extraFontSubsets+1; + if(!empty($this->AliasNbPages)) + $sbarr = range(0,57); + else + $sbarr = range(0,32); + $this->fonts[$fontkey] = array('i'=>$i, 'type'=>$type, 'name'=>$name, 'desc'=>$desc, 'up'=>$up, 'ut'=>$ut, 'cw'=>$cw, 'enc'=>$enc, 'file'=>$ttfilename, 'subsets'=>array(0=>$sbarr), 'subsetfontids'=>array($i), 'used'=>false); + unset($cw); + } + else { + include($this->_getfontpath().$file); + if(!isset($name)) + $this->Error('Could not include font definition file'); + $i=count($this->fonts)+$this->extraFontSubsets+1; + $this->fonts[$fontkey]=array('i'=>$i, 'type'=>$type, 'name'=>$name, 'desc'=>$desc, 'up'=>$up, 'ut'=>$ut, 'cw'=>$cw, 'enc'=>$enc, 'file'=>$file); + } + + if($diff) + { + //Search existing encodings + $d=0; + $nb=count($this->diffs); + for($i=1;$i<=$nb;$i++) + { + if($this->diffs[$i]==$diff) + { + $d=$i; + break; + } + } + if($d==0) + { + $d=$nb+1; + $this->diffs[$d]=$diff; + } + $this->fonts[$fontkey]['diff']=$d; + } + if($file) + { + if($type=='TrueType') + $this->FontFiles[$file]=array('length1'=>$originalsize); + else if ($uni && $type == "TrueTypesubset") + $this->FontFiles[$file]=array('type'=>"TrueTypesubset"); + else + $this->FontFiles[$file]=array('length1'=>$size1, 'length2'=>$size2); + } +} + +function SetFont($family, $style='', $size=0) +{ + //Select a font; size given in points + global $fpdf_charwidths; + + $family=strtolower($family); + if($family=='') + $family=$this->FontFamily; + if($family=='arial') + $family='helvetica'; + elseif($family=='symbol' || $family=='zapfdingbats') + $style=''; + $style=strtoupper($style); + if(strpos($style,'U')!==false) + { + $this->underline=true; + $style=str_replace('U','',$style); + } + else + $this->underline=false; + if($style=='IB') + $style='BI'; + if($size==0) + $size=$this->FontSizePt; + //Test if font is already selected + if($this->FontFamily==$family && $this->FontStyle==$style && $this->FontSizePt==$size) + return; + //Test if used for the first time + $fontkey=$family.$style; + if(!isset($this->fonts[$fontkey])) + { + //Check if one of the standard fonts + if(isset($this->CoreFonts[$fontkey])) + { + if(!isset($fpdf_charwidths[$fontkey])) + { + //Load metric file + $file=$family; + if($family=='times' || $family=='helvetica') + $file.=strtolower($style); + include($this->_getfontpath().$file.'.php'); + if(!isset($fpdf_charwidths[$fontkey])) + $this->Error('Could not include font metric file'); + } + $i=count($this->fonts)+1+$this->extraFontSubsets; + $name=$this->CoreFonts[$fontkey]; + $cw=$fpdf_charwidths[$fontkey]; + $this->fonts[$fontkey]=array('i'=>$i, 'type'=>'core', 'name'=>$name, 'up'=>-100, 'ut'=>50, 'cw'=>$cw); + } + else + $this->Error('Undefined font: '.$family.' '.$style); + } + //Select it + $this->FontFamily=$family; + $this->FontStyle=$style; + $this->FontSizePt=$size; + $this->FontSize=$size/$this->k; + $this->CurrentFont=&$this->fonts[$fontkey]; + if ($this->fonts[$fontkey]['type']=='TrueTypesubset') { $this->unifontSubset = true; } + else { $this->unifontSubset = false; } + if($this->page>0) + $this->_out(sprintf('BT /F%d %.2F Tf ET',$this->CurrentFont['i'],$this->FontSizePt)); +} + +function SetFontSize($size) +{ + //Set font size in points + if($this->FontSizePt==$size) + return; + $this->FontSizePt=$size; + $this->FontSize=$size/$this->k; + if($this->page>0) + $this->_out(sprintf('BT /F%d %.2F Tf ET',$this->CurrentFont['i'],$this->FontSizePt)); +} + +function AddLink() +{ + //Create a new internal link + $n=count($this->links)+1; + $this->links[$n]=array(0, 0); + return $n; +} + +function SetLink($link, $y=0, $page=-1) +{ + //Set destination of internal link + if($y==-1) + $y=$this->y; + if($page==-1) + $page=$this->page; + $this->links[$link]=array($page, $y); +} + +function Link($x, $y, $w, $h, $link) +{ + //Put a link on the page + $this->PageLinks[$this->page][]=array($x*$this->k, $this->hPt-$y*$this->k, $w*$this->k, $h*$this->k, $link); +} + +function Text($x, $y, $txt) +{ + //Output a string + if ($this->unifontSubset) + $txt2 = $this->UTF8toSubset($txt); + else + $txt2='('.$this->_escape($txt).')'; + $s=sprintf('BT %.2F %.2F Td %s Tj ET',$x*$this->k,($this->h-$y)*$this->k,$txt2); + if($this->underline && $txt!='') + $s.=' '.$this->_dounderline($x,$y,$txt); + if($this->ColorFlag) + $s='q '.$this->TextColor.' '.$s.' Q'; + $this->_out($s); +} + +function AcceptPageBreak() +{ + //Accept automatic page break or not + return $this->AutoPageBreak; +} + +function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='') +{ + //Output a cell + $k=$this->k; + if($this->y+$h>$this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->AcceptPageBreak()) + { + //Automatic page break + $x=$this->x; + $ws=$this->ws; + if($ws>0) + { + $this->ws=0; + $this->_out('0 Tw'); + } + $this->AddPage($this->CurOrientation,$this->CurPageFormat); + $this->x=$x; + if($ws>0) + { + $this->ws=$ws; + $this->_out(sprintf('%.3F Tw',$ws*$k)); + } + } + if($w==0) + $w=$this->w-$this->rMargin-$this->x; + $s=''; + if($fill || $border==1) + { + if($fill) + $op=($border==1) ? 'B' : 'f'; + else + $op='S'; + $s=sprintf('%.2F %.2F %.2F %.2F re %s ',$this->x*$k,($this->h-$this->y)*$k,$w*$k,-$h*$k,$op); + } + if(is_string($border)) + { + $x=$this->x; + $y=$this->y; + if(strpos($border,'L')!==false) + $s.=sprintf('%.2F %.2F m %.2F %.2F l S ',$x*$k,($this->h-$y)*$k,$x*$k,($this->h-($y+$h))*$k); + if(strpos($border,'T')!==false) + $s.=sprintf('%.2F %.2F m %.2F %.2F l S ',$x*$k,($this->h-$y)*$k,($x+$w)*$k,($this->h-$y)*$k); + if(strpos($border,'R')!==false) + $s.=sprintf('%.2F %.2F m %.2F %.2F l S ',($x+$w)*$k,($this->h-$y)*$k,($x+$w)*$k,($this->h-($y+$h))*$k); + if(strpos($border,'B')!==false) + $s.=sprintf('%.2F %.2F m %.2F %.2F l S ',$x*$k,($this->h-($y+$h))*$k,($x+$w)*$k,($this->h-($y+$h))*$k); + } + if($txt!=='') + { + if($align=='R') + $dx=$w-$this->cMargin-$this->GetStringWidth($txt); + elseif($align=='C') + $dx=($w-$this->GetStringWidth($txt))/2; + else + $dx=$this->cMargin; + if($this->ColorFlag) + $s.='q '.$this->TextColor.' '; + if ($this->unifontSubset) + $txt2 = $this->UTF8toSubset($txt); + else + $txt2='('.str_replace(')','\\)',str_replace('(','\\(',str_replace('\\','\\\\',$txt))).')'; + $s.=sprintf('BT %.2F %.2F Td %s Tj ET',($this->x+$dx)*$k,($this->h-($this->y+.5*$h+.3*$this->FontSize))*$k,$txt2); + if($this->underline) + $s.=' '.$this->_dounderline($this->x+$dx,$this->y+.5*$h+.3*$this->FontSize,$txt); + if($this->ColorFlag) + $s.=' Q'; + if($link) + $this->Link($this->x+$dx,$this->y+.5*$h-.5*$this->FontSize,$this->GetStringWidth($txt),$this->FontSize,$link); + } + if($s) + $this->_out($s); + $this->lasth=$h; + if($ln>0) + { + //Go to next line + $this->y+=$h; + if($ln==1) + $this->x=$this->lMargin; + } + else + $this->x+=$w; +} + +function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false) +{ + //Output text with automatic or explicit line breaks + $cw=&$this->CurrentFont['cw']; + if($w==0) + $w=$this->w-$this->rMargin-$this->x; + $wmax=($w-2*$this->cMargin); + $s=str_replace("\r",'',$txt); + if ($this->unifontSubset) { + $nb=mb_strlen($s, 'utf-8'); + while($nb>0 && mb_substr($s,$nb-1,1,'utf-8')=="\n") $nb--; + } + else { + $nb=strlen($s); + if($nb>0 && $s[$nb-1]=="\n") + $nb--; + } + + $b=0; + if($border) + { + if($border==1) + { + $border='LTRB'; + $b='LRT'; + $b2='LR'; + } + else + { + $b2=''; + if(strpos($border,'L')!==false) + $b2.='L'; + if(strpos($border,'R')!==false) + $b2.='R'; + $b=(strpos($border,'T')!==false) ? $b2.'T' : $b2; + } + } + $sep=-1; + $i=0; + $j=0; + $l=0; + $ns=0; + $nl=1; + while($i<$nb) + { + //Get next character + if ($this->unifontSubset) { + $c = mb_substr($s,$i,1,'UTF-8'); + } + else { + $c=$s[$i]; + } + if($c=="\n") { + //Explicit line break + if($this->ws>0) + { + $this->ws=0; + $this->_out('0 Tw'); + } + if ($this->unifontSubset) { + $this->Cell($w,$h,mb_substr($s,$j,$i-$j,'UTF-8'),$b,2,$align,$fill); + } + else { + $this->Cell($w,$h,substr($s,$j,$i-$j),$b,2,$align,$fill); + } + $i++; + $sep=-1; + $j=$i; + $l=0; + $ns=0; + $nl++; + if($border && $nl==2) + $b=$b2; + continue; + } + if($c==' ') + { + $sep=$i; + $ls=$l; + $ns++; + } + + if ($this->unifontSubset) { $l += $this->GetStringWidth($c); } + else { $l += $cw[$c]*$this->FontSize/1000; } + + if($l>$wmax) + { + //Automatic line break + if($sep==-1) + { + if($i==$j) + $i++; + if($this->ws>0) + { + $this->ws=0; + $this->_out('0 Tw'); + } + if ($this->unifontSubset) { + $this->Cell($w,$h,mb_substr($s,$j,$i-$j,'UTF-8'),$b,2,$align,$fill); + } + else { + $this->Cell($w,$h,substr($s,$j,$i-$j),$b,2,$align,$fill); + } + } + else + { + if($align=='J') + { + $this->ws=($ns>1) ? ($wmax-$ls)/($ns-1) : 0; + $this->_out(sprintf('%.3F Tw',$this->ws*$this->k)); + } + if ($this->unifontSubset) { + $this->Cell($w,$h,mb_substr($s,$j,$sep-$j,'UTF-8'),$b,2,$align,$fill); + } + else { + $this->Cell($w,$h,substr($s,$j,$sep-$j),$b,2,$align,$fill); + } + $i=$sep+1; + } + $sep=-1; + $j=$i; + $l=0; + $ns=0; + $nl++; + if($border && $nl==2) + $b=$b2; + } + else + $i++; + } + //Last chunk + if($this->ws>0) + { + $this->ws=0; + $this->_out('0 Tw'); + } + if($border && strpos($border,'B')!==false) + $b.='B'; + if ($this->unifontSubset) { + $this->Cell($w,$h,mb_substr($s,$j,$i-$j,'UTF-8'),$b,2,$align,$fill); + } + else { + $this->Cell($w,$h,substr($s,$j,$i-$j),$b,2,$align,$fill); + } + $this->x=$this->lMargin; +} + +function Write($h, $txt, $link='') +{ + //Output text in flowing mode + $cw=&$this->CurrentFont['cw']; + $w=$this->w-$this->rMargin-$this->x; + + $wmax=($w-2*$this->cMargin); + $s=str_replace("\r",'',$txt); + if ($this->unifontSubset) { + $nb=mb_strlen($s, 'UTF-8'); + if($nb==1 && $s==" ") { + $this->x += $this->GetStringWidth($s); + return; + } + } + else { + $nb=strlen($s); + } + + $sep=-1; + $i=0; + $j=0; + $l=0; + $nl=1; + while($i<$nb) + { + //Get next character + //Get next character + if ($this->unifontSubset) { + $c = mb_substr($s,$i,1,'UTF-8'); + } + else { + $c=$s[$i]; + } + if($c=="\n") { + //Explicit line break + if ($this->unifontSubset) { + $this->Cell($w,$h,mb_substr($s,$j,$i-$j,'UTF-8'),0,2,'',0,$link); + } + else { + $this->Cell($w,$h,substr($s,$j,$i-$j),0,2,'',0,$link); + } + $i++; + $sep=-1; + $j=$i; + $l=0; + if($nl==1) + { + $this->x=$this->lMargin; + $w=$this->w-$this->rMargin-$this->x; + $wmax=($w-2*$this->cMargin); + } + $nl++; + continue; + } + if($c==' ') + $sep=$i; + + if ($this->unifontSubset) { $l += $this->GetStringWidth($c); } + else { $l += $cw[$c]*$this->FontSize/1000; } + + if($l>$wmax) + { + //Automatic line break + if($sep==-1) + { + if($this->x>$this->lMargin) + { + //Move to next line + $this->x=$this->lMargin; + $this->y+=$h; + $w=$this->w-$this->rMargin-$this->x; + $wmax=($w-2*$this->cMargin); + $i++; + $nl++; + continue; + } + if($i==$j) + $i++; + if ($this->unifontSubset) { + $this->Cell($w,$h,mb_substr($s,$j,$i-$j,'UTF-8'),0,2,'',0,$link); + } + else { + $this->Cell($w,$h,substr($s,$j,$i-$j),0,2,'',0,$link); + } + } + else + { + if ($this->unifontSubset) { + $this->Cell($w,$h,mb_substr($s,$j,$sep-$j,'UTF-8'),0,2,'',0,$link); + } + else { + $this->Cell($w,$h,substr($s,$j,$sep-$j),0,2,'',0,$link); + } + $i=$sep+1; + } + $sep=-1; + $j=$i; + $l=0; + if($nl==1) + { + $this->x=$this->lMargin; + $w=$this->w-$this->rMargin-$this->x; + $wmax=($w-2*$this->cMargin); + } + $nl++; + } + else + $i++; + } + //Last chunk + if($i!=$j) { + if ($this->unifontSubset) { + $this->Cell($l,$h,mb_substr($s,$j,$i-$j,'UTF-8'),0,0,'',0,$link); + } + else { + $this->Cell($l,$h,substr($s,$j),0,0,'',0,$link); + } + } +} + +function Ln($h=null) +{ + //Line feed; default value is last cell height + $this->x=$this->lMargin; + if($h===null) + $this->y+=$this->lasth; + else + $this->y+=$h; +} + +function Image($file, $x=null, $y=null, $w=0, $h=0, $type='', $link='') +{ + //Put an image on the page + if(!isset($this->images[$file])) + { + //First use of this image, get info + if($type=='') + { + $pos=strrpos($file,'.'); + if(!$pos) + $this->Error('Image file has no extension and no type was specified: '.$file); + $type=substr($file,$pos+1); + } + $type=strtolower($type); + if($type=='jpeg') + $type='jpg'; + $mtd='_parse'.$type; + if(!method_exists($this,$mtd)) + $this->Error('Unsupported image type: '.$type); + $info=$this->$mtd($file); + $info['i']=count($this->images)+1; + $this->images[$file]=$info; + } + else + $info=$this->images[$file]; + //Automatic width and height calculation if needed + if($w==0 && $h==0) + { + //Put image at 72 dpi + $w=$info['w']/$this->k; + $h=$info['h']/$this->k; + } + elseif($w==0) + $w=$h*$info['w']/$info['h']; + elseif($h==0) + $h=$w*$info['h']/$info['w']; + //Flowing mode + if($y===null) + { + if($this->y+$h>$this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->AcceptPageBreak()) + { + //Automatic page break + $x2=$this->x; + $this->AddPage($this->CurOrientation,$this->CurPageFormat); + $this->x=$x2; + } + $y=$this->y; + $this->y+=$h; + } + if($x===null) + $x=$this->x; + $this->_out(sprintf('q %.2F 0 0 %.2F %.2F %.2F cm /I%d Do Q',$w*$this->k,$h*$this->k,$x*$this->k,($this->h-($y+$h))*$this->k,$info['i'])); + if($link) + $this->Link($x,$y,$w,$h,$link); +} + +function GetX() +{ + //Get x position + return $this->x; +} + +function SetX($x) +{ + //Set x position + if($x>=0) + $this->x=$x; + else + $this->x=$this->w+$x; +} + +function GetY() +{ + //Get y position + return $this->y; +} + +function SetY($y) +{ + //Set y position and reset x + $this->x=$this->lMargin; + if($y>=0) + $this->y=$y; + else + $this->y=$this->h+$y; +} + +function SetXY($x, $y) +{ + //Set x and y positions + $this->SetY($y); + $this->SetX($x); +} + +function Output($name='', $dest='') +{ + //Output PDF to some destination + if($this->state<3) + $this->Close(); + $dest=strtoupper($dest); + if($dest=='') + { + if($name=='') + { + $name='doc.pdf'; + $dest='I'; + } + else + $dest='F'; + } + switch($dest) + { + case 'I': + //Send to standard output + if(ob_get_length()) + $this->Error('Some data has already been output, can\'t send PDF file'); + if(php_sapi_name()!='cli') + { + //We send to a browser + header('Content-Type: application/pdf'); + if(headers_sent()) + $this->Error('Some data has already been output, can\'t send PDF file'); + header('Content-Length: '.strlen($this->buffer)); + header('Content-Disposition: inline; filename="'.$name.'"'); + header('Cache-Control: private, max-age=0, must-revalidate'); + header('Pragma: public'); + ini_set('zlib.output_compression','0'); + } + echo $this->buffer; + break; + case 'D': + //Download file + if(ob_get_length()) + $this->Error('Some data has already been output, can\'t send PDF file'); + header('Content-Type: application/x-download'); + if(headers_sent()) + $this->Error('Some data has already been output, can\'t send PDF file'); + header('Content-Length: '.strlen($this->buffer)); + header('Content-Disposition: attachment; filename="'.$name.'"'); + header('Cache-Control: private, max-age=0, must-revalidate'); + header('Pragma: public'); + ini_set('zlib.output_compression','0'); + echo $this->buffer; + break; + case 'F': + //Save to local file + $f=fopen($name,'wb'); + if(!$f) + $this->Error('Unable to create output file: '.$name); + fwrite($f,$this->buffer,strlen($this->buffer)); + fclose($f); + break; + case 'S': + //Return as a string + return $this->buffer; + default: + $this->Error('Incorrect output destination: '.$dest); + } + return ''; +} + +/******************************************************************************* +* * +* Protected methods * +* * +*******************************************************************************/ +function _dochecks() +{ + //Check availability of %F + if(sprintf('%.1F',1.0)!='1.0') + $this->Error('This version of PHP is not supported'); + //Check availability of mbstring + if(!function_exists('mb_strlen')) + $this->Error('mbstring extension is not available'); + //Check mbstring overloading + if(ini_get('mbstring.func_overload') & 2) + $this->Error('mbstring overloading must be disabled'); + //Disable runtime magic quotes + if(get_magic_quotes_runtime()) + @set_magic_quotes_runtime(0); +} + +function _getpageformat($format) +{ + $format=strtolower($format); + if(!isset($this->PageFormats[$format])) + $this->Error('Unknown page format: '.$format); + $a=$this->PageFormats[$format]; + return array($a[0]/$this->k, $a[1]/$this->k); +} + +function _getfontpath() +{ + if(!defined('FPDF_FONTPATH') && is_dir(dirname(__FILE__).'/font')) + define('FPDF_FONTPATH',dirname(__FILE__).'/font/'); + return defined('FPDF_FONTPATH') ? FPDF_FONTPATH : ''; +} + +function _beginpage($orientation, $format) +{ + $this->page++; + $this->pages[$this->page]=''; + $this->state=2; + $this->x=$this->lMargin; + $this->y=$this->tMargin; + $this->FontFamily=''; + //Check page size + if($orientation=='') + $orientation=$this->DefOrientation; + else + $orientation=strtoupper($orientation[0]); + if($format=='') + $format=$this->DefPageFormat; + else + { + if(is_string($format)) + $format=$this->_getpageformat($format); + } + if($orientation!=$this->CurOrientation || $format[0]!=$this->CurPageFormat[0] || $format[1]!=$this->CurPageFormat[1]) + { + //New size + if($orientation=='P') + { + $this->w=$format[0]; + $this->h=$format[1]; + } + else + { + $this->w=$format[1]; + $this->h=$format[0]; + } + $this->wPt=$this->w*$this->k; + $this->hPt=$this->h*$this->k; + $this->PageBreakTrigger=$this->h-$this->bMargin; + $this->CurOrientation=$orientation; + $this->CurPageFormat=$format; + } + if($orientation!=$this->DefOrientation || $format[0]!=$this->DefPageFormat[0] || $format[1]!=$this->DefPageFormat[1]) + $this->PageSizes[$this->page]=array($this->wPt, $this->hPt); +} + +function _endpage() +{ + $this->state=1; +} + +function _escape($s) +{ + //Escape special characters in strings + $s=str_replace('\\','\\\\',$s); + $s=str_replace('(','\\(',$s); + $s=str_replace(')','\\)',$s); + $s=str_replace("\r",'\\r',$s); + return $s; +} + +function _textstring($s) +{ + //Format a text string + return '('.$this->_escape($s).')'; +} + +function _UTF8toUTF16($s) +{ + //Convert UTF-8 to UTF-16BE with BOM + $res="\xFE\xFF"; + $nb=strlen($s); + $i=0; + while($i<$nb) + { + $c1=ord($s[$i++]); + if($c1>=224) + { + //3-byte character + $c2=ord($s[$i++]); + $c3=ord($s[$i++]); + $res.=chr((($c1 & 0x0F)<<4) + (($c2 & 0x3C)>>2)); + $res.=chr((($c2 & 0x03)<<6) + ($c3 & 0x3F)); + } + elseif($c1>=192) + { + //2-byte character + $c2=ord($s[$i++]); + $res.=chr(($c1 & 0x1C)>>2); + $res.=chr((($c1 & 0x03)<<6) + ($c2 & 0x3F)); + } + else + { + //Single-byte character + $res.="\0".chr($c1); + } + } + return $res; +} + +function _dounderline($x, $y, $txt) +{ + //Underline text + $up=$this->CurrentFont['up']; + $ut=$this->CurrentFont['ut']; + $w=$this->GetStringWidth($txt)+$this->ws*substr_count($txt,' '); + return sprintf('%.2F %.2F %.2F %.2F re f',$x*$this->k,($this->h-($y-$up/1000*$this->FontSize))*$this->k,$w*$this->k,-$ut/1000*$this->FontSizePt); +} + +function _parsejpg($file) +{ + //Extract info from a JPEG file + $a=GetImageSize($file); + if(!$a) + $this->Error('Missing or incorrect image file: '.$file); + if($a[2]!=2) + $this->Error('Not a JPEG file: '.$file); + if(!isset($a['channels']) || $a['channels']==3) + $colspace='DeviceRGB'; + elseif($a['channels']==4) + $colspace='DeviceCMYK'; + else + $colspace='DeviceGray'; + $bpc=isset($a['bits']) ? $a['bits'] : 8; + //Read whole file + $f=fopen($file,'rb'); + $data=''; + while(!feof($f)) + $data.=fread($f,8192); + fclose($f); + return array('w'=>$a[0], 'h'=>$a[1], 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'DCTDecode', 'data'=>$data); +} + +function _parsepng($file) +{ + //Extract info from a PNG file + $f=fopen($file,'rb'); + if(!$f) + $this->Error('Can\'t open image file: '.$file); + //Check signature + if($this->_readstream($f,8)!=chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) + $this->Error('Not a PNG file: '.$file); + //Read header chunk + $this->_readstream($f,4); + if($this->_readstream($f,4)!='IHDR') + $this->Error('Incorrect PNG file: '.$file); + $w=$this->_readint($f); + $h=$this->_readint($f); + $bpc=ord($this->_readstream($f,1)); + if($bpc>8) + $this->Error('16-bit depth not supported: '.$file); + $ct=ord($this->_readstream($f,1)); + if($ct==0) + $colspace='DeviceGray'; + elseif($ct==2) + $colspace='DeviceRGB'; + elseif($ct==3) + $colspace='Indexed'; + else + $this->Error('Alpha channel not supported: '.$file); + if(ord($this->_readstream($f,1))!=0) + $this->Error('Unknown compression method: '.$file); + if(ord($this->_readstream($f,1))!=0) + $this->Error('Unknown filter method: '.$file); + if(ord($this->_readstream($f,1))!=0) + $this->Error('Interlacing not supported: '.$file); + $this->_readstream($f,4); + $parms='/DecodeParms <>'; + //Scan chunks looking for palette, transparency and image data + $pal=''; + $trns=''; + $data=''; + do + { + $n=$this->_readint($f); + $type=$this->_readstream($f,4); + if($type=='PLTE') + { + //Read palette + $pal=$this->_readstream($f,$n); + $this->_readstream($f,4); + } + elseif($type=='tRNS') + { + //Read transparency info + $t=$this->_readstream($f,$n); + if($ct==0) + $trns=array(ord(substr($t,1,1))); + elseif($ct==2) + $trns=array(ord(substr($t,1,1)), ord(substr($t,3,1)), ord(substr($t,5,1))); + else + { + $pos=strpos($t,chr(0)); + if($pos!==false) + $trns=array($pos); + } + $this->_readstream($f,4); + } + elseif($type=='IDAT') + { + //Read image data block + $data.=$this->_readstream($f,$n); + $this->_readstream($f,4); + } + elseif($type=='IEND') + break; + else + $this->_readstream($f,$n+4); + } + while($n); + if($colspace=='Indexed' && empty($pal)) + $this->Error('Missing palette in '.$file); + fclose($f); + return array('w'=>$w, 'h'=>$h, 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'FlateDecode', 'parms'=>$parms, 'pal'=>$pal, 'trns'=>$trns, 'data'=>$data); +} + +function _readstream($f, $n) +{ + //Read n bytes from stream + $res=''; + while($n>0 && !feof($f)) + { + $s=fread($f,$n); + if($s===false) + $this->Error('Error while reading stream'); + $n-=strlen($s); + $res.=$s; + } + if($n>0) + $this->Error('Unexpected end of stream'); + return $res; +} + +function _readint($f) +{ + //Read a 4-byte integer from stream + $a=unpack('Ni',$this->_readstream($f,4)); + return $a['i']; +} + +function _parsegif($file) +{ + //Extract info from a GIF file (via PNG conversion) + if(!function_exists('imagepng')) + $this->Error('GD extension is required for GIF support'); + if(!function_exists('imagecreatefromgif')) + $this->Error('GD has no GIF read support'); + $im=imagecreatefromgif($file); + if(!$im) + $this->Error('Missing or incorrect image file: '.$file); + imageinterlace($im,0); + $tmp=tempnam('.','gif'); + if(!$tmp) + $this->Error('Unable to create a temporary file'); + if(!imagepng($im,$tmp)) + $this->Error('Error while saving to temporary file'); + imagedestroy($im); + $info=$this->_parsepng($tmp); + unlink($tmp); + return $info; +} + +function _newobj() +{ + //Begin a new object + $this->n++; + $this->offsets[$this->n]=strlen($this->buffer); + $this->_out($this->n.' 0 obj'); +} + +function _putstream($s) +{ + $this->_out('stream'); + $this->_out($s); + $this->_out('endstream'); +} + +function _out($s) +{ + //Add a line to the document + if($this->state==2) + $this->pages[$this->page].=$s."\n"; + else + $this->buffer.=$s."\n"; +} + +function _putpages() +{ + $nb=$this->page; + if(!empty($this->AliasNbPages)) + { + //Replace number of pages in fonts using subsets + $r = ''; + $nstr = "$nb"; + for($i=0;$ipages[$n]=str_replace('`'.$this->AliasNbPages.'`',$r,$this->pages[$n]); + // Now repeat for no pages in non-subset fonts + $r = $nb; + //Replace number of pages + for($n=1;$n<=$nb;$n++) + $this->pages[$n]=str_replace($this->AliasNbPages,$r,$this->pages[$n]); + } + if($this->DefOrientation=='P') + { + $wPt=$this->DefPageFormat[0]*$this->k; + $hPt=$this->DefPageFormat[1]*$this->k; + } + else + { + $wPt=$this->DefPageFormat[1]*$this->k; + $hPt=$this->DefPageFormat[0]*$this->k; + } + $filter=($this->compress) ? '/Filter /FlateDecode ' : ''; + for($n=1;$n<=$nb;$n++) + { + //Page + $this->_newobj(); + $this->_out('<_out('/Parent 1 0 R'); + if(isset($this->PageSizes[$n])) + $this->_out(sprintf('/MediaBox [0 0 %.2F %.2F]',$this->PageSizes[$n][0],$this->PageSizes[$n][1])); + $this->_out('/Resources 2 0 R'); + if(isset($this->PageLinks[$n])) + { + //Links + $annots='/Annots ['; + foreach($this->PageLinks[$n] as $pl) + { + $rect=sprintf('%.2F %.2F %.2F %.2F',$pl[0],$pl[1],$pl[0]+$pl[2],$pl[1]-$pl[3]); + $annots.='<_textstring($pl[4]).'>>>>'; + else + { + $l=$this->links[$pl[4]]; + $h=isset($this->PageSizes[$l[0]]) ? $this->PageSizes[$l[0]][1] : $hPt; + $annots.=sprintf('/Dest [%d 0 R /XYZ 0 %.2F null]>>',1+2*$l[0],$h-$l[1]*$this->k); + } + } + $this->_out($annots.']'); + } + $this->_out('/Contents '.($this->n+1).' 0 R>>'); + $this->_out('endobj'); + //Page content + $p=($this->compress) ? gzcompress($this->pages[$n]) : $this->pages[$n]; + $this->_newobj(); + $this->_out('<<'.$filter.'/Length '.strlen($p).'>>'); + $this->_putstream($p); + $this->_out('endobj'); + } + //Pages root + $this->offsets[1]=strlen($this->buffer); + $this->_out('1 0 obj'); + $this->_out('<_out($kids.']'); + $this->_out('/Count '.$nb); + $this->_out(sprintf('/MediaBox [0 0 %.2F %.2F]',$wPt,$hPt)); + $this->_out('>>'); + $this->_out('endobj'); +} + +function _putfonts() +{ + $nf=$this->n; + foreach($this->diffs as $diff) + { + //Encodings + $this->_newobj(); + $this->_out('<>'); + $this->_out('endobj'); + } + foreach($this->FontFiles as $file=>$info) + { + if (!isset($info['type']) || $info['type']!='TrueTypesubset') { + //Font file embedding + $this->_newobj(); + $this->FontFiles[$file]['n']=$this->n; + $font=''; + $f=fopen($this->_getfontpath().$file,'rb',1); + if(!$f) + $this->Error('Font file not found'); + while(!feof($f)) + $font.=fread($f,8192); + fclose($f); + $compressed=(substr($file,-2)=='.z'); + if(!$compressed && isset($info['length2'])) + { + $header=(ord($font[0])==128); + if($header) + { + //Strip first binary header + $font=substr($font,6); + } + if($header && ord($font[$info['length1']])==128) + { + //Strip second binary header + $font=substr($font,0,$info['length1']).substr($font,$info['length1']+6); + } + } + $this->_out('<_out('/Filter /FlateDecode'); + $this->_out('/Length1 '.$info['length1']); + if(isset($info['length2'])) + $this->_out('/Length2 '.$info['length2'].' /Length3 0'); + $this->_out('>>'); + $this->_putstream($font); + $this->_out('endobj'); + } + } + foreach($this->fonts as $k=>$font) + { + //Font objects + //$this->fonts[$k]['n']=$this->n+1; + $type=$font['type']; + $name=$font['name']; + if($type=='core') + { + //Standard font + $this->fonts[$k]['n']=$this->n+1; + $this->_newobj(); + $this->_out('<_out('/BaseFont /'.$name); + $this->_out('/Subtype /Type1'); + if($name!='Symbol' && $name!='ZapfDingbats') + $this->_out('/Encoding /WinAnsiEncoding'); + $this->_out('>>'); + $this->_out('endobj'); + } + elseif($type=='Type1' || $type=='TrueType') + { + //Additional Type1 or TrueType font + $this->fonts[$k]['n']=$this->n+1; + $this->_newobj(); + $this->_out('<_out('/BaseFont /'.$name); + $this->_out('/Subtype /'.$type); + $this->_out('/FirstChar 32 /LastChar 255'); + $this->_out('/Widths '.($this->n+1).' 0 R'); + $this->_out('/FontDescriptor '.($this->n+2).' 0 R'); + if($font['enc']) + { + if(isset($font['diff'])) + $this->_out('/Encoding '.($nf+$font['diff']).' 0 R'); + else + $this->_out('/Encoding /WinAnsiEncoding'); + } + $this->_out('>>'); + $this->_out('endobj'); + //Widths + $this->_newobj(); + $cw=&$font['cw']; + $s='['; + for($i=32;$i<=255;$i++) + $s.=$cw[chr($i)].' '; + $this->_out($s.']'); + $this->_out('endobj'); + //Descriptor + $this->_newobj(); + $s='<$v) + $s.=' /'.$k.' '.$v; + $file=$font['file']; + if($file) + $s.=' /FontFile'.($type=='Type1' ? '' : '2').' '.$this->FontFiles[$file]['n'].' 0 R'; + $this->_out($s.'>>'); + $this->_out('endobj'); + } + // TrueType embedded SUBSETS + else if ($type=='TrueTypesubset') { + $ssfaid="A"; + include_once($this->_getfontpath().'unifont/ttfonts.php'); + $ttf = new TTFontFile(); + $ttf->getMetrics($font['file'], 1); + for($sfid=0;$sfidfonts[$k]['n'][$sfid]=$this->n+1; // NB an array for subset + $subsetname = 'MPDFA'.$ssfaid.'+'.$font['name']; + $ssfaid++; + $subset = $font['subsets'][$sfid]; + unset($subset[0]); + $ttfontstream = $ttf->makeSubset($subset); + $ttfontsize = strlen($ttfontstream); + $fontstream = gzcompress($ttfontstream); + $widthstring = ''; + $toUnistring = ''; + foreach($font['subsets'][$sfid] AS $cp=>$u) { + if (isset($font['cw'][$u])) { + $widthstring .= $font['cw'][$u].' '; + } + else { + $widthstring .= $ttf->defaultWidth.' '; + } + $toUnistring .= sprintf("<%02s> <%04s>\n", strtoupper(dechex($cp)), strtoupper(dechex($u))); + } + + //Additional Type1 or TrueType font + $this->_newobj(); + $this->_out('<_out('/BaseFont /'.$subsetname); + $this->_out('/Subtype /TrueType'); + $this->_out('/FirstChar 0 /LastChar '.(count($font['subsets'][$sfid]))); + $this->_out('/Widths '.($this->n+1).' 0 R'); + $this->_out('/FontDescriptor '.($this->n+2).' 0 R'); + $this->_out('/ToUnicode '.($this->n + 3).' 0 R'); + $this->_out('>>'); + $this->_out('endobj'); + + //Widths + $this->_newobj(); + $this->_out('['.$widthstring.']'); + $this->_out('endobj'); + + //Descriptor + $this->_newobj(); + $s='<$v) { + if ($kd == 'Flags') { $v = $v | 4; $v = $v & ~32; } + $s.=' /'.$kd.' '.$v."\n"; + } + + $s.='/FontFile2 '.($this->n + 2).' 0 R'; + $this->_out($s.'>>'); + $this->_out('endobj'); + + // ToUnicode + $toUni = "/CIDInit /ProcSet findresource begin\n"; + $toUni .= "12 dict begin\n"; + $toUni .= "begincmap\n"; + $toUni .= "/CIDSystemInfo\n"; + $toUni .= "<_newobj(); + $this->_out('<>'); + $this->_putstream($toUni); + $this->_out('endobj'); + + //Font file + $this->_newobj(); + $this->_out('<_out('/Filter /FlateDecode'); + $this->_out('/Length1 '.$ttfontsize); + $this->_out('>>'); + $this->_putstream($fontstream); + $this->_out('endobj'); + } + unset($ttf); + } + else + { + //Allow for additional types + $this->fonts[$k]['n']=$this->n+1; + $mtd='_put'.strtolower($type); + if(!method_exists($this,$mtd)) + $this->Error('Unsupported font type: '.$type); + $this->$mtd($font); + } + } +} + +function _putimages() +{ + $filter=($this->compress) ? '/Filter /FlateDecode ' : ''; + reset($this->images); + while(list($file,$info)=each($this->images)) + { + $this->_newobj(); + $this->images[$file]['n']=$this->n; + $this->_out('<_out('/Subtype /Image'); + $this->_out('/Width '.$info['w']); + $this->_out('/Height '.$info['h']); + if($info['cs']=='Indexed') + $this->_out('/ColorSpace [/Indexed /DeviceRGB '.(strlen($info['pal'])/3-1).' '.($this->n+1).' 0 R]'); + else + { + $this->_out('/ColorSpace /'.$info['cs']); + if($info['cs']=='DeviceCMYK') + $this->_out('/Decode [1 0 1 0 1 0 1 0]'); + } + $this->_out('/BitsPerComponent '.$info['bpc']); + if(isset($info['f'])) + $this->_out('/Filter /'.$info['f']); + if(isset($info['parms'])) + $this->_out($info['parms']); + if(isset($info['trns']) && is_array($info['trns'])) + { + $trns=''; + for($i=0;$i_out('/Mask ['.$trns.']'); + } + $this->_out('/Length '.strlen($info['data']).'>>'); + $this->_putstream($info['data']); + unset($this->images[$file]['data']); + $this->_out('endobj'); + //Palette + if($info['cs']=='Indexed') + { + $this->_newobj(); + $pal=($this->compress) ? gzcompress($info['pal']) : $info['pal']; + $this->_out('<<'.$filter.'/Length '.strlen($pal).'>>'); + $this->_putstream($pal); + $this->_out('endobj'); + } + } +} + +function _putxobjectdict() +{ + foreach($this->images as $image) + $this->_out('/I'.$image['i'].' '.$image['n'].' 0 R'); +} + +function _putresourcedict() +{ + $this->_out('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); + $this->_out('/Font <<'); + foreach($this->fonts as $font) { + if ($font['type']=='TrueTypesubset') { + foreach($font['n'] AS $k => $fid) { + $this->_out('/F'.$font['subsetfontids'][$k].' '.$font['n'][$k].' 0 R'); + } + } + else { + $this->_out('/F'.$font['i'].' '.$font['n'].' 0 R'); + } + } + $this->_out('>>'); + $this->_out('/XObject <<'); + $this->_putxobjectdict(); + $this->_out('>>'); +} + +function _putresources() +{ + $this->_putfonts(); + $this->_putimages(); + //Resource dictionary + $this->offsets[2]=strlen($this->buffer); + $this->_out('2 0 obj'); + $this->_out('<<'); + $this->_putresourcedict(); + $this->_out('>>'); + $this->_out('endobj'); +} + +function _putinfo() +{ + $this->_out('/Producer '.$this->_textstring('FPDF '.FPDF_VERSION)); + if(!empty($this->title)) + $this->_out('/Title '.$this->_textstring($this->title)); + if(!empty($this->subject)) + $this->_out('/Subject '.$this->_textstring($this->subject)); + if(!empty($this->author)) + $this->_out('/Author '.$this->_textstring($this->author)); + if(!empty($this->keywords)) + $this->_out('/Keywords '.$this->_textstring($this->keywords)); + if(!empty($this->creator)) + $this->_out('/Creator '.$this->_textstring($this->creator)); + $this->_out('/CreationDate '.$this->_textstring('D:'.@date('YmdHis'))); +} + +function _putcatalog() +{ + $this->_out('/Type /Catalog'); + $this->_out('/Pages 1 0 R'); + if($this->ZoomMode=='fullpage') + $this->_out('/OpenAction [3 0 R /Fit]'); + elseif($this->ZoomMode=='fullwidth') + $this->_out('/OpenAction [3 0 R /FitH null]'); + elseif($this->ZoomMode=='real') + $this->_out('/OpenAction [3 0 R /XYZ null null 1]'); + elseif(!is_string($this->ZoomMode)) + $this->_out('/OpenAction [3 0 R /XYZ null null '.($this->ZoomMode/100).']'); + if($this->LayoutMode=='single') + $this->_out('/PageLayout /SinglePage'); + elseif($this->LayoutMode=='continuous') + $this->_out('/PageLayout /OneColumn'); + elseif($this->LayoutMode=='two') + $this->_out('/PageLayout /TwoColumnLeft'); +} + +function _putheader() +{ + $this->_out('%PDF-'.$this->PDFVersion); +} + +function _puttrailer() +{ + $this->_out('/Size '.($this->n+1)); + $this->_out('/Root '.$this->n.' 0 R'); + $this->_out('/Info '.($this->n-1).' 0 R'); +} + +function _enddoc() +{ + $this->_putheader(); + $this->_putpages(); + $this->_putresources(); + //Info + $this->_newobj(); + $this->_out('<<'); + $this->_putinfo(); + $this->_out('>>'); + $this->_out('endobj'); + //Catalog + $this->_newobj(); + $this->_out('<<'); + $this->_putcatalog(); + $this->_out('>>'); + $this->_out('endobj'); + //Cross-ref + $o=strlen($this->buffer); + $this->_out('xref'); + $this->_out('0 '.($this->n+1)); + $this->_out('0000000000 65535 f '); + for($i=1;$i<=$this->n;$i++) + $this->_out(sprintf('%010d 00000 n ',$this->offsets[$i])); + //Trailer + $this->_out('trailer'); + $this->_out('<<'); + $this->_puttrailer(); + $this->_out('>>'); + $this->_out('startxref'); + $this->_out($o); + $this->_out('%%EOF'); + $this->state=3; +} + +// ********* NEW FUNCTIONS ********* + +// Convert utf-8 string to for Font Subsets +function UTF8toSubset($str) { + $ret = '<'; + if ($this->AliasNbPages) + $str = preg_replace('/'.preg_quote($this->AliasNbPages,'/').'/', chr(7), $str ); + $unicode = $this->UTF8StringToArray($str); + $orig_fid = $this->CurrentFont['subsetfontids'][0]; + $last_fid = $this->CurrentFont['subsetfontids'][0]; + foreach($unicode as $c) { + if ($c == 7) { + if ($orig_fid != $last_fid) { + $ret .= '> Tj /F'.$orig_fid.' '.$this->FontSizePt.' Tf <'; + $last_fid = $orig_fid; + } + $ret .= '`'.$this->AliasNbPages.'`'; + continue; + } + for ($i=0; $i<99; $i++) { + // return c as decimal char + $init = array_search($c, $this->CurrentFont['subsets'][$i]); + if ($init!==false) { + if ($this->CurrentFont['subsetfontids'][$i] != $last_fid) { + $ret .= '> Tj /F'.$this->CurrentFont['subsetfontids'][$i].' '.$this->FontSizePt.' Tf <'; + $last_fid = $this->CurrentFont['subsetfontids'][$i]; + } + $ret .= sprintf("%02s", strtoupper(dechex($init))); + break; + } + else if (count($this->CurrentFont['subsets'][$i]) < 255) { + $n = count($this->CurrentFont['subsets'][$i]); + $this->CurrentFont['subsets'][$i][$n] = $c; + if ($this->CurrentFont['subsetfontids'][$i] != $last_fid) { + $ret .= '> Tj /F'.$this->CurrentFont['subsetfontids'][$i].' '.$this->FontSizePt.' Tf <'; + $last_fid = $this->CurrentFont['subsetfontids'][$i]; + } + $ret .= sprintf("%02s", strtoupper(dechex($n))); + break; + } + else if (!isset($this->CurrentFont['subsets'][($i+1)])) { + $this->CurrentFont['subsets'][($i+1)] = array(0=>0); + $new_fid = count($this->fonts)+$this->extraFontSubsets+1; + $this->CurrentFont['subsetfontids'][($i+1)] = $new_fid; + $this->extraFontSubsets++; + } + } + } + $ret .= '>'; + if ($last_fid != $orig_fid) { + $ret .= ' Tj /F'.$orig_fid.' '.$this->FontSizePt.' Tf <> '; + } + return $ret; +} + +// Converts UTF-8 strings to codepoints array +function UTF8StringToArray($str) { + $out = array(); + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + $h = ord($str[$i]); + if ( $h <= 0x7F ) + $out[] = $h; + elseif ( $h >= 0xC2 ) { + if ( ($h <= 0xDF) && ($i < $len -1) ) + $out[] = ($h & 0x1F) << 6 | (ord($str{++$i}) & 0x3F); + elseif ( ($h <= 0xEF) && ($i < $len -2) ) + $out[] = ($h & 0x0F) << 12 | (ord($str{++$i}) & 0x3F) << 6 + | (ord($str{++$i}) & 0x3F); + elseif ( ($h <= 0xF4) && ($i < $len -3) ) + $out[] = ($h & 0x0F) << 18 | (ord($str{++$i}) & 0x3F) << 12 + | (ord($str{++$i}) & 0x3F) << 6 + | (ord($str{++$i}) & 0x3F); + } + } + return $out; +} + + +//End of class +} + +//Handle special IE contype request +if(isset($_SERVER['HTTP_USER_AGENT']) && $_SERVER['HTTP_USER_AGENT']=='contype') +{ + header('Content-Type: application/pdf'); + exit; +} + +?> \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/imgs/Voir_abs.png b/misc/PublicationBulletins/Portail-LeHavre/imgs/Voir_abs.png new file mode 100755 index 00000000..36de4af7 Binary files /dev/null and b/misc/PublicationBulletins/Portail-LeHavre/imgs/Voir_abs.png differ diff --git a/misc/PublicationBulletins/Portail-LeHavre/imgs/livre.png b/misc/PublicationBulletins/Portail-LeHavre/imgs/livre.png new file mode 100755 index 00000000..3eef403f Binary files /dev/null and b/misc/PublicationBulletins/Portail-LeHavre/imgs/livre.png differ diff --git a/misc/PublicationBulletins/Portail-LeHavre/imgs/minus_img.png b/misc/PublicationBulletins/Portail-LeHavre/imgs/minus_img.png new file mode 100755 index 00000000..9648a218 Binary files /dev/null and b/misc/PublicationBulletins/Portail-LeHavre/imgs/minus_img.png differ diff --git a/misc/PublicationBulletins/Portail-LeHavre/imgs/modifier_texte.png b/misc/PublicationBulletins/Portail-LeHavre/imgs/modifier_texte.png new file mode 100755 index 00000000..894a7eb1 Binary files /dev/null and b/misc/PublicationBulletins/Portail-LeHavre/imgs/modifier_texte.png differ diff --git a/misc/PublicationBulletins/Portail-LeHavre/imgs/plus_img.png b/misc/PublicationBulletins/Portail-LeHavre/imgs/plus_img.png new file mode 100755 index 00000000..a9ee6407 Binary files /dev/null and b/misc/PublicationBulletins/Portail-LeHavre/imgs/plus_img.png differ diff --git a/misc/PublicationBulletins/Portail-LeHavre/index.php b/misc/PublicationBulletins/Portail-LeHavre/index.php new file mode 100755 index 00000000..bf432ff9 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/index.php @@ -0,0 +1,885 @@ + $valeur ) { + $headers_brut .= $nom . ': ' . $valeur . "\r\n"; + } + + return $headers_brut; +} + + +function get_EtudAbs_page($nip, $dept,$beg_date) +{ + global $user_agent; + global $sco_user; + global $sco_pw; + global $sco_url; + $end_date=date("Y-m-d"); + $donnees = array( + 'format' => 'xml', + 'code_nip' => $nip, + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw, + 'beg_date' => $beg_date, + 'end_date' => $end_date); + + // Cration du contenu brut de la requte + $contenu = http_build_query( $donnees ); + + // Dfinition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Dfinition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Cration du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Absences/XMLgetAbsEtud', false, $contexte ); + + return ($retour); +} + + +function get_BilletAbs_list($nip, $dept) +{ + global $user_agent; + global $sco_user; + global $sco_pw; + global $sco_url; + $donnees = array( + 'format' => 'xml', + 'code_nip' => $nip, + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw, +); + + // Cration du contenu brut de la requte + $contenu = http_build_query( $donnees ); + + // Dfinition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Dfinition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Cration du contexte + $contexte = stream_context_create($options); +//echo $sco_url . $dept . '/Scolarite/Absences/XMLgetBilletsEtud'. + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Absences/XMLgetBilletsEtud', false, $contexte ); + + return ($retour); +} + + +function Get_EtudAbs_billet($nip, $dept,$begin,$end,$description) +{ + global $user_agent; + global $sco_user; + global $sco_pw; + global $sco_url; + $end_date=date("Y-m-d"); +$justified="0"; + $donnees = array( + 'format' => 'xml', + 'code_nip' => $nip, + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw, + 'description' =>$description, + 'justified' =>$justified, + 'begin' => $begin, + 'end' => $end); + // Cration du contenu brut de la requte + $contenu = http_build_query( $donnees ); + + // Dfinition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Dfinition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Cration du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Absences/AddBilletAbsence', false, $contexte ); + + return ($retour); +} + + +function get_EtudInfos_page($nip, $dept) +{ + global $user_agent; + global $sco_user; + global $sco_pw; + global $sco_url; + + $donnees = array( + 'code_nip' => $nip, + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw ); + + // Cration du contenu brut de la requte + $contenu = http_build_query( $donnees ); + + // Dfinition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Dfinition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Cration du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Notes/XMLgetEtudInfos', false, $contexte ); + + return ($retour); +} + +function get_bulletinetud_page($nip, $sem, $dept) { + global $user_agent; + global $sco_user; + global $sco_pw; + global $sco_url; + $donnees = array( + 'format' => 'xml', + 'code_nip' => $nip, + 'formsemestre_id' => $sem, + 'version' => 'selectedevals', + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw ); + + // Cration du contenu brut de la requte + $contenu = http_build_query( $donnees ); + + // Dfinition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Dfinition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Cration du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Notes/formsemestre_bulletinetud', false, $contexte ); + + return ($retour); +} + +function get_semestre_info($sem, $dept) +{ + global $user_agent; + global $sco_user; + global $sco_pw; + global $sco_url; + $donnees = array( + 'formsemestre_id' => $sem, + '__ac_name' => $sco_user, + '__ac_password' => $sco_pw ); + + // Cration du contenu brut de la requte + $contenu = http_build_query( $donnees ); + + // Dfinition des headers + $headers = http_build_headers( array( + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Length' => strlen( $contenu) ) ); + + // Dfinition du contexte + $options = array( 'http' => array( 'user_agent' => $user_agent, + 'method' => 'POST', + 'content' => $contenu, + 'header' => $headers ) ); + + // Cration du contexte + $contexte = stream_context_create($options); + + // Envoi du formulaire POST + $retour = file_get_contents( $sco_url . $dept . '/Scolarite/Notes/XMLgetFormsemestres', false, $contexte ); + + return ($retour); +} + +function get_all_semestres($xml_data) +{ + $data = array(); + $xml = simplexml_load_string($xml_data); + foreach ($xml->insemestre as $s) { + $sem = (array) $s['formsemestre_id']; + $data[] = $sem[0]; + } + return $data; +} + +function get_current_semestre($xml_data) +{ + $xml = simplexml_load_string($xml_data); + foreach ($xml->insemestre as $s) { + if ($s['current'] == 1){ + $sem = (array) $s['formsemestre_id']; + return ($sem[0]);} + else{$sem = ""; + return ($sem);} + } + +} + +function print_semestres_list($sems, $dept, $sem) +{ + echo 'Semestre : + +Dconnexion +'; +} + +function print_semestre($xml_data, $sem, $dept, $show_moy=False) +{ + global $etudid; + global $nip; + global $sco_user; + global $sco_pw; + global $sco_url; + $modules=array(); + $codesmodules=array(); + $i=0; + if($sem==""){echo '

      Il n'y a pas de semestre courant

      ';} else{ + $xml = simplexml_load_string($xml_data); + $etudid= $xml->etudiant['etudid']; + + $retour = get_semestre_info($sem, $dept); + $xml2 = simplexml_load_string($retour); + $debut=date("Y-m-d",strtotime($xml2->formsemestre['dateord'])); + $finsemestre=$xml2->formsemestre['date_fin_iso']; + $fin=strtotime($finsemestre)+3000000; + $day=strtotime(date("d-m-Y")); + $publie= $xml2->formsemestre['bul_hide_xml']; + + // teste la publication et affiche un message si non publi + // $showmoy teste si on est avant date de fin du semestre + // si la date du jour dpasse de 45 jours la date de fin de semestre on affiche les moyennes + // sinon pas encore + + $sexe=$xml->etudiant['sexe']; + $prenom=$xml->etudiant['prenom']; + $nom=$xml->etudiant['nom']; + $semestre=$xml2->formsemestre['titre_num']; + + if ($publie=="0") { + if (!$show_moy) { + echo '

      Les informations contenues dans ces tableaux sont + provisoires. L'état n'a pas valeur de bulletin de notes.';} + + echo '
      Il vous appartient de contacter vos enseignants + ou votre dpartement en cas de dsaccord.

      '; + + echo '

      ' . convertir_utf8($sexe). ' ' . convertir_utf8($prenom). ' ' . convertir_utf8($nom). '

      '; + //echo '
      '; + + echo ''.convertir_utf8($semestre).'
      '; + if (!$show_moy) { echo "vous avez ce jour ".convertir_utf8($xml->absences['nbabs'])." demi-journes d'absences dont ".convertir_utf8($xml->absences['nbabsjust']).' justifies';} + + echo ' +'; + echo '
      +

      - Cliquez ici pour afficher/masquer le bulletin de notes

      + +


      +

      Gestion des absences

      +
      Les rgles de gestion peuvent actuellement dpendre des dpartements. La dclaration en ligne ne suffit pas.
      + + + +

      - Cliquez ici pour afficher/masquer la liste des absences du semestre

      '; + + $retourabs = get_EtudAbs_page($nip, $dept,$debut); + $xmlabs = simplexml_load_string($retourabs); + + + + echo ' + '; + $retourbillets = get_BilletAbs_list($nip, $dept); +if($retourbillets!=''){ +echo '

      - Dclaration d'un billet d'absences

      + '; + + + + +if (isset($_POST['submit']) && $_POST['submit'] == "Envoyer"){ +$description=$_POST["description"]; + +$date1 = new DateTime($_POST["begin"]); +$date1->setTime(intval(substr($_POST["begtime"],0,2)), intval(substr($_POST["begtime"],-2)))+1; + +$date2 = new DateTime($_POST["end"]); +$date2->setTime(intval(substr($_POST["endtime"],0,2)), intval(substr($_POST["endtime"],-2))-31); + +if(!$description){$description="Motif: ".$description." - Matires: " ;} + for($i=1;$i'; + + Get_EtudAbs_billet($nip, $dept,$date1 , $date2 , $description); + } + +// pour tester le renvoi des variables + //print_r($_POST); + +echo ' + +
      - Cliquez ici pour afficher/masquer les billets d'absences déposés
      + +'; +} + +}} +else +{echo '

      Votre département n'a pas autorisé l'affichage des informations de ce semestre

      ';} + +}} + + + +function get_dept($nip) +{ + global $sco_url; + $dept = file_get_contents( $sco_url . 'get_etud_dept?code_nip=' . $nip); + return ($dept); +} + + +// function pour la recuperation des infos ldap +function search_results($info) { + foreach ($info as $inf) { + if (is_array($inf)) { + foreach ($inf as $key => $in) { + if ((count($inf[$key]) - 1) > 0) { + if (is_array($in)) { + unset($inf[$key]["count"]); + } + $results[$key] = $inf[$key]; + } + } + } + } + $results["dn"] = explode(',', $info[0]["dn"]); + return $results; +} + + + +// Programme principal +// $nip="20121713"; + +echo ' + +Portail Webnotes + + + + + + + + + + '; + +echo " + + + + +"; + +//$user = $_SERVER['PHP_AUTH_USER']; +//echo 'USER: '.$user."\n"."
      "; + +//$user = "ei121713"; +//echo "On triche USER = ".$user."\n"."
      "; +/* +$ds = ldap_connect("ldap://ldap"); +if ($ds) { + $r = ldap_bind($ds); + $sr = ldap_search($ds, "ou=people,dc=univ-lehavre,dc=fr", "(&(objectClass=ulhEtudiant)(uid=$user))"); + $info = ldap_get_entries($ds, $sr); + + //echo $info["count"]." IS Search Result(s) for \"".$user."\"\n"; + $results = search_results($info); + // si pas de reponse de l a nnuaire, ce n est pas un etudiant + if ($info["count"] == 0 ) { + echo ' + + getEtud + + +

      Service de consultation des notes

      +
      + Il faut être etudiant de l'IUT pour accéder à ses notes. +
      + + '; + } else { + foreach ($results as $key => $result) { + if ($key == 'supannetuid' ) { + //echo " * ".$key." : \n"; + if (is_array($result)){ + foreach($result as $res){ + //echo " ".$res."\n"; + } + } + //echo "
      "; + $nip=$res; + } + } + } + ldap_close($ds); +}*/ +// Login information of a scodoc user that can access notes +$sco_user = 'lecturenotes'; +$sco_pw = 'lecture2014'; +//$sco_url = 'https://test1-scodoc.iut.univ-lehavre.fr/ScoDoc/'; +$nip="20121713"; +$sco_url = 'https://scodoc-demo.iut.univ-lehavre.fr/ScoDoc/'; +//$sco_url = 'https://scodoc.univ-lehavre.fr/ScoDoc/'; +$user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1) Gecko/20061010 Firefox/2.0'; + +echo ''; +if ($nip) { +$dept = get_dept($nip); +if ($dept) { + $retour = get_EtudInfos_page($nip, $dept); + $sems = get_all_semestres($retour); + $sem_current = get_current_semestre($retour); + if (isset($_POST["sem"])) { + $sem = $_POST["sem"]; + } + else { + $sem = $sem_current; + } + + print_semestres_list($sems, $dept, $sem); + if($sem==""){echo '

      Il n'y a pas de semestre en cours - Choisissez ventuellement dans la liste.

      ';} else{ + $retour = get_bulletinetud_page($nip, $sem, $dept); + if ($sem == $sem_current) { + print_semestre($retour, $sem, $dept, False); + } + else { + print_semestre($retour, $sem, $dept, True); + } + $erreur=0; // Tout est OK +}} +else { + echo "Numro tudiant inconnu : " . $nip . ". Contactez votre Chef de dpartement."; + +}} + +echo ''; + + +echo ' + +'; + + +?> \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/jQuery/ColVis.js b/misc/PublicationBulletins/Portail-LeHavre/jQuery/ColVis.js new file mode 100755 index 00000000..593ad472 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/jQuery/ColVis.js @@ -0,0 +1,1133 @@ +/* + * File: ColVis.js + * Version: 1.1.0-dev + * CVS: $Id$ + * Description: Controls for column visiblity in DataTables + * Author: Allan Jardine (www.sprymedia.co.uk) + * Created: Wed Sep 15 18:23:29 BST 2010 + * Modified: $Date$ by $Author$ + * Language: Javascript + * License: GPL v2 or BSD 3 point style + * Project: Just a little bit of fun :-) + * Contact: www.sprymedia.co.uk/contact + * + * Copyright 2010-2011 Allan Jardine, all rights reserved. + * + * This source file is free software, under either the GPL v2 license or a + * BSD style license, available at: + * http://datatables.net/license_gpl2 + * http://datatables.net/license_bsd + */ + +(function($) { + +/** + * ColVis provides column visiblity control for DataTables + * @class ColVis + * @constructor + * @param {object} DataTables settings object + */ +var ColVis = function( oDTSettings, oInit ) +{ + /* Santiy check that we are a new instance */ + if ( !this.CLASS || this.CLASS != "ColVis" ) + { + alert( "Warning: ColVis must be initialised with the keyword 'new'" ); + } + + if ( typeof oInit == 'undefined' ) + { + oInit = {}; + } + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Public class variables + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * @namespace Settings object which contains customisable information for ColVis instance + */ + this.s = { + /** + * DataTables settings object + * @property dt + * @type Object + * @default null + */ + "dt": null, + + /** + * Customisation object + * @property oInit + * @type Object + * @default passed in + */ + "oInit": oInit, + + /** + * Callback function to tell the user when the state has changed + * @property fnStateChange + * @type function + * @default null + */ + "fnStateChange": null, + + /** + * Mode of activation. Can be 'click' or 'mouseover' + * @property activate + * @type String + * @default click + */ + "activate": "click", + + /** + * Position of the collection menu when shown - align "left" or "right" + * @property sAlign + * @type String + * @default right + */ + "sAlign": "left", + + /** + * Text used for the button + * @property buttonText + * @type String + * @default Show / hide columns + */ + "buttonText": "Show / hide columns", + + /** + * Flag to say if the collection is hidden + * @property hidden + * @type boolean + * @default true + */ + "hidden": true, + + /** + * List of columns (integers) which should be excluded from the list + * @property aiExclude + * @type Array + * @default [] + */ + "aiExclude": [], + + /** + * Group buttons + * @property aoGroups + * @type Array + * @default [] + */ + "aoGroups": [], + + /** + * Store the original viisbility settings so they could be restored + * @property abOriginal + * @type Array + * @default [] + */ + "abOriginal": [], + + /** + * Show Show-All button + * @property bShowAll + * @type Array + * @default [] + */ + "bShowAll": false, + + /** + * Show All button text + * @property sShowAll + * @type String + * @default Restore original + */ + "sShowAll": "Show All", + + /** + * Show restore button + * @property bRestore + * @type Array + * @default [] + */ + "bRestore": false, + + /** + * Restore button text + * @property sRestore + * @type String + * @default Restore original + */ + "sRestore": "Restore original", + + /** + * Overlay animation duration in mS + * @property iOverlayFade + * @type Integer + * @default 500 + */ + "iOverlayFade": 500, + + /** + * Label callback for column names. Takes three parameters: 1. the column index, 2. the column + * title detected by DataTables and 3. the TH node for the column + * @property fnLabel + * @type Function + * @default null + */ + "fnLabel": null, + + /** + * Indicate if ColVis should automatically calculate the size of buttons or not. The default + * is for it to do so. Set to "css" to disable the automatic sizing + * @property sSize + * @type String + * @default auto + */ + "sSize": "auto", + + /** + * Indicate if the column list should be positioned by Javascript, visually below the button + * or allow CSS to do the positioning + * @property bCssPosition + * @type boolean + * @default false + */ + "bCssPosition": false + }; + + + /** + * @namespace Common and useful DOM elements for the class instance + */ + this.dom = { + /** + * Wrapper for the button - given back to DataTables as the node to insert + * @property wrapper + * @type Node + * @default null + */ + "wrapper": null, + + /** + * Activation button + * @property button + * @type Node + * @default null + */ + "button": null, + + /** + * Collection list node + * @property collection + * @type Node + * @default null + */ + "collection": null, + + /** + * Background node used for shading the display and event capturing + * @property background + * @type Node + * @default null + */ + "background": null, + + /** + * Element to position over the activation button to catch mouse events when using mouseover + * @property catcher + * @type Node + * @default null + */ + "catcher": null, + + /** + * List of button elements + * @property buttons + * @type Array + * @default [] + */ + "buttons": [], + + /** + * List of group button elements + * @property groupButtons + * @type Array + * @default [] + */ + "groupButtons": [], + + /** + * Restore button + * @property restore + * @type Node + * @default null + */ + "restore": null + }; + + /* Store global reference */ + ColVis.aInstances.push( this ); + + /* Constructor logic */ + this.s.dt = oDTSettings; + this._fnConstruct(); + return this; +}; + + + +ColVis.prototype = { + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Public methods + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * Rebuild the list of buttons for this instance (i.e. if there is a column header update) + * @method fnRebuild + * @returns void + */ + "fnRebuild": function () + { + /* Remove the old buttons */ + for ( var i=this.dom.buttons.length-1 ; i>=0 ; i-- ) + { + if ( this.dom.buttons[i] !== null ) + { + this.dom.collection.removeChild( this.dom.buttons[i] ); + } + } + this.dom.buttons.splice( 0, this.dom.buttons.length ); + + if ( this.dom.restore ) + { + this.dom.restore.parentNode( this.dom.restore ); + } + + /* Re-add them (this is not the optimal way of doing this, it is fast and effective) */ + this._fnAddGroups(); + this._fnAddButtons(); + + /* Update the checkboxes */ + this._fnDrawCallback(); + }, + + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Private methods (they are of course public in JS, but recommended as private) + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * Constructor logic + * @method _fnConstruct + * @returns void + * @private + */ + "_fnConstruct": function () + { + this._fnApplyCustomisation(); + + var that = this; + var i, iLen; + this.dom.wrapper = document.createElement('div'); + this.dom.wrapper.className = "ColVis TableTools"; + + this.dom.button = this._fnDomBaseButton( this.s.buttonText ); + this.dom.button.className += " ColVis_MasterButton"; + this.dom.wrapper.appendChild( this.dom.button ); + + this.dom.catcher = this._fnDomCatcher(); + this.dom.collection = this._fnDomCollection(); + this.dom.background = this._fnDomBackground(); + + this._fnAddGroups(); + this._fnAddButtons(); + + /* Store the original visbility information */ + for ( i=0, iLen=this.s.dt.aoColumns.length ; i'+this.s.sRestore+'' ); + + $(nButton).click( function (e) { + for ( var i=0, iLen=that.s.abOriginal.length ; i'+this.s.sShowAll+'' ); + + $(nButton).click( function (e) { + for ( var i=0, iLen=that.s.abOriginal.length ; i'+ + ''+sTitle+'' ); + + $(nButton).click( function (e) { + var showHide = !$('input', this).is(":checked"); + if ( e.target.nodeName.toLowerCase() == "input" ) + { + showHide = $('input', this).is(":checked"); + } + + for ( var j=0 ; j < oGroup.aiColumns.length ; j++ ) + { + that.s.dt.oInstance.fnSetColumnVis( oGroup.aiColumns[j], showHide ); + } + }); + + return nButton; + }, + + + /** + * Create the DOM for a show / hide button + * @method _fnDomColumnButton + * @param {int} i Column in question + * @returns {Node} Created button + * @private + */ + "_fnDomColumnButton": function ( i ) + { + var + that = this, + oColumn = this.s.dt.aoColumns[i], + nButton = document.createElement('button'), + nSpan = document.createElement('span'), + dt = this.s.dt; + + nButton.className = !dt.bJUI ? "ColVis_Button TableTools_Button" : + "ColVis_Button TableTools_Button ui-button ui-state-default"; + nButton.appendChild( nSpan ); + var sTitle = this.s.fnLabel===null ? oColumn.sTitle : this.s.fnLabel( i, oColumn.sTitle, oColumn.nTh ); + $(nSpan).html( + ''+ + ''+sTitle+'' ); + + $(nButton).click( function (e) { + var showHide = !$('input', this).is(":checked"); + if ( e.target.nodeName.toLowerCase() == "input" ) + { + showHide = $('input', this).is(":checked"); + } + + /* Need to consider the case where the initialiser created more than one table - change the + * API index that DataTables is using + */ + var oldIndex = $.fn.dataTableExt.iApiIndex; + $.fn.dataTableExt.iApiIndex = that._fnDataTablesApiIndex.call(that); + + // Optimisation for server-side processing when scrolling - don't do a full redraw + if ( dt.oFeatures.bServerSide && (dt.oScroll.sX !== "" || dt.oScroll.sY !== "" ) ) + { + that.s.dt.oInstance.fnSetColumnVis( i, showHide, false ); + that.s.dt.oInstance.fnAdjustColumnSizing( false ); + that.s.dt.oInstance.oApi._fnScrollDraw( that.s.dt ); + that._fnDrawCallback(); + } + else + { + that.s.dt.oInstance.fnSetColumnVis( i, showHide ); + } + + $.fn.dataTableExt.iApiIndex = oldIndex; /* Restore */ + + if ( that.s.fnStateChange !== null ) + { + that.s.fnStateChange.call( that, i, showHide ); + } + } ); + + return nButton; + }, + + + /** + * Get the position in the DataTables instance array of the table for this instance of ColVis + * @method _fnDataTablesApiIndex + * @returns {int} Index + * @private + */ + "_fnDataTablesApiIndex": function () + { + for ( var i=0, iLen=this.s.dt.oInstance.length ; i
      ","
      "],tr:[2,"","
      "],td:[3,"","
      "],col:[2,"","
      "],area:[1,"",""],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X
      ","
      "]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1>");try{for(;d1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]===""&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("
      ").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/jQuery/jquery-1.3.2.min.js b/misc/PublicationBulletins/Portail-LeHavre/jQuery/jquery-1.3.2.min.js new file mode 100755 index 00000000..55c2e6d7 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/jQuery/jquery-1.3.2.min.js @@ -0,0 +1,19 @@ +/* + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"
      ","
      "]||!O.indexOf("
      "]||(!O.indexOf("
      "]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
      ","
      "]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); +/* + * Sizzle CSS Selector Engine - v0.9.3 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

      ";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
      ";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
      ").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
      ';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/jQuery/jquery-1.8.2.min.js b/misc/PublicationBulletins/Portail-LeHavre/jQuery/jquery-1.8.2.min.js new file mode 100755 index 00000000..f65cf1dc --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/jQuery/jquery-1.8.2.min.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.2 jquery.com | jquery.org/license */ +(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write(""),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b
      a",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="
      t
      ",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="
      ",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;be.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="
      ",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="

      ",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b0)for(e=d;e=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*\s*$/g,bz={option:[1,""],legend:[1,"
      ","
      "],thead:[1,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],col:[2,"","
      "],area:[1,"",""],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X
      ","
      "]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1>");try{for(;d1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]===""&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("
      ").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/jQuery/jquery.dataTables.js b/misc/PublicationBulletins/Portail-LeHavre/jQuery/jquery.dataTables.js new file mode 100755 index 00000000..27f1c278 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/jQuery/jquery.dataTables.js @@ -0,0 +1,12099 @@ +/** + * @summary DataTables + * @description Paginate, search and sort HTML tables + * @version 1.9.4 + * @file jquery.dataTables.js + * @author Allan Jardine (www.sprymedia.co.uk) + * @contact www.sprymedia.co.uk/contact + * + * @copyright Copyright 2008-2012 Allan Jardine, all rights reserved. + * + * This source file is free software, under either the GPL v2 license or a + * BSD style license, available at: + * http://datatables.net/license_gpl2 + * http://datatables.net/license_bsd + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + * + * For details please refer to: http://www.datatables.net + */ + +/*jslint evil: true, undef: true, browser: true */ +/*globals $, jQuery,define,_fnExternApiFunc,_fnInitialise,_fnInitComplete,_fnLanguageCompat,_fnAddColumn,_fnColumnOptions,_fnAddData,_fnCreateTr,_fnGatherData,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnServerParams,_fnAddOptionsHtml,_fnFeatureHtmlTable,_fnScrollDraw,_fnAdjustColumnSizing,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnBuildSearchArray,_fnBuildSearchRow,_fnFilterCreateSearch,_fnDataToSearch,_fnSort,_fnSortAttachListener,_fnSortingClasses,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnFeatureHtmlLength,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnNodeToDataIndex,_fnVisbleColumns,_fnCalculateEnd,_fnConvertToWidth,_fnCalculateColumnWidths,_fnScrollingWidthAdjust,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnDetectType,_fnSettingsFromNode,_fnGetDataMaster,_fnGetTrNodes,_fnGetTdNodes,_fnEscapeRegex,_fnDeleteIndex,_fnReOrderIndex,_fnColumnOrdering,_fnLog,_fnClearTable,_fnSaveState,_fnLoadState,_fnCreateCookie,_fnReadCookie,_fnDetectHeader,_fnGetUniqueThs,_fnScrollBarWidth,_fnApplyToChildren,_fnMap,_fnGetRowData,_fnGetCellData,_fnSetCellData,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnApplyColumnDefs,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnJsonString,_fnRender,_fnNodeToColumnIndex,_fnInfoMacros,_fnBrowserDetect,_fnGetColumns*/ + +(/** @lends */function( window, document, undefined ) { + +(function( factory ) { + "use strict"; + + // Define as an AMD module if possible + if ( typeof define === 'function' && define.amd ) + { + define( ['jquery'], factory ); + } + /* Define using browser globals otherwise + * Prevent multiple instantiations if the script is loaded twice + */ + else if ( jQuery && !jQuery.fn.dataTable ) + { + factory( jQuery ); + } +} +(/** @lends */function( $ ) { + "use strict"; + /** + * DataTables is a plug-in for the jQuery Javascript library. It is a + * highly flexible tool, based upon the foundations of progressive + * enhancement, which will add advanced interaction controls to any + * HTML table. For a full list of features please refer to + * DataTables.net. + * + * Note that the DataTable object is not a global variable but is + * aliased to jQuery.fn.DataTable and jQuery.fn.dataTable through which + * it may be accessed. + * + * @class + * @param {object} [oInit={}] Configuration object for DataTables. Options + * are defined by {@link DataTable.defaults} + * @requires jQuery 1.3+ + * + * @example + * // Basic initialisation + * $(document).ready( function { + * $('#example').dataTable(); + * } ); + * + * @example + * // Initialisation with configuration options - in this case, disable + * // pagination and sorting. + * $(document).ready( function { + * $('#example').dataTable( { + * "bPaginate": false, + * "bSort": false + * } ); + * } ); + */ + var DataTable = function( oInit ) + { + + + /** + * Add a column to the list used for the table with default values + * @param {object} oSettings dataTables settings object + * @param {node} nTh The th element for this column + * @memberof DataTable#oApi + */ + function _fnAddColumn( oSettings, nTh ) + { + var oDefaults = DataTable.defaults.columns; + var iCol = oSettings.aoColumns.length; + var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, { + "sSortingClass": oSettings.oClasses.sSortable, + "sSortingClassJUI": oSettings.oClasses.sSortJUI, + "nTh": nTh ? nTh : document.createElement('th'), + "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '', + "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], + "mData": oDefaults.mData ? oDefaults.oDefaults : iCol + } ); + oSettings.aoColumns.push( oCol ); + + /* Add a column specific filter */ + if ( oSettings.aoPreSearchCols[ iCol ] === undefined || oSettings.aoPreSearchCols[ iCol ] === null ) + { + oSettings.aoPreSearchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch ); + } + else + { + var oPre = oSettings.aoPreSearchCols[ iCol ]; + + /* Don't require that the user must specify bRegex, bSmart or bCaseInsensitive */ + if ( oPre.bRegex === undefined ) + { + oPre.bRegex = true; + } + + if ( oPre.bSmart === undefined ) + { + oPre.bSmart = true; + } + + if ( oPre.bCaseInsensitive === undefined ) + { + oPre.bCaseInsensitive = true; + } + } + + /* Use the column options function to initialise classes etc */ + _fnColumnOptions( oSettings, iCol, null ); + } + + + /** + * Apply options for a column + * @param {object} oSettings dataTables settings object + * @param {int} iCol column index to consider + * @param {object} oOptions object with sType, bVisible and bSearchable etc + * @memberof DataTable#oApi + */ + function _fnColumnOptions( oSettings, iCol, oOptions ) + { + var oCol = oSettings.aoColumns[ iCol ]; + + /* User specified column options */ + if ( oOptions !== undefined && oOptions !== null ) + { + /* Backwards compatibility for mDataProp */ + if ( oOptions.mDataProp && !oOptions.mData ) + { + oOptions.mData = oOptions.mDataProp; + } + + if ( oOptions.sType !== undefined ) + { + oCol.sType = oOptions.sType; + oCol._bAutoType = false; + } + + $.extend( oCol, oOptions ); + _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); + + /* iDataSort to be applied (backwards compatibility), but aDataSort will take + * priority if defined + */ + if ( oOptions.iDataSort !== undefined ) + { + oCol.aDataSort = [ oOptions.iDataSort ]; + } + _fnMap( oCol, oOptions, "aDataSort" ); + } + + /* Cache the data get and set functions for speed */ + var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null; + var mData = _fnGetObjectDataFn( oCol.mData ); + + oCol.fnGetData = function (oData, sSpecific) { + var innerData = mData( oData, sSpecific ); + + if ( oCol.mRender && (sSpecific && sSpecific !== '') ) + { + return mRender( innerData, sSpecific, oData ); + } + return innerData; + }; + oCol.fnSetData = _fnSetObjectDataFn( oCol.mData ); + + /* Feature sorting overrides column specific when off */ + if ( !oSettings.oFeatures.bSort ) + { + oCol.bSortable = false; + } + + /* Check that the class assignment is correct for sorting */ + if ( !oCol.bSortable || + ($.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) == -1) ) + { + oCol.sSortingClass = oSettings.oClasses.sSortableNone; + oCol.sSortingClassJUI = ""; + } + else if ( $.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) == -1 ) + { + oCol.sSortingClass = oSettings.oClasses.sSortable; + oCol.sSortingClassJUI = oSettings.oClasses.sSortJUI; + } + else if ( $.inArray('asc', oCol.asSorting) != -1 && $.inArray('desc', oCol.asSorting) == -1 ) + { + oCol.sSortingClass = oSettings.oClasses.sSortableAsc; + oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIAscAllowed; + } + else if ( $.inArray('asc', oCol.asSorting) == -1 && $.inArray('desc', oCol.asSorting) != -1 ) + { + oCol.sSortingClass = oSettings.oClasses.sSortableDesc; + oCol.sSortingClassJUI = oSettings.oClasses.sSortJUIDescAllowed; + } + } + + + /** + * Adjust the table column widths for new data. Note: you would probably want to + * do a redraw after calling this function! + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnAdjustColumnSizing ( oSettings ) + { + /* Not interested in doing column width calculation if auto-width is disabled */ + if ( oSettings.oFeatures.bAutoWidth === false ) + { + return false; + } + + _fnCalculateColumnWidths( oSettings ); + for ( var i=0 , iLen=oSettings.aoColumns.length ; i
      ')[0]; + oSettings.nTable.parentNode.insertBefore( nHolding, oSettings.nTable ); + + /* + * All DataTables are wrapped in a div + */ + oSettings.nTableWrapper = $('
      ')[0]; + oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling; + + /* Track where we want to insert the option */ + var nInsertNode = oSettings.nTableWrapper; + + /* Loop over the user set positioning and place the elements as needed */ + var aDom = oSettings.sDom.split(''); + var nTmp, iPushFeature, cOption, nNewNode, cNext, sAttr, j; + for ( var i=0 ; i')[0]; + + /* Check to see if we should append an id and/or a class name to the container */ + cNext = aDom[i+1]; + if ( cNext == "'" || cNext == '"' ) + { + sAttr = ""; + j = 2; + while ( aDom[i+j] != cNext ) + { + sAttr += aDom[i+j]; + j++; + } + + /* Replace jQuery UI constants */ + if ( sAttr == "H" ) + { + sAttr = oSettings.oClasses.sJUIHeader; + } + else if ( sAttr == "F" ) + { + sAttr = oSettings.oClasses.sJUIFooter; + } + + /* The attribute can be in the format of "#id.class", "#id" or "class" This logic + * breaks the string into parts and applies them as needed + */ + if ( sAttr.indexOf('.') != -1 ) + { + var aSplit = sAttr.split('.'); + nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1); + nNewNode.className = aSplit[1]; + } + else if ( sAttr.charAt(0) == "#" ) + { + nNewNode.id = sAttr.substr(1, sAttr.length-1); + } + else + { + nNewNode.className = sAttr; + } + + i += j; /* Move along the position array */ + } + + nInsertNode.appendChild( nNewNode ); + nInsertNode = nNewNode; + } + else if ( cOption == '>' ) + { + /* End container div */ + nInsertNode = nInsertNode.parentNode; + } + else if ( cOption == 'l' && oSettings.oFeatures.bPaginate && oSettings.oFeatures.bLengthChange ) + { + /* Length */ + nTmp = _fnFeatureHtmlLength( oSettings ); + iPushFeature = 1; + } + else if ( cOption == 'f' && oSettings.oFeatures.bFilter ) + { + /* Filter */ + nTmp = _fnFeatureHtmlFilter( oSettings ); + iPushFeature = 1; + } + else if ( cOption == 'r' && oSettings.oFeatures.bProcessing ) + { + /* pRocessing */ + nTmp = _fnFeatureHtmlProcessing( oSettings ); + iPushFeature = 1; + } + else if ( cOption == 't' ) + { + /* Table */ + nTmp = _fnFeatureHtmlTable( oSettings ); + iPushFeature = 1; + } + else if ( cOption == 'i' && oSettings.oFeatures.bInfo ) + { + /* Info */ + nTmp = _fnFeatureHtmlInfo( oSettings ); + iPushFeature = 1; + } + else if ( cOption == 'p' && oSettings.oFeatures.bPaginate ) + { + /* Pagination */ + nTmp = _fnFeatureHtmlPaginate( oSettings ); + iPushFeature = 1; + } + else if ( DataTable.ext.aoFeatures.length !== 0 ) + { + /* Plug-in features */ + var aoFeatures = DataTable.ext.aoFeatures; + for ( var k=0, kLen=aoFeatures.length ; k') : + sSearchStr==="" ? '' : sSearchStr+' '; + + var nFilter = document.createElement( 'div' ); + nFilter.className = oSettings.oClasses.sFilter; + nFilter.innerHTML = ''; + if ( !oSettings.aanFeatures.f ) + { + nFilter.id = oSettings.sTableId+'_filter'; + } + + var jqFilter = $('input[type="text"]', nFilter); + + // Store a reference to the input element, so other input elements could be + // added to the filter wrapper if needed (submit button for example) + nFilter._DT_Input = jqFilter[0]; + + jqFilter.val( oPreviousSearch.sSearch.replace('"','"') ); + jqFilter.bind( 'keyup.DT', function(e) { + /* Update all other filter input elements for the new display */ + var n = oSettings.aanFeatures.f; + var val = this.value==="" ? "" : this.value; // mental IE8 fix :-( + + for ( var i=0, iLen=n.length ; i=0 ; i-- ) + { + var sData = _fnDataToSearch( _fnGetCellData( oSettings, oSettings.aiDisplay[i], iColumn, 'filter' ), + oSettings.aoColumns[iColumn].sType ); + if ( ! rpSearch.test( sData ) ) + { + oSettings.aiDisplay.splice( i, 1 ); + iIndexCorrector++; + } + } + } + + + /** + * Filter the data table based on user input and draw the table + * @param {object} oSettings dataTables settings object + * @param {string} sInput string to filter on + * @param {int} iForce optional - force a research of the master array (1) or not (undefined or 0) + * @param {bool} bRegex treat as a regular expression or not + * @param {bool} bSmart perform smart filtering or not + * @param {bool} bCaseInsensitive Do case insenstive matching or not + * @memberof DataTable#oApi + */ + function _fnFilter( oSettings, sInput, iForce, bRegex, bSmart, bCaseInsensitive ) + { + var i; + var rpSearch = _fnFilterCreateSearch( sInput, bRegex, bSmart, bCaseInsensitive ); + var oPrevSearch = oSettings.oPreviousSearch; + + /* Check if we are forcing or not - optional parameter */ + if ( !iForce ) + { + iForce = 0; + } + + /* Need to take account of custom filtering functions - always filter */ + if ( DataTable.ext.afnFiltering.length !== 0 ) + { + iForce = 1; + } + + /* + * If the input is blank - we want the full data set + */ + if ( sInput.length <= 0 ) + { + oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length); + oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); + } + else + { + /* + * We are starting a new search or the new search string is smaller + * then the old one (i.e. delete). Search from the master array + */ + if ( oSettings.aiDisplay.length == oSettings.aiDisplayMaster.length || + oPrevSearch.sSearch.length > sInput.length || iForce == 1 || + sInput.indexOf(oPrevSearch.sSearch) !== 0 ) + { + /* Nuke the old display array - we are going to rebuild it */ + oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length); + + /* Force a rebuild of the search array */ + _fnBuildSearchArray( oSettings, 1 ); + + /* Search through all records to populate the search array + * The the oSettings.aiDisplayMaster and asDataSearch arrays have 1 to 1 + * mapping + */ + for ( i=0 ; i').html(sSearch).text(); + } + + // Strip newline characters + return sSearch.replace( /[\n\r]/g, " " ); + } + + /** + * Build a regular expression object suitable for searching a table + * @param {string} sSearch string to search for + * @param {bool} bRegex treat as a regular expression or not + * @param {bool} bSmart perform smart filtering or not + * @param {bool} bCaseInsensitive Do case insensitive matching or not + * @returns {RegExp} constructed object + * @memberof DataTable#oApi + */ + function _fnFilterCreateSearch( sSearch, bRegex, bSmart, bCaseInsensitive ) + { + var asSearch, sRegExpString; + + if ( bSmart ) + { + /* Generate the regular expression to use. Something along the lines of: + * ^(?=.*?\bone\b)(?=.*?\btwo\b)(?=.*?\bthree\b).*$ + */ + asSearch = bRegex ? sSearch.split( ' ' ) : _fnEscapeRegex( sSearch ).split( ' ' ); + sRegExpString = '^(?=.*?'+asSearch.join( ')(?=.*?' )+').*$'; + return new RegExp( sRegExpString, bCaseInsensitive ? "i" : "" ); + } + else + { + sSearch = bRegex ? sSearch : _fnEscapeRegex( sSearch ); + return new RegExp( sSearch, bCaseInsensitive ? "i" : "" ); + } + } + + + /** + * Convert raw data into something that the user can search on + * @param {string} sData data to be modified + * @param {string} sType data type + * @returns {string} search string + * @memberof DataTable#oApi + */ + function _fnDataToSearch ( sData, sType ) + { + if ( typeof DataTable.ext.ofnSearch[sType] === "function" ) + { + return DataTable.ext.ofnSearch[sType]( sData ); + } + else if ( sData === null ) + { + return ''; + } + else if ( sType == "html" ) + { + return sData.replace(/[\r\n]/g," ").replace( /<.*?>/g, "" ); + } + else if ( typeof sData === "string" ) + { + return sData.replace(/[\r\n]/g," "); + } + return sData; + } + + + /** + * scape a string such that it can be used in a regular expression + * @param {string} sVal string to escape + * @returns {string} escaped string + * @memberof DataTable#oApi + */ + function _fnEscapeRegex ( sVal ) + { + var acEscape = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ]; + var reReplace = new RegExp( '(\\' + acEscape.join('|\\') + ')', 'g' ); + return sVal.replace(reReplace, '\\$1'); + } + + + /** + * Generate the node required for the info display + * @param {object} oSettings dataTables settings object + * @returns {node} Information element + * @memberof DataTable#oApi + */ + function _fnFeatureHtmlInfo ( oSettings ) + { + var nInfo = document.createElement( 'div' ); + nInfo.className = oSettings.oClasses.sInfo; + + /* Actions that are to be taken once only for this feature */ + if ( !oSettings.aanFeatures.i ) + { + /* Add draw callback */ + oSettings.aoDrawCallback.push( { + "fn": _fnUpdateInfo, + "sName": "information" + } ); + + /* Add id */ + nInfo.id = oSettings.sTableId+'_info'; + } + oSettings.nTable.setAttribute( 'aria-describedby', oSettings.sTableId+'_info' ); + + return nInfo; + } + + + /** + * Update the information elements in the display + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnUpdateInfo ( oSettings ) + { + /* Show information about the table */ + if ( !oSettings.oFeatures.bInfo || oSettings.aanFeatures.i.length === 0 ) + { + return; + } + + var + oLang = oSettings.oLanguage, + iStart = oSettings._iDisplayStart+1, + iEnd = oSettings.fnDisplayEnd(), + iMax = oSettings.fnRecordsTotal(), + iTotal = oSettings.fnRecordsDisplay(), + sOut; + + if ( iTotal === 0 ) + { + /* Empty record set */ + sOut = oLang.sInfoEmpty; + } + else { + /* Normal record set */ + sOut = oLang.sInfo; + } + + if ( iTotal != iMax ) + { + /* Record set after filtering */ + sOut += ' ' + oLang.sInfoFiltered; + } + + // Convert the macros + sOut += oLang.sInfoPostFix; + sOut = _fnInfoMacros( oSettings, sOut ); + + if ( oLang.fnInfoCallback !== null ) + { + sOut = oLang.fnInfoCallback.call( oSettings.oInstance, + oSettings, iStart, iEnd, iMax, iTotal, sOut ); + } + + var n = oSettings.aanFeatures.i; + for ( var i=0, iLen=n.length ; i'; + var i, iLen; + var aLengthMenu = oSettings.aLengthMenu; + + if ( aLengthMenu.length == 2 && typeof aLengthMenu[0] === 'object' && + typeof aLengthMenu[1] === 'object' ) + { + for ( i=0, iLen=aLengthMenu[0].length ; i'+aLengthMenu[1][i]+''; + } + } + else + { + for ( i=0, iLen=aLengthMenu.length ; i'+aLengthMenu[i]+''; + } + } + sStdMenu += ''; + + var nLength = document.createElement( 'div' ); + if ( !oSettings.aanFeatures.l ) + { + nLength.id = oSettings.sTableId+'_length'; + } + nLength.className = oSettings.oClasses.sLength; + nLength.innerHTML = ''; + + /* + * Set the length to the current display length - thanks to Andrea Pavlovic for this fix, + * and Stefan Skopnik for fixing the fix! + */ + $('select option[value="'+oSettings._iDisplayLength+'"]', nLength).attr("selected", true); + + $('select', nLength).bind( 'change.DT', function(e) { + var iVal = $(this).val(); + + /* Update all other length options for the new display */ + var n = oSettings.aanFeatures.l; + for ( i=0, iLen=n.length ; i oSettings.aiDisplay.length || + oSettings._iDisplayLength == -1 ) + { + oSettings._iDisplayEnd = oSettings.aiDisplay.length; + } + else + { + oSettings._iDisplayEnd = oSettings._iDisplayStart + oSettings._iDisplayLength; + } + } + } + + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Note that most of the paging logic is done in + * DataTable.ext.oPagination + */ + + /** + * Generate the node required for default pagination + * @param {object} oSettings dataTables settings object + * @returns {node} Pagination feature node + * @memberof DataTable#oApi + */ + function _fnFeatureHtmlPaginate ( oSettings ) + { + if ( oSettings.oScroll.bInfinite ) + { + return null; + } + + var nPaginate = document.createElement( 'div' ); + nPaginate.className = oSettings.oClasses.sPaging+oSettings.sPaginationType; + + DataTable.ext.oPagination[ oSettings.sPaginationType ].fnInit( oSettings, nPaginate, + function( oSettings ) { + _fnCalculateEnd( oSettings ); + _fnDraw( oSettings ); + } + ); + + /* Add a draw callback for the pagination on first instance, to update the paging display */ + if ( !oSettings.aanFeatures.p ) + { + oSettings.aoDrawCallback.push( { + "fn": function( oSettings ) { + DataTable.ext.oPagination[ oSettings.sPaginationType ].fnUpdate( oSettings, function( oSettings ) { + _fnCalculateEnd( oSettings ); + _fnDraw( oSettings ); + } ); + }, + "sName": "pagination" + } ); + } + return nPaginate; + } + + + /** + * Alter the display settings to change the page + * @param {object} oSettings dataTables settings object + * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" + * or page number to jump to (integer) + * @returns {bool} true page has changed, false - no change (no effect) eg 'first' on page 1 + * @memberof DataTable#oApi + */ + function _fnPageChange ( oSettings, mAction ) + { + var iOldStart = oSettings._iDisplayStart; + + if ( typeof mAction === "number" ) + { + oSettings._iDisplayStart = mAction * oSettings._iDisplayLength; + if ( oSettings._iDisplayStart > oSettings.fnRecordsDisplay() ) + { + oSettings._iDisplayStart = 0; + } + } + else if ( mAction == "first" ) + { + oSettings._iDisplayStart = 0; + } + else if ( mAction == "previous" ) + { + oSettings._iDisplayStart = oSettings._iDisplayLength>=0 ? + oSettings._iDisplayStart - oSettings._iDisplayLength : + 0; + + /* Correct for under-run */ + if ( oSettings._iDisplayStart < 0 ) + { + oSettings._iDisplayStart = 0; + } + } + else if ( mAction == "next" ) + { + if ( oSettings._iDisplayLength >= 0 ) + { + /* Make sure we are not over running the display array */ + if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() ) + { + oSettings._iDisplayStart += oSettings._iDisplayLength; + } + } + else + { + oSettings._iDisplayStart = 0; + } + } + else if ( mAction == "last" ) + { + if ( oSettings._iDisplayLength >= 0 ) + { + var iPages = parseInt( (oSettings.fnRecordsDisplay()-1) / oSettings._iDisplayLength, 10 ) + 1; + oSettings._iDisplayStart = (iPages-1) * oSettings._iDisplayLength; + } + else + { + oSettings._iDisplayStart = 0; + } + } + else + { + _fnLog( oSettings, 0, "Unknown paging action: "+mAction ); + } + $(oSettings.oInstance).trigger('page', oSettings); + + return iOldStart != oSettings._iDisplayStart; + } + + + + /** + * Generate the node required for the processing node + * @param {object} oSettings dataTables settings object + * @returns {node} Processing element + * @memberof DataTable#oApi + */ + function _fnFeatureHtmlProcessing ( oSettings ) + { + var nProcessing = document.createElement( 'div' ); + + if ( !oSettings.aanFeatures.r ) + { + nProcessing.id = oSettings.sTableId+'_processing'; + } + nProcessing.innerHTML = oSettings.oLanguage.sProcessing; + nProcessing.className = oSettings.oClasses.sProcessing; + oSettings.nTable.parentNode.insertBefore( nProcessing, oSettings.nTable ); + + return nProcessing; + } + + + /** + * Display or hide the processing indicator + * @param {object} oSettings dataTables settings object + * @param {bool} bShow Show the processing indicator (true) or not (false) + * @memberof DataTable#oApi + */ + function _fnProcessingDisplay ( oSettings, bShow ) + { + if ( oSettings.oFeatures.bProcessing ) + { + var an = oSettings.aanFeatures.r; + for ( var i=0, iLen=an.length ; i 0 ) + { + nCaption = nCaption[0]; + if ( nCaption._captionSide === "top" ) + { + nScrollHeadTable.appendChild( nCaption ); + } + else if ( nCaption._captionSide === "bottom" && nTfoot ) + { + nScrollFootTable.appendChild( nCaption ); + } + } + + /* + * Sizing + */ + /* When x-scrolling add the width and a scroller to move the header with the body */ + if ( oSettings.oScroll.sX !== "" ) + { + nScrollHead.style.width = _fnStringToCss( oSettings.oScroll.sX ); + nScrollBody.style.width = _fnStringToCss( oSettings.oScroll.sX ); + + if ( nTfoot !== null ) + { + nScrollFoot.style.width = _fnStringToCss( oSettings.oScroll.sX ); + } + + /* When the body is scrolled, then we also want to scroll the headers */ + $(nScrollBody).scroll( function (e) { + nScrollHead.scrollLeft = this.scrollLeft; + + if ( nTfoot !== null ) + { + nScrollFoot.scrollLeft = this.scrollLeft; + } + } ); + } + + /* When yscrolling, add the height */ + if ( oSettings.oScroll.sY !== "" ) + { + nScrollBody.style.height = _fnStringToCss( oSettings.oScroll.sY ); + } + + /* Redraw - align columns across the tables */ + oSettings.aoDrawCallback.push( { + "fn": _fnScrollDraw, + "sName": "scrolling" + } ); + + /* Infinite scrolling event handlers */ + if ( oSettings.oScroll.bInfinite ) + { + $(nScrollBody).scroll( function() { + /* Use a blocker to stop scrolling from loading more data while other data is still loading */ + if ( !oSettings.bDrawing && $(this).scrollTop() !== 0 ) + { + /* Check if we should load the next data set */ + if ( $(this).scrollTop() + $(this).height() > + $(oSettings.nTable).height() - oSettings.oScroll.iLoadGap ) + { + /* Only do the redraw if we have to - we might be at the end of the data */ + if ( oSettings.fnDisplayEnd() < oSettings.fnRecordsDisplay() ) + { + _fnPageChange( oSettings, 'next' ); + _fnCalculateEnd( oSettings ); + _fnDraw( oSettings ); + } + } + } + } ); + } + + oSettings.nScrollHead = nScrollHead; + oSettings.nScrollFoot = nScrollFoot; + + return nScroller; + } + + + /** + * Update the various tables for resizing. It's a bit of a pig this function, but + * basically the idea to: + * 1. Re-create the table inside the scrolling div + * 2. Take live measurements from the DOM + * 3. Apply the measurements + * 4. Clean up + * @param {object} o dataTables settings object + * @returns {node} Node to add to the DOM + * @memberof DataTable#oApi + */ + function _fnScrollDraw ( o ) + { + var + nScrollHeadInner = o.nScrollHead.getElementsByTagName('div')[0], + nScrollHeadTable = nScrollHeadInner.getElementsByTagName('table')[0], + nScrollBody = o.nTable.parentNode, + i, iLen, j, jLen, anHeadToSize, anHeadSizers, anFootSizers, anFootToSize, oStyle, iVis, + nTheadSize, nTfootSize, + iWidth, aApplied=[], aAppliedFooter=[], iSanityWidth, + nScrollFootInner = (o.nTFoot !== null) ? o.nScrollFoot.getElementsByTagName('div')[0] : null, + nScrollFootTable = (o.nTFoot !== null) ? nScrollFootInner.getElementsByTagName('table')[0] : null, + ie67 = o.oBrowser.bScrollOversize, + zeroOut = function(nSizer) { + oStyle = nSizer.style; + oStyle.paddingTop = "0"; + oStyle.paddingBottom = "0"; + oStyle.borderTopWidth = "0"; + oStyle.borderBottomWidth = "0"; + oStyle.height = 0; + }; + + /* + * 1. Re-create the table inside the scrolling div + */ + + /* Remove the old minimised thead and tfoot elements in the inner table */ + $(o.nTable).children('thead, tfoot').remove(); + + /* Clone the current header and footer elements and then place it into the inner table */ + nTheadSize = $(o.nTHead).clone()[0]; + o.nTable.insertBefore( nTheadSize, o.nTable.childNodes[0] ); + anHeadToSize = o.nTHead.getElementsByTagName('tr'); + anHeadSizers = nTheadSize.getElementsByTagName('tr'); + + if ( o.nTFoot !== null ) + { + nTfootSize = $(o.nTFoot).clone()[0]; + o.nTable.insertBefore( nTfootSize, o.nTable.childNodes[1] ); + anFootToSize = o.nTFoot.getElementsByTagName('tr'); + anFootSizers = nTfootSize.getElementsByTagName('tr'); + } + + /* + * 2. Take live measurements from the DOM - do not alter the DOM itself! + */ + + /* Remove old sizing and apply the calculated column widths + * Get the unique column headers in the newly created (cloned) header. We want to apply the + * calculated sizes to this header + */ + if ( o.oScroll.sX === "" ) + { + nScrollBody.style.width = '100%'; + nScrollHeadInner.parentNode.style.width = '100%'; + } + + var nThs = _fnGetUniqueThs( o, nTheadSize ); + for ( i=0, iLen=nThs.length ; i nScrollBody.offsetHeight || + $(nScrollBody).css('overflow-y') == "scroll") ) + { + o.nTable.style.width = _fnStringToCss( $(o.nTable).outerWidth() - o.oScroll.iBarWidth); + } + } + else + { + if ( o.oScroll.sXInner !== "" ) + { + /* x scroll inner has been given - use it */ + o.nTable.style.width = _fnStringToCss(o.oScroll.sXInner); + } + else if ( iSanityWidth == $(nScrollBody).width() && + $(nScrollBody).height() < $(o.nTable).height() ) + { + /* There is y-scrolling - try to take account of the y scroll bar */ + o.nTable.style.width = _fnStringToCss( iSanityWidth-o.oScroll.iBarWidth ); + if ( $(o.nTable).outerWidth() > iSanityWidth-o.oScroll.iBarWidth ) + { + /* Not possible to take account of it */ + o.nTable.style.width = _fnStringToCss( iSanityWidth ); + } + } + else + { + /* All else fails */ + o.nTable.style.width = _fnStringToCss( iSanityWidth ); + } + } + + /* Recalculate the sanity width - now that we've applied the required width, before it was + * a temporary variable. This is required because the column width calculation is done + * before this table DOM is created. + */ + iSanityWidth = $(o.nTable).outerWidth(); + + /* We want the hidden header to have zero height, so remove padding and borders. Then + * set the width based on the real headers + */ + + // Apply all styles in one pass. Invalidates layout only once because we don't read any + // DOM properties. + _fnApplyToChildren( zeroOut, anHeadSizers ); + + // Read all widths in next pass. Forces layout only once because we do not change + // any DOM properties. + _fnApplyToChildren( function(nSizer) { + aApplied.push( _fnStringToCss( $(nSizer).width() ) ); + }, anHeadSizers ); + + // Apply all widths in final pass. Invalidates layout only once because we do not + // read any DOM properties. + _fnApplyToChildren( function(nToSize, i) { + nToSize.style.width = aApplied[i]; + }, anHeadToSize ); + + $(anHeadSizers).height(0); + + /* Same again with the footer if we have one */ + if ( o.nTFoot !== null ) + { + _fnApplyToChildren( zeroOut, anFootSizers ); + + _fnApplyToChildren( function(nSizer) { + aAppliedFooter.push( _fnStringToCss( $(nSizer).width() ) ); + }, anFootSizers ); + + _fnApplyToChildren( function(nToSize, i) { + nToSize.style.width = aAppliedFooter[i]; + }, anFootToSize ); + + $(anFootSizers).height(0); + } + + /* + * 3. Apply the measurements + */ + + /* "Hide" the header and footer that we used for the sizing. We want to also fix their width + * to what they currently are + */ + _fnApplyToChildren( function(nSizer, i) { + nSizer.innerHTML = ""; + nSizer.style.width = aApplied[i]; + }, anHeadSizers ); + + if ( o.nTFoot !== null ) + { + _fnApplyToChildren( function(nSizer, i) { + nSizer.innerHTML = ""; + nSizer.style.width = aAppliedFooter[i]; + }, anFootSizers ); + } + + /* Sanity check that the table is of a sensible width. If not then we are going to get + * misalignment - try to prevent this by not allowing the table to shrink below its min width + */ + if ( $(o.nTable).outerWidth() < iSanityWidth ) + { + /* The min width depends upon if we have a vertical scrollbar visible or not */ + var iCorrection = ((nScrollBody.scrollHeight > nScrollBody.offsetHeight || + $(nScrollBody).css('overflow-y') == "scroll")) ? + iSanityWidth+o.oScroll.iBarWidth : iSanityWidth; + + /* IE6/7 are a law unto themselves... */ + if ( ie67 && (nScrollBody.scrollHeight > + nScrollBody.offsetHeight || $(nScrollBody).css('overflow-y') == "scroll") ) + { + o.nTable.style.width = _fnStringToCss( iCorrection-o.oScroll.iBarWidth ); + } + + /* Apply the calculated minimum width to the table wrappers */ + nScrollBody.style.width = _fnStringToCss( iCorrection ); + o.nScrollHead.style.width = _fnStringToCss( iCorrection ); + + if ( o.nTFoot !== null ) + { + o.nScrollFoot.style.width = _fnStringToCss( iCorrection ); + } + + /* And give the user a warning that we've stopped the table getting too small */ + if ( o.oScroll.sX === "" ) + { + _fnLog( o, 1, "The table cannot fit into the current element which will cause column"+ + " misalignment. The table has been drawn at its minimum possible width." ); + } + else if ( o.oScroll.sXInner !== "" ) + { + _fnLog( o, 1, "The table cannot fit into the current element which will cause column"+ + " misalignment. Increase the sScrollXInner value or remove it to allow automatic"+ + " calculation" ); + } + } + else + { + nScrollBody.style.width = _fnStringToCss( '100%' ); + o.nScrollHead.style.width = _fnStringToCss( '100%' ); + + if ( o.nTFoot !== null ) + { + o.nScrollFoot.style.width = _fnStringToCss( '100%' ); + } + } + + + /* + * 4. Clean up + */ + if ( o.oScroll.sY === "" ) + { + /* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting + * the scrollbar height from the visible display, rather than adding it on. We need to + * set the height in order to sort this. Don't want to do it in any other browsers. + */ + if ( ie67 ) + { + nScrollBody.style.height = _fnStringToCss( o.nTable.offsetHeight+o.oScroll.iBarWidth ); + } + } + + if ( o.oScroll.sY !== "" && o.oScroll.bCollapse ) + { + nScrollBody.style.height = _fnStringToCss( o.oScroll.sY ); + + var iExtra = (o.oScroll.sX !== "" && o.nTable.offsetWidth > nScrollBody.offsetWidth) ? + o.oScroll.iBarWidth : 0; + if ( o.nTable.offsetHeight < nScrollBody.offsetHeight ) + { + nScrollBody.style.height = _fnStringToCss( o.nTable.offsetHeight+iExtra ); + } + } + + /* Finally set the width's of the header and footer tables */ + var iOuterWidth = $(o.nTable).outerWidth(); + nScrollHeadTable.style.width = _fnStringToCss( iOuterWidth ); + nScrollHeadInner.style.width = _fnStringToCss( iOuterWidth ); + + // Figure out if there are scrollbar present - if so then we need a the header and footer to + // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar) + var bScrolling = $(o.nTable).height() > nScrollBody.clientHeight || $(nScrollBody).css('overflow-y') == "scroll"; + nScrollHeadInner.style.paddingRight = bScrolling ? o.oScroll.iBarWidth+"px" : "0px"; + + if ( o.nTFoot !== null ) + { + nScrollFootTable.style.width = _fnStringToCss( iOuterWidth ); + nScrollFootInner.style.width = _fnStringToCss( iOuterWidth ); + nScrollFootInner.style.paddingRight = bScrolling ? o.oScroll.iBarWidth+"px" : "0px"; + } + + /* Adjust the position of the header in case we loose the y-scrollbar */ + $(nScrollBody).scroll(); + + /* If sorting or filtering has occurred, jump the scrolling back to the top */ + if ( o.bSorted || o.bFiltered ) + { + nScrollBody.scrollTop = 0; + } + } + + + /** + * Apply a given function to the display child nodes of an element array (typically + * TD children of TR rows + * @param {function} fn Method to apply to the objects + * @param array {nodes} an1 List of elements to look through for display children + * @param array {nodes} an2 Another list (identical structure to the first) - optional + * @memberof DataTable#oApi + */ + function _fnApplyToChildren( fn, an1, an2 ) + { + var index=0, i=0, iLen=an1.length; + var nNode1, nNode2; + + while ( i < iLen ) + { + nNode1 = an1[i].firstChild; + nNode2 = an2 ? an2[i].firstChild : null; + while ( nNode1 ) + { + if ( nNode1.nodeType === 1 ) + { + if ( an2 ) + { + fn( nNode1, nNode2, index ); + } + else + { + fn( nNode1, index ); + } + index++; + } + nNode1 = nNode1.nextSibling; + nNode2 = an2 ? nNode2.nextSibling : null; + } + i++; + } + } + + /** + * Convert a CSS unit width to pixels (e.g. 2em) + * @param {string} sWidth width to be converted + * @param {node} nParent parent to get the with for (required for relative widths) - optional + * @returns {int} iWidth width in pixels + * @memberof DataTable#oApi + */ + function _fnConvertToWidth ( sWidth, nParent ) + { + if ( !sWidth || sWidth === null || sWidth === '' ) + { + return 0; + } + + if ( !nParent ) + { + nParent = document.body; + } + + var iWidth; + var nTmp = document.createElement( "div" ); + nTmp.style.width = _fnStringToCss( sWidth ); + + nParent.appendChild( nTmp ); + iWidth = nTmp.offsetWidth; + nParent.removeChild( nTmp ); + + return ( iWidth ); + } + + + /** + * Calculate the width of columns for the table + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnCalculateColumnWidths ( oSettings ) + { + var iTableWidth = oSettings.nTable.offsetWidth; + var iUserInputs = 0; + var iTmpWidth; + var iVisibleColumns = 0; + var iColums = oSettings.aoColumns.length; + var i, iIndex, iCorrector, iWidth; + var oHeaders = $('th', oSettings.nTHead); + var widthAttr = oSettings.nTable.getAttribute('width'); + var nWrapper = oSettings.nTable.parentNode; + + /* Convert any user input sizes into pixel sizes */ + for ( i=0 ; itd', nCalcTmp); + } + + /* Apply custom sizing to the cloned header */ + var nThs = _fnGetUniqueThs( oSettings, nTheadClone ); + iCorrector = 0; + for ( i=0 ; i 0 ) + { + oSettings.aoColumns[i].sWidth = _fnStringToCss( iWidth ); + } + iCorrector++; + } + } + + var cssWidth = $(nCalcTmp).css('width'); + oSettings.nTable.style.width = (cssWidth.indexOf('%') !== -1) ? + cssWidth : _fnStringToCss( $(nCalcTmp).outerWidth() ); + nCalcTmp.parentNode.removeChild( nCalcTmp ); + } + + if ( widthAttr ) + { + oSettings.nTable.style.width = _fnStringToCss( widthAttr ); + } + } + + + /** + * Adjust a table's width to take account of scrolling + * @param {object} oSettings dataTables settings object + * @param {node} n table node + * @memberof DataTable#oApi + */ + function _fnScrollingWidthAdjust ( oSettings, n ) + { + if ( oSettings.oScroll.sX === "" && oSettings.oScroll.sY !== "" ) + { + /* When y-scrolling only, we want to remove the width of the scroll bar so the table + * + scroll bar will fit into the area avaialble. + */ + var iOrigWidth = $(n).width(); + n.style.width = _fnStringToCss( $(n).outerWidth()-oSettings.oScroll.iBarWidth ); + } + else if ( oSettings.oScroll.sX !== "" ) + { + /* When x-scrolling both ways, fix the table at it's current size, without adjusting */ + n.style.width = _fnStringToCss( $(n).outerWidth() ); + } + } + + + /** + * Get the widest node + * @param {object} oSettings dataTables settings object + * @param {int} iCol column of interest + * @returns {node} widest table node + * @memberof DataTable#oApi + */ + function _fnGetWidestNode( oSettings, iCol ) + { + var iMaxIndex = _fnGetMaxLenString( oSettings, iCol ); + if ( iMaxIndex < 0 ) + { + return null; + } + + if ( oSettings.aoData[iMaxIndex].nTr === null ) + { + var n = document.createElement('td'); + n.innerHTML = _fnGetCellData( oSettings, iMaxIndex, iCol, '' ); + return n; + } + return _fnGetTdNodes(oSettings, iMaxIndex)[iCol]; + } + + + /** + * Get the maximum strlen for each data column + * @param {object} oSettings dataTables settings object + * @param {int} iCol column of interest + * @returns {string} max string length for each column + * @memberof DataTable#oApi + */ + function _fnGetMaxLenString( oSettings, iCol ) + { + var iMax = -1; + var iMaxIndex = -1; + + for ( var i=0 ; i/g, "" ); + if ( s.length > iMax ) + { + iMax = s.length; + iMaxIndex = i; + } + } + + return iMaxIndex; + } + + + /** + * Append a CSS unit (only if required) to a string + * @param {array} aArray1 first array + * @param {array} aArray2 second array + * @returns {int} 0 if match, 1 if length is different, 2 if no match + * @memberof DataTable#oApi + */ + function _fnStringToCss( s ) + { + if ( s === null ) + { + return "0px"; + } + + if ( typeof s == 'number' ) + { + if ( s < 0 ) + { + return "0px"; + } + return s+"px"; + } + + /* Check if the last character is not 0-9 */ + var c = s.charCodeAt( s.length-1 ); + if (c < 0x30 || c > 0x39) + { + return s; + } + return s+"px"; + } + + + /** + * Get the width of a scroll bar in this browser being used + * @returns {int} width in pixels + * @memberof DataTable#oApi + */ + function _fnScrollBarWidth () + { + var inner = document.createElement('p'); + var style = inner.style; + style.width = "100%"; + style.height = "200px"; + style.padding = "0px"; + + var outer = document.createElement('div'); + style = outer.style; + style.position = "absolute"; + style.top = "0px"; + style.left = "0px"; + style.visibility = "hidden"; + style.width = "200px"; + style.height = "150px"; + style.padding = "0px"; + style.overflow = "hidden"; + outer.appendChild(inner); + + document.body.appendChild(outer); + var w1 = inner.offsetWidth; + outer.style.overflow = 'scroll'; + var w2 = inner.offsetWidth; + if ( w1 == w2 ) + { + w2 = outer.clientWidth; + } + + document.body.removeChild(outer); + return (w1 - w2); + } + + /** + * Change the order of the table + * @param {object} oSettings dataTables settings object + * @param {bool} bApplyClasses optional - should we apply classes or not + * @memberof DataTable#oApi + */ + function _fnSort ( oSettings, bApplyClasses ) + { + var + i, iLen, j, jLen, k, kLen, + sDataType, nTh, + aaSort = [], + aiOrig = [], + oSort = DataTable.ext.oSort, + aoData = oSettings.aoData, + aoColumns = oSettings.aoColumns, + oAria = oSettings.oLanguage.oAria; + + /* No sorting required if server-side or no sorting array */ + if ( !oSettings.oFeatures.bServerSide && + (oSettings.aaSorting.length !== 0 || oSettings.aaSortingFixed !== null) ) + { + aaSort = ( oSettings.aaSortingFixed !== null ) ? + oSettings.aaSortingFixed.concat( oSettings.aaSorting ) : + oSettings.aaSorting.slice(); + + /* If there is a sorting data type, and a function belonging to it, then we need to + * get the data from the developer's function and apply it for this column + */ + for ( i=0 ; i/g, "" ); + nTh = aoColumns[i].nTh; + nTh.removeAttribute('aria-sort'); + nTh.removeAttribute('aria-label'); + + /* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */ + if ( aoColumns[i].bSortable ) + { + if ( aaSort.length > 0 && aaSort[0][0] == i ) + { + nTh.setAttribute('aria-sort', aaSort[0][1]=="asc" ? "ascending" : "descending" ); + + var nextSort = (aoColumns[i].asSorting[ aaSort[0][2]+1 ]) ? + aoColumns[i].asSorting[ aaSort[0][2]+1 ] : aoColumns[i].asSorting[0]; + nTh.setAttribute('aria-label', sTitle+ + (nextSort=="asc" ? oAria.sSortAscending : oAria.sSortDescending) ); + } + else + { + nTh.setAttribute('aria-label', sTitle+ + (aoColumns[i].asSorting[0]=="asc" ? oAria.sSortAscending : oAria.sSortDescending) ); + } + } + else + { + nTh.setAttribute('aria-label', sTitle); + } + } + + /* Tell the draw function that we have sorted the data */ + oSettings.bSorted = true; + $(oSettings.oInstance).trigger('sort', oSettings); + + /* Copy the master data into the draw array and re-draw */ + if ( oSettings.oFeatures.bFilter ) + { + /* _fnFilter() will redraw the table for us */ + _fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 ); + } + else + { + oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); + oSettings._iDisplayStart = 0; /* reset display back to page 0 */ + _fnCalculateEnd( oSettings ); + _fnDraw( oSettings ); + } + } + + + /** + * Attach a sort handler (click) to a node + * @param {object} oSettings dataTables settings object + * @param {node} nNode node to attach the handler to + * @param {int} iDataIndex column sorting index + * @param {function} [fnCallback] callback function + * @memberof DataTable#oApi + */ + function _fnSortAttachListener ( oSettings, nNode, iDataIndex, fnCallback ) + { + _fnBindAction( nNode, {}, function (e) { + /* If the column is not sortable - don't to anything */ + if ( oSettings.aoColumns[iDataIndex].bSortable === false ) + { + return; + } + + /* + * This is a little bit odd I admit... I declare a temporary function inside the scope of + * _fnBuildHead and the click handler in order that the code presented here can be used + * twice - once for when bProcessing is enabled, and another time for when it is + * disabled, as we need to perform slightly different actions. + * Basically the issue here is that the Javascript engine in modern browsers don't + * appear to allow the rendering engine to update the display while it is still executing + * it's thread (well - it does but only after long intervals). This means that the + * 'processing' display doesn't appear for a table sort. To break the js thread up a bit + * I force an execution break by using setTimeout - but this breaks the expected + * thread continuation for the end-developer's point of view (their code would execute + * too early), so we only do it when we absolutely have to. + */ + var fnInnerSorting = function () { + var iColumn, iNextSort; + + /* If the shift key is pressed then we are multiple column sorting */ + if ( e.shiftKey ) + { + /* Are we already doing some kind of sort on this column? */ + var bFound = false; + for ( var i=0 ; i 0 && sCurrentClass.indexOf(sNewClass) == -1 ) + { + /* We need to add a class */ + nTds[i].className = sCurrentClass + " " + sNewClass; + } + } + } + } + + + + /** + * Save the state of a table in a cookie such that the page can be reloaded + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnSaveState ( oSettings ) + { + if ( !oSettings.oFeatures.bStateSave || oSettings.bDestroying ) + { + return; + } + + /* Store the interesting variables */ + var i, iLen, bInfinite=oSettings.oScroll.bInfinite; + var oState = { + "iCreate": new Date().getTime(), + "iStart": (bInfinite ? 0 : oSettings._iDisplayStart), + "iEnd": (bInfinite ? oSettings._iDisplayLength : oSettings._iDisplayEnd), + "iLength": oSettings._iDisplayLength, + "aaSorting": $.extend( true, [], oSettings.aaSorting ), + "oSearch": $.extend( true, {}, oSettings.oPreviousSearch ), + "aoSearchCols": $.extend( true, [], oSettings.aoPreSearchCols ), + "abVisCols": [] + }; + + for ( i=0, iLen=oSettings.aoColumns.length ; i 4096 ) /* Magic 10 for padding */ + { + for ( var i=0, iLen=aCookies.length ; i 4096 ) { + if ( aOldCookies.length === 0 ) { + // Deleted all DT cookies and still not enough space. Can't state save + return; + } + + var old = aOldCookies.pop(); + document.cookie = old.name+"=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path="+ + aParts.join('/') + "/"; + } + } + + document.cookie = sFullCookie; + } + + + /** + * Read an old cookie to get a cookie with an old table state + * @param {string} sName name of the cookie to read + * @returns {string} contents of the cookie - or null if no cookie with that name found + * @memberof DataTable#oApi + */ + function _fnReadCookie ( sName ) + { + var + aParts = window.location.pathname.split('/'), + sNameEQ = sName + '_' + aParts[aParts.length-1].replace(/[\/:]/g,"").toLowerCase() + '=', + sCookieContents = document.cookie.split(';'); + + for( var i=0 ; i=0 ; i-- ) + { + aRet.push( aoStore[i].fn.apply( oSettings.oInstance, aArgs ) ); + } + + if ( sTrigger !== null ) + { + $(oSettings.oInstance).trigger(sTrigger, aArgs); + } + + return aRet; + } + + + /** + * JSON stringify. If JSON.stringify it provided by the browser, json2.js or any other + * library, then we use that as it is fast, safe and accurate. If the function isn't + * available then we need to built it ourselves - the inspiration for this function comes + * from Craig Buckler ( http://www.sitepoint.com/javascript-json-serialization/ ). It is + * not perfect and absolutely should not be used as a replacement to json2.js - but it does + * do what we need, without requiring a dependency for DataTables. + * @param {object} o JSON object to be converted + * @returns {string} JSON string + * @memberof DataTable#oApi + */ + var _fnJsonString = (window.JSON) ? JSON.stringify : function( o ) + { + /* Not an object or array */ + var sType = typeof o; + if (sType !== "object" || o === null) + { + // simple data type + if (sType === "string") + { + o = '"'+o+'"'; + } + return o+""; + } + + /* If object or array, need to recurse over it */ + var + sProp, mValue, + json = [], + bArr = $.isArray(o); + + for (sProp in o) + { + mValue = o[sProp]; + sType = typeof mValue; + + if (sType === "string") + { + mValue = '"'+mValue+'"'; + } + else if (sType === "object" && mValue !== null) + { + mValue = _fnJsonString(mValue); + } + + json.push((bArr ? "" : '"'+sProp+'":') + mValue); + } + + return (bArr ? "[" : "{") + json + (bArr ? "]" : "}"); + }; + + + /** + * From some browsers (specifically IE6/7) we need special handling to work around browser + * bugs - this function is used to detect when these workarounds are needed. + * @param {object} oSettings dataTables settings object + * @memberof DataTable#oApi + */ + function _fnBrowserDetect( oSettings ) + { + /* IE6/7 will oversize a width 100% element inside a scrolling element, to include the + * width of the scrollbar, while other browsers ensure the inner element is contained + * without forcing scrolling + */ + var n = $( + '
      '+ + '
      '+ + '
      '+ + '
      '+ + '
      ')[0]; + + document.body.appendChild( n ); + oSettings.oBrowser.bScrollOversize = $('#DT_BrowserTest', n)[0].offsetWidth === 100 ? true : false; + document.body.removeChild( n ); + } + + + /** + * Perform a jQuery selector action on the table's TR elements (from the tbody) and + * return the resulting jQuery object. + * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on + * @param {object} [oOpts] Optional parameters for modifying the rows to be included + * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter + * criterion ("applied") or all TR elements (i.e. no filter). + * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. + * Can be either 'current', whereby the current sorting of the table is used, or + * 'original' whereby the original order the data was read into the table is used. + * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page + * ("current") or not ("all"). If 'current' is given, then order is assumed to be + * 'current' and filter is 'applied', regardless of what they might be given as. + * @returns {object} jQuery object, filtered by the given selector. + * @dtopt API + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Highlight every second row + * oTable.$('tr:odd').css('backgroundColor', 'blue'); + * } ); + * + * @example + * $(document).ready(function() { + * var oTable = $('#example').dataTable(); + * + * // Filter to rows with 'Webkit' in them, add a background colour and then + * // remove the filter, thus highlighting the 'Webkit' rows only. + * oTable.fnFilter('Webkit'); + * oTable.$('tr', {"filter": "applied"}).css('backgroundColor', 'blue'); + * oTable.fnFilter(''); + * } ); + */ + this.$ = function ( sSelector, oOpts ) + { + var i, iLen, a = [], tr; + var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] ); + var aoData = oSettings.aoData; + var aiDisplay = oSettings.aiDisplay; + var aiDisplayMaster = oSettings.aiDisplayMaster; + + if ( !oOpts ) + { + oOpts = {}; + } + + oOpts = $.extend( {}, { + "filter": "none", // applied + "order": "current", // "original" + "page": "all" // current + }, oOpts ); + + // Current page implies that order=current and fitler=applied, since it is fairly + // senseless otherwise + if ( oOpts.page == 'current' ) + { + for ( i=oSettings._iDisplayStart, iLen=oSettings.fnDisplayEnd() ; i + *
    6. 1D array of data - add a single row with the data provided
    7. + *
    8. 2D array of arrays - add multiple rows in a single call
    9. + *
    10. object - data object when using mData
    11. + *
    12. array of objects - multiple data objects when using mData
    13. + * + * @param {bool} [bRedraw=true] redraw the table or not + * @returns {array} An array of integers, representing the list of indexes in + * aoData ({@link DataTable.models.oSettings}) that have been added to + * the table. + * @dtopt API + * + * @example + * // Global var for counter + * var giCount = 2; + * + * $(document).ready(function() { + * $('#example').dataTable(); + * } ); + * + * function fnClickAddRow() { + * $('#example').dataTable().fnAddData( [ + * giCount+".1", + * giCount+".2", + * giCount+".3", + * giCount+".4" ] + * ); + * + * giCount++; + * } + */ + this.fnAddData = function( mData, bRedraw ) + { + if ( mData.length === 0 ) + { + return []; + } + + var aiReturn = []; + var iTest; + + /* Find settings from table node */ + var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] ); + + /* Check if we want to add multiple rows or not */ + if ( typeof mData[0] === "object" && mData[0] !== null ) + { + for ( var i=0 ; i= oSettings.fnRecordsDisplay() ) + { + oSettings._iDisplayStart -= oSettings._iDisplayLength; + if ( oSettings._iDisplayStart < 0 ) + { + oSettings._iDisplayStart = 0; + } + } + + if ( bRedraw === undefined || bRedraw ) + { + _fnCalculateEnd( oSettings ); + _fnDraw( oSettings ); + } + + return oData; + }; + + + /** + * Restore the table to it's original state in the DOM by removing all of DataTables + * enhancements, alterations to the DOM structure of the table and event listeners. + * @param {boolean} [bRemove=false] Completely remove the table from the DOM + * @dtopt API + * + * @example + * $(document).ready(function() { + * // This example is fairly pointless in reality, but shows how fnDestroy can be used + * var oTable = $('#example').dataTable(); + * oTable.fnDestroy(); + * } ); + */ + this.fnDestroy = function ( bRemove ) + { + var oSettings = _fnSettingsFromNode( this[DataTable.ext.iApiIndex] ); + var nOrig = oSettings.nTableWrapper.parentNode; + var nBody = oSettings.nTBody; + var i, iLen; + + bRemove = (bRemove===undefined) ? false : bRemove; + + /* Flag to note that the table is currently being destroyed - no action should be taken */ + oSettings.bDestroying = true; + + /* Fire off the destroy callbacks for plug-ins etc */ + _fnCallbackFire( oSettings, "aoDestroyCallback", "destroy", [oSettings] ); + + /* If the table is not being removed, restore the hidden columns */ + if ( !bRemove ) + { + for ( i=0, iLen=oSettings.aoColumns.length ; itr>td.'+oSettings.oClasses.sRowEmpty, oSettings.nTable).parent().remove(); + + /* When scrolling we had to break the table up - restore it */ + if ( oSettings.nTable != oSettings.nTHead.parentNode ) + { + $(oSettings.nTable).children('thead').remove(); + oSettings.nTable.appendChild( oSettings.nTHead ); + } + + if ( oSettings.nTFoot && oSettings.nTable != oSettings.nTFoot.parentNode ) + { + $(oSettings.nTable).children('tfoot').remove(); + oSettings.nTable.appendChild( oSettings.nTFoot ); + } + + /* Remove the DataTables generated nodes, events and classes */ + oSettings.nTable.parentNode.removeChild( oSettings.nTable ); + $(oSettings.nTableWrapper).remove(); + + oSettings.aaSorting = []; + oSettings.aaSortingFixed = []; + _fnSortingClasses( oSettings ); + + $(_fnGetTrNodes( oSettings )).removeClass( oSettings.asStripeClasses.join(' ') ); + + $('th, td', oSettings.nTHead).removeClass( [ + oSettings.oClasses.sSortable, + oSettings.oClasses.sSortableAsc, + oSettings.oClasses.sSortableDesc, + oSettings.oClasses.sSortableNone ].join(' ') + ); + if ( oSettings.bJUI ) + { + $('th span.'+oSettings.oClasses.sSortIcon + + ', td span.'+oSettings.oClasses.sSortIcon, oSettings.nTHead).remove(); + + $('th, td', oSettings.nTHead).each( function () { + var jqWrapper = $('div.'+oSettings.oClasses.sSortJUIWrapper, this); + var kids = jqWrapper.contents(); + $(this).append( kids ); + jqWrapper.remove(); + } ); + } + + /* Add the TR elements back into the table in their original order */ + if ( !bRemove && oSettings.nTableReinsertBefore ) + { + nOrig.insertBefore( oSettings.nTable, oSettings.nTableReinsertBefore ); + } + else if ( !bRemove ) + { + nOrig.appendChild( oSettings.nTable ); + } + + for ( i=0, iLen=oSettings.aoData.length ; i
      a",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="
      t
      ",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="
      ",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;be.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="
      ",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="

      ",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b0)for(e=d;e=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*\s*$/g,bz={option:[1,""],legend:[1,"
      ","
      "],thead:[1,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],col:[2,"","
      "],area:[1,"",""],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X
      ","
      "]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1>");try{for(;d1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]===""&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("
      ").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/jQuery/jquery.mobile-1.3.1.js b/misc/PublicationBulletins/Portail-LeHavre/jQuery/jquery.mobile-1.3.1.js new file mode 100755 index 00000000..fec04bc4 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/jQuery/jquery.mobile-1.3.1.js @@ -0,0 +1,11191 @@ +/* +* jQuery Mobile 1.3.1 +* Git HEAD hash: 74b4bec049fd93e4fe40205e6157de16eb64eb46 <> Date: Wed Apr 10 2013 21:57:23 UTC +* http://jquerymobile.com +* +* Copyright 2010, 2013 jQuery Foundation, Inc. and other contributors +* Released under the MIT license. +* http://jquery.org/license +* +*/ + + +(function ( root, doc, factory ) { + if ( typeof define === "function" && define.amd ) { + // AMD. Register as an anonymous module. + define( [ "jquery" ], function ( $ ) { + factory( $, root, doc ); + return $.mobile; + }); + } else { + // Browser globals + factory( root.jQuery, root, doc ); + } +}( this, document, function ( jQuery, window, document, undefined ) { +(function( $ ) { + $.mobile = {}; +}( jQuery )); +(function( $, window, undefined ) { + var nsNormalizeDict = {}; + + // jQuery.mobile configurable options + $.mobile = $.extend($.mobile, { + + // Version of the jQuery Mobile Framework + version: "1.3.1", + + // Namespace used framework-wide for data-attrs. Default is no namespace + ns: "", + + // Define the url parameter used for referencing widget-generated sub-pages. + // Translates to to example.html&ui-page=subpageIdentifier + // hash segment before &ui-page= is used to make Ajax request + subPageUrlKey: "ui-page", + + // Class assigned to page currently in view, and during transitions + activePageClass: "ui-page-active", + + // Class used for "active" button state, from CSS framework + activeBtnClass: "ui-btn-active", + + // Class used for "focus" form element state, from CSS framework + focusClass: "ui-focus", + + // Automatically handle clicks and form submissions through Ajax, when same-domain + ajaxEnabled: true, + + // Automatically load and show pages based on location.hash + hashListeningEnabled: true, + + // disable to prevent jquery from bothering with links + linkBindingEnabled: true, + + // Set default page transition - 'none' for no transitions + defaultPageTransition: "fade", + + // Set maximum window width for transitions to apply - 'false' for no limit + maxTransitionWidth: false, + + // Minimum scroll distance that will be remembered when returning to a page + minScrollBack: 250, + + // DEPRECATED: the following property is no longer in use, but defined until 2.0 to prevent conflicts + touchOverflowEnabled: false, + + // Set default dialog transition - 'none' for no transitions + defaultDialogTransition: "pop", + + // Error response message - appears when an Ajax page request fails + pageLoadErrorMessage: "Error Loading Page", + + // For error messages, which theme does the box uses? + pageLoadErrorMessageTheme: "e", + + // replace calls to window.history.back with phonegaps navigation helper + // where it is provided on the window object + phonegapNavigationEnabled: false, + + //automatically initialize the DOM when it's ready + autoInitializePage: true, + + pushStateEnabled: true, + + // allows users to opt in to ignoring content by marking a parent element as + // data-ignored + ignoreContentEnabled: false, + + // turn of binding to the native orientationchange due to android orientation behavior + orientationChangeEnabled: true, + + buttonMarkup: { + hoverDelay: 200 + }, + + // define the window and the document objects + window: $( window ), + document: $( document ), + + // TODO might be useful upstream in jquery itself ? + keyCode: { + ALT: 18, + BACKSPACE: 8, + CAPS_LOCK: 20, + COMMA: 188, + COMMAND: 91, + COMMAND_LEFT: 91, // COMMAND + COMMAND_RIGHT: 93, + CONTROL: 17, + DELETE: 46, + DOWN: 40, + END: 35, + ENTER: 13, + ESCAPE: 27, + HOME: 36, + INSERT: 45, + LEFT: 37, + MENU: 93, // COMMAND_RIGHT + NUMPAD_ADD: 107, + NUMPAD_DECIMAL: 110, + NUMPAD_DIVIDE: 111, + NUMPAD_ENTER: 108, + NUMPAD_MULTIPLY: 106, + NUMPAD_SUBTRACT: 109, + PAGE_DOWN: 34, + PAGE_UP: 33, + PERIOD: 190, + RIGHT: 39, + SHIFT: 16, + SPACE: 32, + TAB: 9, + UP: 38, + WINDOWS: 91 // COMMAND + }, + + // Place to store various widget extensions + behaviors: {}, + + // Scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value + silentScroll: function( ypos ) { + if ( $.type( ypos ) !== "number" ) { + ypos = $.mobile.defaultHomeScroll; + } + + // prevent scrollstart and scrollstop events + $.event.special.scrollstart.enabled = false; + + setTimeout( function() { + window.scrollTo( 0, ypos ); + $.mobile.document.trigger( "silentscroll", { x: 0, y: ypos }); + }, 20 ); + + setTimeout( function() { + $.event.special.scrollstart.enabled = true; + }, 150 ); + }, + + // Expose our cache for testing purposes. + nsNormalizeDict: nsNormalizeDict, + + // Take a data attribute property, prepend the namespace + // and then camel case the attribute string. Add the result + // to our nsNormalizeDict so we don't have to do this again. + nsNormalize: function( prop ) { + if ( !prop ) { + return; + } + + return nsNormalizeDict[ prop ] || ( nsNormalizeDict[ prop ] = $.camelCase( $.mobile.ns + prop ) ); + }, + + // Find the closest parent with a theme class on it. Note that + // we are not using $.fn.closest() on purpose here because this + // method gets called quite a bit and we need it to be as fast + // as possible. + getInheritedTheme: function( el, defaultTheme ) { + var e = el[ 0 ], + ltr = "", + re = /ui-(bar|body|overlay)-([a-z])\b/, + c, m; + + while ( e ) { + c = e.className || ""; + if ( c && ( m = re.exec( c ) ) && ( ltr = m[ 2 ] ) ) { + // We found a parent with a theme class + // on it so bail from this loop. + break; + } + + e = e.parentNode; + } + + // Return the theme letter we found, if none, return the + // specified default. + + return ltr || defaultTheme || "a"; + }, + + // TODO the following $ and $.fn extensions can/probably should be moved into jquery.mobile.core.helpers + // + // Find the closest javascript page element to gather settings data jsperf test + // http://jsperf.com/single-complex-selector-vs-many-complex-selectors/edit + // possibly naive, but it shows that the parsing overhead for *just* the page selector vs + // the page and dialog selector is negligable. This could probably be speed up by + // doing a similar parent node traversal to the one found in the inherited theme code above + closestPageData: function( $target ) { + return $target + .closest( ':jqmData(role="page"), :jqmData(role="dialog")' ) + .data( "mobile-page" ); + }, + + enhanceable: function( $set ) { + return this.haveParents( $set, "enhance" ); + }, + + hijackable: function( $set ) { + return this.haveParents( $set, "ajax" ); + }, + + haveParents: function( $set, attr ) { + if ( !$.mobile.ignoreContentEnabled ) { + return $set; + } + + var count = $set.length, + $newSet = $(), + e, $element, excluded; + + for ( var i = 0; i < count; i++ ) { + $element = $set.eq( i ); + excluded = false; + e = $set[ i ]; + + while ( e ) { + var c = e.getAttribute ? e.getAttribute( "data-" + $.mobile.ns + attr ) : ""; + + if ( c === "false" ) { + excluded = true; + break; + } + + e = e.parentNode; + } + + if ( !excluded ) { + $newSet = $newSet.add( $element ); + } + } + + return $newSet; + }, + + getScreenHeight: function() { + // Native innerHeight returns more accurate value for this across platforms, + // jQuery version is here as a normalized fallback for platforms like Symbian + return window.innerHeight || $.mobile.window.height(); + } + }, $.mobile ); + + // Mobile version of data and removeData and hasData methods + // ensures all data is set and retrieved using jQuery Mobile's data namespace + $.fn.jqmData = function( prop, value ) { + var result; + if ( typeof prop !== "undefined" ) { + if ( prop ) { + prop = $.mobile.nsNormalize( prop ); + } + + // undefined is permitted as an explicit input for the second param + // in this case it returns the value and does not set it to undefined + if( arguments.length < 2 || value === undefined ){ + result = this.data( prop ); + } else { + result = this.data( prop, value ); + } + } + return result; + }; + + $.jqmData = function( elem, prop, value ) { + var result; + if ( typeof prop !== "undefined" ) { + result = $.data( elem, prop ? $.mobile.nsNormalize( prop ) : prop, value ); + } + return result; + }; + + $.fn.jqmRemoveData = function( prop ) { + return this.removeData( $.mobile.nsNormalize( prop ) ); + }; + + $.jqmRemoveData = function( elem, prop ) { + return $.removeData( elem, $.mobile.nsNormalize( prop ) ); + }; + + $.fn.removeWithDependents = function() { + $.removeWithDependents( this ); + }; + + $.removeWithDependents = function( elem ) { + var $elem = $( elem ); + + ( $elem.jqmData( 'dependents' ) || $() ).remove(); + $elem.remove(); + }; + + $.fn.addDependents = function( newDependents ) { + $.addDependents( $( this ), newDependents ); + }; + + $.addDependents = function( elem, newDependents ) { + var dependents = $( elem ).jqmData( 'dependents' ) || $(); + + $( elem ).jqmData( 'dependents', $.merge( dependents, newDependents ) ); + }; + + // note that this helper doesn't attempt to handle the callback + // or setting of an html element's text, its only purpose is + // to return the html encoded version of the text in all cases. (thus the name) + $.fn.getEncodedText = function() { + return $( "
      " ).text( $( this ).text() ).html(); + }; + + // fluent helper function for the mobile namespaced equivalent + $.fn.jqmEnhanceable = function() { + return $.mobile.enhanceable( this ); + }; + + $.fn.jqmHijackable = function() { + return $.mobile.hijackable( this ); + }; + + // Monkey-patching Sizzle to filter the :jqmData selector + var oldFind = $.find, + jqmDataRE = /:jqmData\(([^)]*)\)/g; + + $.find = function( selector, context, ret, extra ) { + selector = selector.replace( jqmDataRE, "[data-" + ( $.mobile.ns || "" ) + "$1]" ); + + return oldFind.call( this, selector, context, ret, extra ); + }; + + $.extend( $.find, oldFind ); + + $.find.matches = function( expr, set ) { + return $.find( expr, null, null, set ); + }; + + $.find.matchesSelector = function( node, expr ) { + return $.find( expr, null, null, [ node ] ).length > 0; + }; +})( jQuery, this ); + + +/*! + * jQuery UI Widget v1.10.0pre - 2012-11-13 (ff055a0c353c3c8ce6e5bfa07ad7cb03e8885bc5) + * http://jqueryui.com + * + * Copyright 2010, 2013 jQuery Foundation and other contributors + * Released under the MIT license. + * http://jquery.org/license + * + * http://api.jqueryui.com/jQuery.widget/ + */ +(function( $, undefined ) { + +var uuid = 0, + slice = Array.prototype.slice, + _cleanData = $.cleanData; +$.cleanData = function( elems ) { + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { + try { + $( elem ).triggerHandler( "remove" ); + // http://bugs.jquery.com/ticket/8235 + } catch( e ) {} + } + _cleanData( elems ); +}; + +$.widget = function( name, base, prototype ) { + var fullName, existingConstructor, constructor, basePrototype, + namespace = name.split( "." )[ 0 ]; + + name = name.split( "." )[ 1 ]; + fullName = namespace + "-" + name; + + if ( !prototype ) { + prototype = base; + base = $.Widget; + } + + // create selector for plugin + $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + return !!$.data( elem, fullName ); + }; + + $[ namespace ] = $[ namespace ] || {}; + existingConstructor = $[ namespace ][ name ]; + constructor = $[ namespace ][ name ] = function( options, element ) { + // allow instantiation without "new" keyword + if ( !this._createWidget ) { + return new constructor( options, element ); + } + + // allow instantiation without initializing for simple inheritance + // must use "new" keyword (the code above always passes args) + if ( arguments.length ) { + this._createWidget( options, element ); + } + }; + // extend with the existing constructor to carry over any static properties + $.extend( constructor, existingConstructor, { + version: prototype.version, + // copy the object used to create the prototype in case we need to + // redefine the widget later + _proto: $.extend( {}, prototype ), + // track widgets that inherit from this widget in case this widget is + // redefined after a widget inherits from it + _childConstructors: [] + }); + + basePrototype = new base(); + // we need to make the options hash a property directly on the new instance + // otherwise we'll modify the options hash on the prototype that we're + // inheriting from + basePrototype.options = $.widget.extend( {}, basePrototype.options ); + $.each( prototype, function( prop, value ) { + if ( $.isFunction( value ) ) { + prototype[ prop ] = (function() { + var _super = function() { + return base.prototype[ prop ].apply( this, arguments ); + }, + _superApply = function( args ) { + return base.prototype[ prop ].apply( this, args ); + }; + return function() { + var __super = this._super, + __superApply = this._superApply, + returnValue; + + this._super = _super; + this._superApply = _superApply; + + returnValue = value.apply( this, arguments ); + + this._super = __super; + this._superApply = __superApply; + + return returnValue; + }; + })(); + } + }); + constructor.prototype = $.widget.extend( basePrototype, { + // TODO: remove support for widgetEventPrefix + // always use the name + a colon as the prefix, e.g., draggable:start + // don't prefix for widgets that aren't DOM-based + widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name + }, prototype, { + constructor: constructor, + namespace: namespace, + widgetName: name, + widgetFullName: fullName + }); + + // If this widget is being redefined then we need to find all widgets that + // are inheriting from it and redefine all of them so that they inherit from + // the new version of this widget. We're essentially trying to replace one + // level in the prototype chain. + if ( existingConstructor ) { + $.each( existingConstructor._childConstructors, function( i, child ) { + var childPrototype = child.prototype; + + // redefine the child widget using the same prototype that was + // originally used, but inherit from the new version of the base + $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto ); + }); + // remove the list of existing child constructors from the old constructor + // so the old child constructors can be garbage collected + delete existingConstructor._childConstructors; + } else { + base._childConstructors.push( constructor ); + } + + $.widget.bridge( name, constructor ); +}; + +$.widget.extend = function( target ) { + var input = slice.call( arguments, 1 ), + inputIndex = 0, + inputLength = input.length, + key, + value; + for ( ; inputIndex < inputLength; inputIndex++ ) { + for ( key in input[ inputIndex ] ) { + value = input[ inputIndex ][ key ]; + if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { + // Clone objects + if ( $.isPlainObject( value ) ) { + target[ key ] = $.isPlainObject( target[ key ] ) ? + $.widget.extend( {}, target[ key ], value ) : + // Don't extend strings, arrays, etc. with objects + $.widget.extend( {}, value ); + // Copy everything else by reference + } else { + target[ key ] = value; + } + } + } + } + return target; +}; + +$.widget.bridge = function( name, object ) { + var fullName = object.prototype.widgetFullName || name; + $.fn[ name ] = function( options ) { + var isMethodCall = typeof options === "string", + args = slice.call( arguments, 1 ), + returnValue = this; + + // allow multiple hashes to be passed on init + options = !isMethodCall && args.length ? + $.widget.extend.apply( null, [ options ].concat(args) ) : + options; + + if ( isMethodCall ) { + this.each(function() { + var methodValue, + instance = $.data( this, fullName ); + if ( !instance ) { + return $.error( "cannot call methods on " + name + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for " + name + " widget instance" ); + } + methodValue = instance[ options ].apply( instance, args ); + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + }); + } else { + this.each(function() { + var instance = $.data( this, fullName ); + if ( instance ) { + instance.option( options || {} )._init(); + } else { + $.data( this, fullName, new object( options, this ) ); + } + }); + } + + return returnValue; + }; +}; + +$.Widget = function( /* options, element */ ) {}; +$.Widget._childConstructors = []; + +$.Widget.prototype = { + widgetName: "widget", + widgetEventPrefix: "", + defaultElement: "
      ", + options: { + disabled: false, + + // callbacks + create: null + }, + _createWidget: function( options, element ) { + element = $( element || this.defaultElement || this )[ 0 ]; + this.element = $( element ); + this.uuid = uuid++; + this.eventNamespace = "." + this.widgetName + this.uuid; + this.options = $.widget.extend( {}, + this.options, + this._getCreateOptions(), + options ); + + this.bindings = $(); + this.hoverable = $(); + this.focusable = $(); + + if ( element !== this ) { + $.data( element, this.widgetFullName, this ); + this._on( true, this.element, { + remove: function( event ) { + if ( event.target === element ) { + this.destroy(); + } + } + }); + this.document = $( element.style ? + // element within the document + element.ownerDocument : + // element is window or document + element.document || element ); + this.window = $( this.document[0].defaultView || this.document[0].parentWindow ); + } + + this._create(); + this._trigger( "create", null, this._getCreateEventData() ); + this._init(); + }, + _getCreateOptions: $.noop, + _getCreateEventData: $.noop, + _create: $.noop, + _init: $.noop, + + destroy: function() { + this._destroy(); + // we can probably remove the unbind calls in 2.0 + // all event bindings should go through this._on() + this.element + .unbind( this.eventNamespace ) + // 1.9 BC for #7810 + // TODO remove dual storage + .removeData( this.widgetName ) + .removeData( this.widgetFullName ) + // support: jquery <1.6.3 + // http://bugs.jquery.com/ticket/9413 + .removeData( $.camelCase( this.widgetFullName ) ); + this.widget() + .unbind( this.eventNamespace ) + .removeAttr( "aria-disabled" ) + .removeClass( + this.widgetFullName + "-disabled " + + "ui-state-disabled" ); + + // clean up events and states + this.bindings.unbind( this.eventNamespace ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + }, + _destroy: $.noop, + + widget: function() { + return this.element; + }, + + option: function( key, value ) { + var options = key, + parts, + curOption, + i; + + if ( arguments.length === 0 ) { + // don't return a reference to the internal hash + return $.widget.extend( {}, this.options ); + } + + if ( typeof key === "string" ) { + // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } + options = {}; + parts = key.split( "." ); + key = parts.shift(); + if ( parts.length ) { + curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); + for ( i = 0; i < parts.length - 1; i++ ) { + curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; + curOption = curOption[ parts[ i ] ]; + } + key = parts.pop(); + if ( value === undefined ) { + return curOption[ key ] === undefined ? null : curOption[ key ]; + } + curOption[ key ] = value; + } else { + if ( value === undefined ) { + return this.options[ key ] === undefined ? null : this.options[ key ]; + } + options[ key ] = value; + } + } + + this._setOptions( options ); + + return this; + }, + _setOptions: function( options ) { + var key; + + for ( key in options ) { + this._setOption( key, options[ key ] ); + } + + return this; + }, + _setOption: function( key, value ) { + this.options[ key ] = value; + + if ( key === "disabled" ) { + this.widget() + .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value ) + .attr( "aria-disabled", value ); + this.hoverable.removeClass( "ui-state-hover" ); + this.focusable.removeClass( "ui-state-focus" ); + } + + return this; + }, + + enable: function() { + return this._setOption( "disabled", false ); + }, + disable: function() { + return this._setOption( "disabled", true ); + }, + + _on: function( suppressDisabledCheck, element, handlers ) { + var delegateElement, + instance = this; + + // no suppressDisabledCheck flag, shuffle arguments + if ( typeof suppressDisabledCheck !== "boolean" ) { + handlers = element; + element = suppressDisabledCheck; + suppressDisabledCheck = false; + } + + // no element argument, shuffle and use this.element + if ( !handlers ) { + handlers = element; + element = this.element; + delegateElement = this.widget(); + } else { + // accept selectors, DOM elements + element = delegateElement = $( element ); + this.bindings = this.bindings.add( element ); + } + + $.each( handlers, function( event, handler ) { + function handlerProxy() { + // allow widgets to customize the disabled handling + // - disabled as an array instead of boolean + // - disabled class as method for disabling individual parts + if ( !suppressDisabledCheck && + ( instance.options.disabled === true || + $( this ).hasClass( "ui-state-disabled" ) ) ) { + return; + } + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + + // copy the guid so direct unbinding works + if ( typeof handler !== "string" ) { + handlerProxy.guid = handler.guid = + handler.guid || handlerProxy.guid || $.guid++; + } + + var match = event.match( /^(\w+)\s*(.*)$/ ), + eventName = match[1] + instance.eventNamespace, + selector = match[2]; + if ( selector ) { + delegateElement.delegate( selector, eventName, handlerProxy ); + } else { + element.bind( eventName, handlerProxy ); + } + }); + }, + + _off: function( element, eventName ) { + eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace; + element.unbind( eventName ).undelegate( eventName ); + }, + + _delay: function( handler, delay ) { + function handlerProxy() { + return ( typeof handler === "string" ? instance[ handler ] : handler ) + .apply( instance, arguments ); + } + var instance = this; + return setTimeout( handlerProxy, delay || 0 ); + }, + + _hoverable: function( element ) { + this.hoverable = this.hoverable.add( element ); + this._on( element, { + mouseenter: function( event ) { + $( event.currentTarget ).addClass( "ui-state-hover" ); + }, + mouseleave: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-hover" ); + } + }); + }, + + _focusable: function( element ) { + this.focusable = this.focusable.add( element ); + this._on( element, { + focusin: function( event ) { + $( event.currentTarget ).addClass( "ui-state-focus" ); + }, + focusout: function( event ) { + $( event.currentTarget ).removeClass( "ui-state-focus" ); + } + }); + }, + + _trigger: function( type, event, data ) { + var prop, orig, + callback = this.options[ type ]; + + data = data || {}; + event = $.Event( event ); + event.type = ( type === this.widgetEventPrefix ? + type : + this.widgetEventPrefix + type ).toLowerCase(); + // the original event may come from any element + // so we need to reset the target on the new event + event.target = this.element[ 0 ]; + + // copy original event properties over to the new event + orig = event.originalEvent; + if ( orig ) { + for ( prop in orig ) { + if ( !( prop in event ) ) { + event[ prop ] = orig[ prop ]; + } + } + } + + this.element.trigger( event, data ); + return !( $.isFunction( callback ) && + callback.apply( this.element[0], [ event ].concat( data ) ) === false || + event.isDefaultPrevented() ); + } +}; + +$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { + $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { + if ( typeof options === "string" ) { + options = { effect: options }; + } + var hasOptions, + effectName = !options ? + method : + options === true || typeof options === "number" ? + defaultEffect : + options.effect || defaultEffect; + options = options || {}; + if ( typeof options === "number" ) { + options = { duration: options }; + } + hasOptions = !$.isEmptyObject( options ); + options.complete = callback; + if ( options.delay ) { + element.delay( options.delay ); + } + if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { + element[ method ]( options ); + } else if ( effectName !== method && element[ effectName ] ) { + element[ effectName ]( options.duration, options.easing, callback ); + } else { + element.queue(function( next ) { + $( this )[ method ](); + if ( callback ) { + callback.call( element[ 0 ] ); + } + next(); + }); + } + }; +}); + +})( jQuery ); + +(function( $, undefined ) { + +$.widget( "mobile.widget", { + // decorate the parent _createWidget to trigger `widgetinit` for users + // who wish to do post post `widgetcreate` alterations/additions + // + // TODO create a pull request for jquery ui to trigger this event + // in the original _createWidget + _createWidget: function() { + $.Widget.prototype._createWidget.apply( this, arguments ); + this._trigger( 'init' ); + }, + + _getCreateOptions: function() { + + var elem = this.element, + options = {}; + + $.each( this.options, function( option ) { + + var value = elem.jqmData( option.replace( /[A-Z]/g, function( c ) { + return "-" + c.toLowerCase(); + }) + ); + + if ( value !== undefined ) { + options[ option ] = value; + } + }); + + return options; + }, + + enhanceWithin: function( target, useKeepNative ) { + this.enhance( $( this.options.initSelector, $( target )), useKeepNative ); + }, + + enhance: function( targets, useKeepNative ) { + var page, keepNative, $widgetElements = $( targets ), self = this; + + // if ignoreContentEnabled is set to true the framework should + // only enhance the selected elements when they do NOT have a + // parent with the data-namespace-ignore attribute + $widgetElements = $.mobile.enhanceable( $widgetElements ); + + if ( useKeepNative && $widgetElements.length ) { + // TODO remove dependency on the page widget for the keepNative. + // Currently the keepNative value is defined on the page prototype so + // the method is as well + page = $.mobile.closestPageData( $widgetElements ); + keepNative = ( page && page.keepNativeSelector()) || ""; + + $widgetElements = $widgetElements.not( keepNative ); + } + + $widgetElements[ this.widgetName ](); + }, + + raise: function( msg ) { + throw "Widget [" + this.widgetName + "]: " + msg; + } +}); + +})( jQuery ); + + +(function( $, window ) { + // DEPRECATED + // NOTE global mobile object settings + $.extend( $.mobile, { + // DEPRECATED Should the text be visble in the loading message? + loadingMessageTextVisible: undefined, + + // DEPRECATED When the text is visible, what theme does the loading box use? + loadingMessageTheme: undefined, + + // DEPRECATED default message setting + loadingMessage: undefined, + + // DEPRECATED + // Turn on/off page loading message. Theme doubles as an object argument + // with the following shape: { theme: '', text: '', html: '', textVisible: '' } + // NOTE that the $.mobile.loading* settings and params past the first are deprecated + showPageLoadingMsg: function( theme, msgText, textonly ) { + $.mobile.loading( 'show', theme, msgText, textonly ); + }, + + // DEPRECATED + hidePageLoadingMsg: function() { + $.mobile.loading( 'hide' ); + }, + + loading: function() { + this.loaderWidget.loader.apply( this.loaderWidget, arguments ); + } + }); + + // TODO move loader class down into the widget settings + var loaderClass = "ui-loader", $html = $( "html" ), $window = $.mobile.window; + + $.widget( "mobile.loader", { + // NOTE if the global config settings are defined they will override these + // options + options: { + // the theme for the loading message + theme: "a", + + // whether the text in the loading message is shown + textVisible: false, + + // custom html for the inner content of the loading message + html: "", + + // the text to be displayed when the popup is shown + text: "loading" + }, + + defaultHtml: "
      " + + "" + + "

      " + + "
      ", + + // For non-fixed supportin browsers. Position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top + fakeFixLoader: function() { + var activeBtn = $( "." + $.mobile.activeBtnClass ).first(); + + this.element + .css({ + top: $.support.scrollTop && $window.scrollTop() + $window.height() / 2 || + activeBtn.length && activeBtn.offset().top || 100 + }); + }, + + // check position of loader to see if it appears to be "fixed" to center + // if not, use abs positioning + checkLoaderPosition: function() { + var offset = this.element.offset(), + scrollTop = $window.scrollTop(), + screenHeight = $.mobile.getScreenHeight(); + + if ( offset.top < scrollTop || ( offset.top - scrollTop ) > screenHeight ) { + this.element.addClass( "ui-loader-fakefix" ); + this.fakeFixLoader(); + $window + .unbind( "scroll", this.checkLoaderPosition ) + .bind( "scroll", $.proxy( this.fakeFixLoader, this ) ); + } + }, + + resetHtml: function() { + this.element.html( $( this.defaultHtml ).html() ); + }, + + // Turn on/off page loading message. Theme doubles as an object argument + // with the following shape: { theme: '', text: '', html: '', textVisible: '' } + // NOTE that the $.mobile.loading* settings and params past the first are deprecated + // TODO sweet jesus we need to break some of this out + show: function( theme, msgText, textonly ) { + var textVisible, message, $header, loadSettings; + + this.resetHtml(); + + // use the prototype options so that people can set them globally at + // mobile init. Consistency, it's what's for dinner + if ( $.type(theme) === "object" ) { + loadSettings = $.extend( {}, this.options, theme ); + + // prefer object property from the param then the old theme setting + theme = loadSettings.theme || $.mobile.loadingMessageTheme; + } else { + loadSettings = this.options; + + // here we prefer the them value passed as a string argument, then + // we prefer the global option because we can't use undefined default + // prototype options, then the prototype option + theme = theme || $.mobile.loadingMessageTheme || loadSettings.theme; + } + + // set the message text, prefer the param, then the settings object + // then loading message + message = msgText || $.mobile.loadingMessage || loadSettings.text; + + // prepare the dom + $html.addClass( "ui-loading" ); + + if ( $.mobile.loadingMessage !== false || loadSettings.html ) { + // boolean values require a bit more work :P, supports object properties + // and old settings + if ( $.mobile.loadingMessageTextVisible !== undefined ) { + textVisible = $.mobile.loadingMessageTextVisible; + } else { + textVisible = loadSettings.textVisible; + } + + // add the proper css given the options (theme, text, etc) + // Force text visibility if the second argument was supplied, or + // if the text was explicitly set in the object args + this.element.attr("class", loaderClass + + " ui-corner-all ui-body-" + theme + + " ui-loader-" + ( textVisible || msgText || theme.text ? "verbose" : "default" ) + + ( loadSettings.textonly || textonly ? " ui-loader-textonly" : "" ) ); + + // TODO verify that jquery.fn.html is ok to use in both cases here + // this might be overly defensive in preventing unknowing xss + // if the html attribute is defined on the loading settings, use that + // otherwise use the fallbacks from above + if ( loadSettings.html ) { + this.element.html( loadSettings.html ); + } else { + this.element.find( "h1" ).text( message ); + } + + // attach the loader to the DOM + this.element.appendTo( $.mobile.pageContainer ); + + // check that the loader is visible + this.checkLoaderPosition(); + + // on scroll check the loader position + $window.bind( "scroll", $.proxy( this.checkLoaderPosition, this ) ); + } + }, + + hide: function() { + $html.removeClass( "ui-loading" ); + + if ( $.mobile.loadingMessage ) { + this.element.removeClass( "ui-loader-fakefix" ); + } + + $.mobile.window.unbind( "scroll", this.fakeFixLoader ); + $.mobile.window.unbind( "scroll", this.checkLoaderPosition ); + } + }); + + $window.bind( 'pagecontainercreate', function() { + $.mobile.loaderWidget = $.mobile.loaderWidget || $( $.mobile.loader.prototype.defaultHtml ).loader(); + }); +})(jQuery, this); + + +// Script: jQuery hashchange event +// +// *Version: 1.3, Last updated: 7/21/2010* +// +// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/ +// GitHub - http://github.com/cowboy/jquery-hashchange/ +// Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js +// (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped) +// +// About: License +// +// Copyright (c) 2010 "Cowboy" Ben Alman, +// Dual licensed under the MIT and GPL licenses. +// http://benalman.com/about/license/ +// +// About: Examples +// +// These working examples, complete with fully commented code, illustrate a few +// ways in which this plugin can be used. +// +// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/ +// document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/ +// +// About: Support and Testing +// +// Information about what version or versions of jQuery this plugin has been +// tested with, what browsers it has been tested in, and where the unit tests +// reside (so you can test it yourself). +// +// jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2 +// Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5, +// Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5. +// Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/ +// +// About: Known issues +// +// While this jQuery hashchange event implementation is quite stable and +// robust, there are a few unfortunate browser bugs surrounding expected +// hashchange event-based behaviors, independent of any JavaScript +// window.onhashchange abstraction. See the following examples for more +// information: +// +// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/ +// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/ +// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/ +// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/ +// +// Also note that should a browser natively support the window.onhashchange +// event, but not report that it does, the fallback polling loop will be used. +// +// About: Release History +// +// 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more +// "removable" for mobile-only development. Added IE6/7 document.title +// support. Attempted to make Iframe as hidden as possible by using +// techniques from http://www.paciellogroup.com/blog/?p=604. Added +// support for the "shortcut" format $(window).hashchange( fn ) and +// $(window).hashchange() like jQuery provides for built-in events. +// Renamed jQuery.hashchangeDelay to and +// lowered its default value to 50. Added +// and properties plus document-domain.html +// file to address access denied issues when setting document.domain in +// IE6/7. +// 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin +// from a page on another domain would cause an error in Safari 4. Also, +// IE6/7 Iframe is now inserted after the body (this actually works), +// which prevents the page from scrolling when the event is first bound. +// Event can also now be bound before DOM ready, but it won't be usable +// before then in IE6/7. +// 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug +// where browser version is incorrectly reported as 8.0, despite +// inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag. +// 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special +// window.onhashchange functionality into a separate plugin for users +// who want just the basic event & back button support, without all the +// extra awesomeness that BBQ provides. This plugin will be included as +// part of jQuery BBQ, but also be available separately. + +(function( $, window, undefined ) { + // Reused string. + var str_hashchange = 'hashchange', + + // Method / object references. + doc = document, + fake_onhashchange, + special = $.event.special, + + // Does the browser support window.onhashchange? Note that IE8 running in + // IE7 compatibility mode reports true for 'onhashchange' in window, even + // though the event isn't supported, so also test document.documentMode. + doc_mode = doc.documentMode, + supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 ); + + // Get location.hash (or what you'd expect location.hash to be) sans any + // leading #. Thanks for making this necessary, Firefox! + function get_fragment( url ) { + url = url || location.href; + return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' ); + }; + + // Method: jQuery.fn.hashchange + // + // Bind a handler to the window.onhashchange event or trigger all bound + // window.onhashchange event handlers. This behavior is consistent with + // jQuery's built-in event handlers. + // + // Usage: + // + // > jQuery(window).hashchange( [ handler ] ); + // + // Arguments: + // + // handler - (Function) Optional handler to be bound to the hashchange + // event. This is a "shortcut" for the more verbose form: + // jQuery(window).bind( 'hashchange', handler ). If handler is omitted, + // all bound window.onhashchange event handlers will be triggered. This + // is a shortcut for the more verbose + // jQuery(window).trigger( 'hashchange' ). These forms are described in + // the section. + // + // Returns: + // + // (jQuery) The initial jQuery collection of elements. + + // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and + // $(elem).hashchange() for triggering, like jQuery does for built-in events. + $.fn[ str_hashchange ] = function( fn ) { + return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange ); + }; + + // Property: jQuery.fn.hashchange.delay + // + // The numeric interval (in milliseconds) at which the + // polling loop executes. Defaults to 50. + + // Property: jQuery.fn.hashchange.domain + // + // If you're setting document.domain in your JavaScript, and you want hash + // history to work in IE6/7, not only must this property be set, but you must + // also set document.domain BEFORE jQuery is loaded into the page. This + // property is only applicable if you are supporting IE6/7 (or IE8 operating + // in "IE7 compatibility" mode). + // + // In addition, the property must be set to the + // path of the included "document-domain.html" file, which can be renamed or + // modified if necessary (note that the document.domain specified must be the + // same in both your main JavaScript as well as in this file). + // + // Usage: + // + // jQuery.fn.hashchange.domain = document.domain; + + // Property: jQuery.fn.hashchange.src + // + // If, for some reason, you need to specify an Iframe src file (for example, + // when setting document.domain as in ), you can + // do so using this property. Note that when using this property, history + // won't be recorded in IE6/7 until the Iframe src file loads. This property + // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7 + // compatibility" mode). + // + // Usage: + // + // jQuery.fn.hashchange.src = 'path/to/file.html'; + + $.fn[ str_hashchange ].delay = 50; + /* + $.fn[ str_hashchange ].domain = null; + $.fn[ str_hashchange ].src = null; + */ + + // Event: hashchange event + // + // Fired when location.hash changes. In browsers that support it, the native + // HTML5 window.onhashchange event is used, otherwise a polling loop is + // initialized, running every milliseconds to + // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7 + // compatibility" mode), a hidden Iframe is created to allow the back button + // and hash-based history to work. + // + // Usage as described in : + // + // > // Bind an event handler. + // > jQuery(window).hashchange( function(e) { + // > var hash = location.hash; + // > ... + // > }); + // > + // > // Manually trigger the event handler. + // > jQuery(window).hashchange(); + // + // A more verbose usage that allows for event namespacing: + // + // > // Bind an event handler. + // > jQuery(window).bind( 'hashchange', function(e) { + // > var hash = location.hash; + // > ... + // > }); + // > + // > // Manually trigger the event handler. + // > jQuery(window).trigger( 'hashchange' ); + // + // Additional Notes: + // + // * The polling loop and Iframe are not created until at least one handler + // is actually bound to the 'hashchange' event. + // * If you need the bound handler(s) to execute immediately, in cases where + // a location.hash exists on page load, via bookmark or page refresh for + // example, use jQuery(window).hashchange() or the more verbose + // jQuery(window).trigger( 'hashchange' ). + // * The event can be bound before DOM ready, but since it won't be usable + // before then in IE6/7 (due to the necessary Iframe), recommended usage is + // to bind it inside a DOM ready handler. + + // Override existing $.event.special.hashchange methods (allowing this plugin + // to be defined after jQuery BBQ in BBQ's source code). + special[ str_hashchange ] = $.extend( special[ str_hashchange ], { + + // Called only when the first 'hashchange' event is bound to window. + setup: function() { + // If window.onhashchange is supported natively, there's nothing to do.. + if ( supports_onhashchange ) { return false; } + + // Otherwise, we need to create our own. And we don't want to call this + // until the user binds to the event, just in case they never do, since it + // will create a polling loop and possibly even a hidden Iframe. + $( fake_onhashchange.start ); + }, + + // Called only when the last 'hashchange' event is unbound from window. + teardown: function() { + // If window.onhashchange is supported natively, there's nothing to do.. + if ( supports_onhashchange ) { return false; } + + // Otherwise, we need to stop ours (if possible). + $( fake_onhashchange.stop ); + } + + }); + + // fake_onhashchange does all the work of triggering the window.onhashchange + // event for browsers that don't natively support it, including creating a + // polling loop to watch for hash changes and in IE 6/7 creating a hidden + // Iframe to enable back and forward. + fake_onhashchange = (function() { + var self = {}, + timeout_id, + + // Remember the initial hash so it doesn't get triggered immediately. + last_hash = get_fragment(), + + fn_retval = function( val ) { return val; }, + history_set = fn_retval, + history_get = fn_retval; + + // Start the polling loop. + self.start = function() { + timeout_id || poll(); + }; + + // Stop the polling loop. + self.stop = function() { + timeout_id && clearTimeout( timeout_id ); + timeout_id = undefined; + }; + + // This polling loop checks every $.fn.hashchange.delay milliseconds to see + // if location.hash has changed, and triggers the 'hashchange' event on + // window when necessary. + function poll() { + var hash = get_fragment(), + history_hash = history_get( last_hash ); + + if ( hash !== last_hash ) { + history_set( last_hash = hash, history_hash ); + + $(window).trigger( str_hashchange ); + + } else if ( history_hash !== last_hash ) { + location.href = location.href.replace( /#.*/, '' ) + history_hash; + } + + timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay ); + }; + + // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv + // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + window.attachEvent && !window.addEventListener && !supports_onhashchange && (function() { + // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8 + // when running in "IE7 compatibility" mode. + + var iframe, + iframe_src; + + // When the event is bound and polling starts in IE 6/7, create a hidden + // Iframe for history handling. + self.start = function() { + if ( !iframe ) { + iframe_src = $.fn[ str_hashchange ].src; + iframe_src = iframe_src && iframe_src + get_fragment(); + + // Create hidden Iframe. Attempt to make Iframe as hidden as possible + // by using techniques from http://www.paciellogroup.com/blog/?p=604. + iframe = $('':"");inst._keyEvent=false;return html},_generateMonthYearHeader:function(inst,drawMonth,drawYear,minDate,maxDate,selectedDate,secondary,monthNames,monthNamesShort){minDate=(inst.rangeStart&&minDate&&selectedDate "}else{var inMinYear=(minDate&&minDate.getFullYear()==drawYear);var inMaxYear=(maxDate&&maxDate.getFullYear()==drawYear);monthHtml+='"}if(!showMonthAfterYear){html+=monthHtml+((secondary||changeMonth||changeYear)&&(!(changeMonth&&changeYear))?" ":"")}if(secondary||!changeYear){html+=''+drawYear+""}else{var years=this._get(inst,"yearRange").split(":");var year=0;var endYear=0;if(years.length!=2){year=drawYear-10;endYear=drawYear+10}else{if(years[0].charAt(0)=="+"||years[0].charAt(0)=="-"){year=drawYear+parseInt(years[0],10);endYear=drawYear+parseInt(years[1],10)}else{year=parseInt(years[0],10);endYear=parseInt(years[1],10)}}year=(minDate?Math.max(year,minDate.getFullYear()):year);endYear=(maxDate?Math.min(endYear,maxDate.getFullYear()):endYear);html+='"}if(showMonthAfterYear){html+=(secondary||changeMonth||changeYear?" ":"")+monthHtml}html+="
      ";return html},_adjustInstDate:function(inst,offset,period){var year=inst.drawYear+(period=="Y"?offset:0);var month=inst.drawMonth+(period=="M"?offset:0);var day=Math.min(inst.selectedDay,this._getDaysInMonth(year,month))+(period=="D"?offset:0);var date=this._daylightSavingAdjust(new Date(year,month,day));var minDate=this._getMinMaxDate(inst,"min",true);var maxDate=this._getMinMaxDate(inst,"max");date=(minDate&&datemaxDate?maxDate:date);inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear();if(period=="M"||period=="Y"){this._notifyChange(inst)}},_notifyChange:function(inst){var onChange=this._get(inst,"onChangeMonthYear");if(onChange){onChange.apply((inst.input?inst.input[0]:null),[inst.selectedYear,inst.selectedMonth+1,inst])}},_getNumberOfMonths:function(inst){var numMonths=this._get(inst,"numberOfMonths");return(numMonths==null?[1,1]:(typeof numMonths=="number"?[1,numMonths]:numMonths))},_getMinMaxDate:function(inst,minMax,checkRange){var date=this._determineDate(this._get(inst,minMax+"Date"),null);return(!checkRange||!inst.rangeStart?date:(!date||inst.rangeStart>date?inst.rangeStart:date))},_getDaysInMonth:function(year,month){return 32-new Date(year,month,32).getDate()},_getFirstDayOfMonth:function(year,month){return new Date(year,month,1).getDay()},_canAdjustMonth:function(inst,offset,curYear,curMonth){var numMonths=this._getNumberOfMonths(inst);var date=this._daylightSavingAdjust(new Date(curYear,curMonth+(offset<0?offset:numMonths[1]),1));if(offset<0){date.setDate(this._getDaysInMonth(date.getFullYear(),date.getMonth()))}return this._isInRange(inst,date)},_isInRange:function(inst,date){var newMinDate=(!inst.rangeStart?null:this._daylightSavingAdjust(new Date(inst.selectedYear,inst.selectedMonth,inst.selectedDay)));newMinDate=(newMinDate&&inst.rangeStart=minDate)&&(!maxDate||date<=maxDate))},_getFormatConfig:function(inst){var shortYearCutoff=this._get(inst,"shortYearCutoff");shortYearCutoff=(typeof shortYearCutoff!="string"?shortYearCutoff:new Date().getFullYear()%100+parseInt(shortYearCutoff,10));return{shortYearCutoff:shortYearCutoff,dayNamesShort:this._get(inst,"dayNamesShort"),dayNames:this._get(inst,"dayNames"),monthNamesShort:this._get(inst,"monthNamesShort"),monthNames:this._get(inst,"monthNames")}},_formatDate:function(inst,day,month,year){if(!day){inst.currentDay=inst.selectedDay;inst.currentMonth=inst.selectedMonth;inst.currentYear=inst.selectedYear}var date=(day?(typeof day=="object"?day:this._daylightSavingAdjust(new Date(year,month,day))):this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));return this.formatDate(this._get(inst,"dateFormat"),date,this._getFormatConfig(inst))}});function extendRemove(target,props){$.extend(target,props);for(var name in props){if(props[name]==null||props[name]==undefined){target[name]=props[name]}}return target}function isArray(a){return(a&&(($.browser.safari&&typeof a=="object"&&a.length)||(a.constructor&&a.constructor.toString().match(/\Array\(\)/))))}$.fn.datepicker=function(options){if(!$.datepicker.initialized){$(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv);$.datepicker.initialized=true}var otherArgs=Array.prototype.slice.call(arguments,1);if(typeof options=="string"&&(options=="isDisabled"||options=="getDate")){return $.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this[0]].concat(otherArgs))}if(options=="option"&&arguments.length==2&&typeof arguments[1]=="string"){return $.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this[0]].concat(otherArgs))}return this.each(function(){typeof options=="string"?$.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this].concat(otherArgs)):$.datepicker._attachDatepicker(this,options)})};$.datepicker=new Datepicker();$.datepicker.initialized=false;$.datepicker.uuid=new Date().getTime();$.datepicker.version="1.7.2";window.DP_jQuery=$})(jQuery);;/* + * jQuery UI Progressbar 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Progressbar + * + * Depends: + * ui.core.js + */ +(function(a){a.widget("ui.progressbar",{_init:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this._valueMin(),"aria-valuemax":this._valueMax(),"aria-valuenow":this._value()});this.valueDiv=a('
      ').appendTo(this.element);this._refreshValue()},destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow").removeData("progressbar").unbind(".progressbar");this.valueDiv.remove();a.widget.prototype.destroy.apply(this,arguments)},value:function(b){if(b===undefined){return this._value()}this._setData("value",b);return this},_setData:function(b,c){switch(b){case"value":this.options.value=c;this._refreshValue();this._trigger("change",null,{});break}a.widget.prototype._setData.apply(this,arguments)},_value:function(){var b=this.options.value;if(bthis._valueMax()){b=this._valueMax()}return b},_valueMin:function(){var b=0;return b},_valueMax:function(){var b=100;return b},_refreshValue:function(){var b=this.value();this.valueDiv[b==this._valueMax()?"addClass":"removeClass"]("ui-corner-right");this.valueDiv.width(b+"%");this.element.attr("aria-valuenow",b)}});a.extend(a.ui.progressbar,{version:"1.7.2",defaults:{value:0}})})(jQuery);;/* + * jQuery UI Effects 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/ + */ +jQuery.effects||(function(d){d.effects={version:"1.7.2",save:function(g,h){for(var f=0;f
      ');var j=f.parent();if(f.css("position")=="static"){j.css({position:"relative"});f.css({position:"relative"})}else{var i=f.css("top");if(isNaN(parseInt(i,10))){i="auto"}var h=f.css("left");if(isNaN(parseInt(h,10))){h="auto"}j.css({position:f.css("position"),top:i,left:h,zIndex:f.css("z-index")}).show();f.css({position:"relative",top:0,left:0})}j.css(g);return j},removeWrapper:function(f){if(f.parent().is(".ui-effects-wrapper")){return f.parent().replaceWith(f)}return f},setTransition:function(g,i,f,h){h=h||{};d.each(i,function(k,j){unit=g.cssUnit(j);if(unit[0]>0){h[j]=unit[0]*f+unit[1]}});return h},animateClass:function(h,i,k,j){var f=(typeof k=="function"?k:(j?j:null));var g=(typeof k=="string"?k:null);return this.each(function(){var q={};var o=d(this);var p=o.attr("style")||"";if(typeof p=="object"){p=p.cssText}if(h.toggle){o.hasClass(h.toggle)?h.remove=h.toggle:h.add=h.toggle}var l=d.extend({},(document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle));if(h.add){o.addClass(h.add)}if(h.remove){o.removeClass(h.remove)}var m=d.extend({},(document.defaultView?document.defaultView.getComputedStyle(this,null):this.currentStyle));if(h.add){o.removeClass(h.add)}if(h.remove){o.addClass(h.remove)}for(var r in m){if(typeof m[r]!="function"&&m[r]&&r.indexOf("Moz")==-1&&r.indexOf("length")==-1&&m[r]!=l[r]&&(r.match(/color/i)||(!r.match(/color/i)&&!isNaN(parseInt(m[r],10))))&&(l.position!="static"||(l.position=="static"&&!r.match(/left|top|bottom|right/)))){q[r]=m[r]}}o.animate(q,i,g,function(){if(typeof d(this).attr("style")=="object"){d(this).attr("style")["cssText"]="";d(this).attr("style")["cssText"]=p}else{d(this).attr("style",p)}if(h.add){d(this).addClass(h.add)}if(h.remove){d(this).removeClass(h.remove)}if(f){f.apply(this,arguments)}})})}};function c(g,f){var i=g[1]&&g[1].constructor==Object?g[1]:{};if(f){i.mode=f}var h=g[1]&&g[1].constructor!=Object?g[1]:(i.duration?i.duration:g[2]);h=d.fx.off?0:typeof h==="number"?h:d.fx.speeds[h]||d.fx.speeds._default;var j=i.callback||(d.isFunction(g[1])&&g[1])||(d.isFunction(g[2])&&g[2])||(d.isFunction(g[3])&&g[3]);return[g[0],i,h,j]}d.fn.extend({_show:d.fn.show,_hide:d.fn.hide,__toggle:d.fn.toggle,_addClass:d.fn.addClass,_removeClass:d.fn.removeClass,_toggleClass:d.fn.toggleClass,effect:function(g,f,h,i){return d.effects[g]?d.effects[g].call(this,{method:g,options:f||{},duration:h,callback:i}):null},show:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))){return this._show.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"show"))}},hide:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))){return this._hide.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"hide"))}},toggle:function(){if(!arguments[0]||(arguments[0].constructor==Number||(/(slow|normal|fast)/).test(arguments[0]))||(d.isFunction(arguments[0])||typeof arguments[0]=="boolean")){return this.__toggle.apply(this,arguments)}else{return this.effect.apply(this,c(arguments,"toggle"))}},addClass:function(g,f,i,h){return f?d.effects.animateClass.apply(this,[{add:g},f,i,h]):this._addClass(g)},removeClass:function(g,f,i,h){return f?d.effects.animateClass.apply(this,[{remove:g},f,i,h]):this._removeClass(g)},toggleClass:function(g,f,i,h){return((typeof f!=="boolean")&&f)?d.effects.animateClass.apply(this,[{toggle:g},f,i,h]):this._toggleClass(g,f)},morph:function(f,h,g,j,i){return d.effects.animateClass.apply(this,[{add:h,remove:f},g,j,i])},switchClass:function(){return this.morph.apply(this,arguments)},cssUnit:function(f){var g=this.css(f),h=[];d.each(["em","px","%","pt"],function(j,k){if(g.indexOf(k)>0){h=[parseFloat(g),k]}});return h}});d.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","color","outlineColor"],function(g,f){d.fx.step[f]=function(h){if(h.state==0){h.start=e(h.elem,f);h.end=b(h.end)}h.elem.style[f]="rgb("+[Math.max(Math.min(parseInt((h.pos*(h.end[0]-h.start[0]))+h.start[0],10),255),0),Math.max(Math.min(parseInt((h.pos*(h.end[1]-h.start[1]))+h.start[1],10),255),0),Math.max(Math.min(parseInt((h.pos*(h.end[2]-h.start[2]))+h.start[2],10),255),0)].join(",")+")"}});function b(g){var f;if(g&&g.constructor==Array&&g.length==3){return g}if(f=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(g)){return[parseInt(f[1],10),parseInt(f[2],10),parseInt(f[3],10)]}if(f=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(g)){return[parseFloat(f[1])*2.55,parseFloat(f[2])*2.55,parseFloat(f[3])*2.55]}if(f=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(g)){return[parseInt(f[1],16),parseInt(f[2],16),parseInt(f[3],16)]}if(f=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(g)){return[parseInt(f[1]+f[1],16),parseInt(f[2]+f[2],16),parseInt(f[3]+f[3],16)]}if(f=/rgba\(0, 0, 0, 0\)/.exec(g)){return a.transparent}return a[d.trim(g).toLowerCase()]}function e(h,f){var g;do{g=d.curCSS(h,f);if(g!=""&&g!="transparent"||d.nodeName(h,"body")){break}f="backgroundColor"}while(h=h.parentNode);return b(g)}var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]};d.easing.jswing=d.easing.swing;d.extend(d.easing,{def:"easeOutQuad",swing:function(g,h,f,j,i){return d.easing[d.easing.def](g,h,f,j,i)},easeInQuad:function(g,h,f,j,i){return j*(h/=i)*h+f},easeOutQuad:function(g,h,f,j,i){return -j*(h/=i)*(h-2)+f},easeInOutQuad:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h+f}return -j/2*((--h)*(h-2)-1)+f},easeInCubic:function(g,h,f,j,i){return j*(h/=i)*h*h+f},easeOutCubic:function(g,h,f,j,i){return j*((h=h/i-1)*h*h+1)+f},easeInOutCubic:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h+f}return j/2*((h-=2)*h*h+2)+f},easeInQuart:function(g,h,f,j,i){return j*(h/=i)*h*h*h+f},easeOutQuart:function(g,h,f,j,i){return -j*((h=h/i-1)*h*h*h-1)+f},easeInOutQuart:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h*h+f}return -j/2*((h-=2)*h*h*h-2)+f},easeInQuint:function(g,h,f,j,i){return j*(h/=i)*h*h*h*h+f},easeOutQuint:function(g,h,f,j,i){return j*((h=h/i-1)*h*h*h*h+1)+f},easeInOutQuint:function(g,h,f,j,i){if((h/=i/2)<1){return j/2*h*h*h*h*h+f}return j/2*((h-=2)*h*h*h*h+2)+f},easeInSine:function(g,h,f,j,i){return -j*Math.cos(h/i*(Math.PI/2))+j+f},easeOutSine:function(g,h,f,j,i){return j*Math.sin(h/i*(Math.PI/2))+f},easeInOutSine:function(g,h,f,j,i){return -j/2*(Math.cos(Math.PI*h/i)-1)+f},easeInExpo:function(g,h,f,j,i){return(h==0)?f:j*Math.pow(2,10*(h/i-1))+f},easeOutExpo:function(g,h,f,j,i){return(h==i)?f+j:j*(-Math.pow(2,-10*h/i)+1)+f},easeInOutExpo:function(g,h,f,j,i){if(h==0){return f}if(h==i){return f+j}if((h/=i/2)<1){return j/2*Math.pow(2,10*(h-1))+f}return j/2*(-Math.pow(2,-10*--h)+2)+f},easeInCirc:function(g,h,f,j,i){return -j*(Math.sqrt(1-(h/=i)*h)-1)+f},easeOutCirc:function(g,h,f,j,i){return j*Math.sqrt(1-(h=h/i-1)*h)+f},easeInOutCirc:function(g,h,f,j,i){if((h/=i/2)<1){return -j/2*(Math.sqrt(1-h*h)-1)+f}return j/2*(Math.sqrt(1-(h-=2)*h)+1)+f},easeInElastic:function(g,i,f,m,l){var j=1.70158;var k=0;var h=m;if(i==0){return f}if((i/=l)==1){return f+m}if(!k){k=l*0.3}if(h
      ").css({position:"absolute",visibility:"visible",left:-d*(g/e),top:-f*(c/k)}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:g/e,height:c/k,left:l.left+d*(g/e)+(b.options.mode=="show"?(d-Math.floor(e/2))*(g/e):0),top:l.top+f*(c/k)+(b.options.mode=="show"?(f-Math.floor(k/2))*(c/k):0),opacity:b.options.mode=="show"?0:1}).animate({left:l.left+d*(g/e)+(b.options.mode=="show"?0:(d-Math.floor(e/2))*(g/e)),top:l.top+f*(c/k)+(b.options.mode=="show"?0:(f-Math.floor(k/2))*(c/k)),opacity:b.options.mode=="show"?1:0},b.duration||500)}}setTimeout(function(){b.options.mode=="show"?h.css({visibility:"visible"}):h.css({visibility:"visible"}).hide();if(b.callback){b.callback.apply(h[0])}h.dequeue();a("div.ui-effects-explode").remove()},b.duration||500)})}})(jQuery);;/* + * jQuery UI Effects Fold 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Fold + * + * Depends: + * effects.core.js + */ +(function(a){a.effects.fold=function(b){return this.queue(function(){var e=a(this),k=["position","top","left"];var h=a.effects.setMode(e,b.options.mode||"hide");var o=b.options.size||15;var n=!(!b.options.horizFirst);var g=b.duration?b.duration/2:a.fx.speeds._default/2;a.effects.save(e,k);e.show();var d=a.effects.createWrapper(e).css({overflow:"hidden"});var i=((h=="show")!=n);var f=i?["width","height"]:["height","width"];var c=i?[d.width(),d.height()]:[d.height(),d.width()];var j=/([0-9]+)%/.exec(o);if(j){o=parseInt(j[1],10)/100*c[h=="hide"?0:1]}if(h=="show"){d.css(n?{height:0,width:o}:{height:o,width:0})}var m={},l={};m[f[0]]=h=="show"?c[0]:o;l[f[1]]=h=="show"?c[1]:0;d.animate(m,g,b.options.easing).animate(l,g,b.options.easing,function(){if(h=="hide"){e.hide()}a.effects.restore(e,k);a.effects.removeWrapper(e);if(b.callback){b.callback.apply(e[0],arguments)}e.dequeue()})})}})(jQuery);;/* + * jQuery UI Effects Highlight 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Highlight + * + * Depends: + * effects.core.js + */ +(function(a){a.effects.highlight=function(b){return this.queue(function(){var e=a(this),d=["backgroundImage","backgroundColor","opacity"];var h=a.effects.setMode(e,b.options.mode||"show");var c=b.options.color||"#ffff99";var g=e.css("backgroundColor");a.effects.save(e,d);e.show();e.css({backgroundImage:"none",backgroundColor:c});var f={backgroundColor:g};if(h=="hide"){f.opacity=0}e.animate(f,{queue:false,duration:b.duration,easing:b.options.easing,complete:function(){if(h=="hide"){e.hide()}a.effects.restore(e,d);if(h=="show"&&a.browser.msie){this.style.removeAttribute("filter")}if(b.callback){b.callback.apply(this,arguments)}e.dequeue()}})})}})(jQuery);;/* + * jQuery UI Effects Pulsate 1.7.2 + * + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * http://docs.jquery.com/UI/Effects/Pulsate + * + * Depends: + * effects.core.js + */ +(function(a){a.effects.pulsate=function(b){return this.queue(function(){var d=a(this);var g=a.effects.setMode(d,b.options.mode||"show");var f=b.options.times||5;var e=b.duration?b.duration/2:a.fx.speeds._default/2;if(g=="hide"){f--}if(d.is(":hidden")){d.css("opacity",0);d.show();d.animate({opacity:1},e,b.options.easing);f=f-2}for(var c=0;c').appendTo(document.body).addClass(b.options.className).css({top:d.top,left:d.left,height:f.innerHeight(),width:f.innerWidth(),position:"absolute"}).animate(g,b.duration,b.options.easing,function(){c.remove();(b.callback&&b.callback.apply(f[0],arguments));f.dequeue()})})}})(jQuery);; \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/libjs/jquery-ui/js/jquery-ui-i18n.js b/misc/PublicationBulletins/Portail-LeHavre/libjs/jquery-ui/js/jquery-ui-i18n.js new file mode 100755 index 00000000..61c769e6 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/libjs/jquery-ui/js/jquery-ui-i18n.js @@ -0,0 +1,771 @@ +/* Arabic Translation for jQuery UI date picker plugin. */ +/* Khaled Al Horani -- koko.dw@gmail.com */ +/* خالد الحوراني -- koko.dw@gmail.com */ +/* NOTE: monthNames are the original months names and they are the Arabic names, not the new months name فبراير - يناير and there isn't any Arabic roots for these months */ +jQuery(function($){ + $.datepicker.regional['ar'] = { + closeText: 'إغلاق', + prevText: '<السابق', + nextText: 'التالي>', + currentText: 'اليوم', + monthNames: ['كانون الثاني', 'شباط', 'آذار', 'نيسان', 'آذار', 'حزيران', + 'تموز', 'آب', 'أيلول', 'تشرين الأول', 'تشرين الثاني', 'كانون الأول'], + monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'], + dayNames: ['السبت', 'الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة'], + dayNamesShort: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة'], + dayNamesMin: ['سبت', 'أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة'], + dateFormat: 'dd/mm/yy', firstDay: 0, + isRTL: true}; + $.datepicker.setDefaults($.datepicker.regional['ar']); +});/* Bulgarian initialisation for the jQuery UI date picker plugin. */ +/* Written by Stoyan Kyosev (http://svest.org). */ +jQuery(function($){ + $.datepicker.regional['bg'] = { + closeText: 'затвори', + prevText: '<назад', + nextText: 'напред>', + nextBigText: '>>', + currentText: 'днес', + monthNames: ['Януари','Февруари','Март','Април','Май','Юни', + 'Юли','Август','Септември','Октомври','Ноември','Декември'], + monthNamesShort: ['Яну','Фев','Мар','Апр','Май','Юни', + 'Юли','Авг','Сеп','Окт','Нов','Дек'], + dayNames: ['Неделя','Понеделник','Вторник','Сряда','Четвъртък','Петък','Събота'], + dayNamesShort: ['Нед','Пон','Вто','Сря','Чет','Пет','Съб'], + dayNamesMin: ['Не','По','Вт','Ср','Че','Пе','Съ'], + dateFormat: 'dd.mm.yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['bg']); +}); +/* Inicialitzaci� en catal� per a l'extenci� 'calendar' per jQuery. */ +/* Writers: (joan.leon@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['ca'] = { + closeText: 'Tancar', + prevText: '<Ant', + nextText: 'Seg>', + currentText: 'Avui', + monthNames: ['Gener','Febrer','Març','Abril','Maig','Juny', + 'Juliol','Agost','Setembre','Octubre','Novembre','Desembre'], + monthNamesShort: ['Gen','Feb','Mar','Abr','Mai','Jun', + 'Jul','Ago','Set','Oct','Nov','Des'], + dayNames: ['Diumenge','Dilluns','Dimarts','Dimecres','Dijous','Divendres','Dissabte'], + dayNamesShort: ['Dug','Dln','Dmt','Dmc','Djs','Dvn','Dsb'], + dayNamesMin: ['Dg','Dl','Dt','Dc','Dj','Dv','Ds'], + dateFormat: 'mm/dd/yy', firstDay: 0, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['ca']); +});/* Czech initialisation for the jQuery UI date picker plugin. */ +/* Written by Tomas Muller (tomas@tomas-muller.net). */ +jQuery(function($){ + $.datepicker.regional['cs'] = { + closeText: 'Zavřít', + prevText: '<Dříve', + nextText: 'Později>', + currentText: 'Nyní', + monthNames: ['leden','únor','březen','duben','květen','červen', + 'červenec','srpen','září','říjen','listopad','prosinec'], + monthNamesShort: ['led','úno','bře','dub','kvě','čer', + 'čvc','srp','zář','říj','lis','pro'], + dayNames: ['neděle', 'pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', 'sobota'], + dayNamesShort: ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'], + dayNamesMin: ['ne','po','út','st','čt','pá','so'], + dateFormat: 'dd.mm.yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['cs']); +}); +/* Danish initialisation for the jQuery UI date picker plugin. */ +/* Written by Jan Christensen ( deletestuff@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['da'] = { + closeText: 'Luk', + prevText: '<Forrige', + nextText: 'Næste>', + currentText: 'Idag', + monthNames: ['Januar','Februar','Marts','April','Maj','Juni', + 'Juli','August','September','Oktober','November','December'], + monthNamesShort: ['Jan','Feb','Mar','Apr','Maj','Jun', + 'Jul','Aug','Sep','Okt','Nov','Dec'], + dayNames: ['Søndag','Mandag','Tirsdag','Onsdag','Torsdag','Fredag','Lørdag'], + dayNamesShort: ['Søn','Man','Tir','Ons','Tor','Fre','Lør'], + dayNamesMin: ['Sø','Ma','Ti','On','To','Fr','Lø'], + dateFormat: 'dd-mm-yy', firstDay: 0, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['da']); +}); +/* German initialisation for the jQuery UI date picker plugin. */ +/* Written by Milian Wolff (mail@milianw.de). */ +jQuery(function($){ + $.datepicker.regional['de'] = { + closeText: 'schließen', + prevText: '<zurück', + nextText: 'Vor>', + currentText: 'heute', + monthNames: ['Januar','Februar','März','April','Mai','Juni', + 'Juli','August','September','Oktober','November','Dezember'], + monthNamesShort: ['Jan','Feb','Mär','Apr','Mai','Jun', + 'Jul','Aug','Sep','Okt','Nov','Dez'], + dayNames: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'], + dayNamesShort: ['So','Mo','Di','Mi','Do','Fr','Sa'], + dayNamesMin: ['So','Mo','Di','Mi','Do','Fr','Sa'], + dateFormat: 'dd.mm.yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['de']); +}); +/* Greek (el) initialisation for the jQuery UI date picker plugin. */ +/* Written by Alex Cicovic (http://www.alexcicovic.com) */ +jQuery(function($){ + $.datepicker.regional['el'] = { + closeText: 'Κλείσιμο', + prevText: 'Προηγούμενος', + nextText: 'Επόμενος', + currentText: 'Τρέχων Μήνας', + monthNames: ['Ιανουάριος','Φεβρουάριος','Μάρτιος','Απρίλιος','Μάιος','Ιούνιος', + 'Ιούλιος','Αύγουστος','Σεπτέμβριος','Οκτώβριος','Νοέμβριος','Δεκέμβριος'], + monthNamesShort: ['Ιαν','Φεβ','Μαρ','Απρ','Μαι','Ιουν', + 'Ιουλ','Αυγ','Σεπ','Οκτ','Νοε','Δεκ'], + dayNames: ['Κυριακή','Δευτέρα','Τρίτη','Τετάρτη','Πέμπτη','Παρασκευή','Σάββατο'], + dayNamesShort: ['Κυρ','Δευ','Τρι','Τετ','Πεμ','Παρ','Σαβ'], + dayNamesMin: ['Κυ','Δε','Τρ','Τε','Πε','Πα','Σα'], + dateFormat: 'dd/mm/yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['el']); +});/* Esperanto initialisation for the jQuery UI date picker plugin. */ +/* Written by Olivier M. (olivierweb@ifrance.com). */ +jQuery(function($){ + $.datepicker.regional['eo'] = { + closeText: 'Fermi', + prevText: '<Anta', + nextText: 'Sekv>', + currentText: 'Nuna', + monthNames: ['Januaro','Februaro','Marto','Aprilo','Majo','Junio', + 'Julio','Aŭgusto','Septembro','Oktobro','Novembro','Decembro'], + monthNamesShort: ['Jan','Feb','Mar','Apr','Maj','Jun', + 'Jul','Aŭg','Sep','Okt','Nov','Dec'], + dayNames: ['Dimanĉo','Lundo','Mardo','Merkredo','Ĵaŭdo','Vendredo','Sabato'], + dayNamesShort: ['Dim','Lun','Mar','Mer','Ĵaŭ','Ven','Sab'], + dayNamesMin: ['Di','Lu','Ma','Me','Ĵa','Ve','Sa'], + dateFormat: 'dd/mm/yy', firstDay: 0, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['eo']); +}); +/* Inicializaci�n en espa�ol para la extensi�n 'UI date picker' para jQuery. */ +/* Traducido por Vester (xvester@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['es'] = { + closeText: 'Cerrar', + prevText: '<Ant', + nextText: 'Sig>', + currentText: 'Hoy', + monthNames: ['Enero','Febrero','Marzo','Abril','Mayo','Junio', + 'Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'], + monthNamesShort: ['Ene','Feb','Mar','Abr','May','Jun', + 'Jul','Ago','Sep','Oct','Nov','Dic'], + dayNames: ['Domingo','Lunes','Martes','Miércoles','Jueves','Viernes','Sábado'], + dayNamesShort: ['Dom','Lun','Mar','Mié','Juv','Vie','Sáb'], + dayNamesMin: ['Do','Lu','Ma','Mi','Ju','Vi','Sá'], + dateFormat: 'dd/mm/yy', firstDay: 0, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['es']); +});/* Persian (Farsi) Translation for the jQuery UI date picker plugin. */ +/* Javad Mowlanezhad -- jmowla@gmail.com */ +/* Jalali calendar should supported soon! (Its implemented but I have to test it) */ +jQuery(function($) { + $.datepicker.regional['fa'] = { + closeText: 'بستن', + prevText: '<قبلي', + nextText: 'بعدي>', + currentText: 'امروز', + monthNames: ['فروردين','ارديبهشت','خرداد','تير','مرداد','شهريور', + 'مهر','آبان','آذر','دي','بهمن','اسفند'], + monthNamesShort: ['1','2','3','4','5','6','7','8','9','10','11','12'], + dayNames: ['يکشنبه','دوشنبه','سه‌شنبه','چهارشنبه','پنجشنبه','جمعه','شنبه'], + dayNamesShort: ['ي','د','س','چ','پ','ج', 'ش'], + dayNamesMin: ['ي','د','س','چ','پ','ج', 'ش'], + dateFormat: 'yy/mm/dd', firstDay: 6, + isRTL: true}; + $.datepicker.setDefaults($.datepicker.regional['fa']); +});/* Finnish initialisation for the jQuery UI date picker plugin. */ +/* Written by Harri Kilpi� (harrikilpio@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['fi'] = { + closeText: 'Sulje', + prevText: '«Edellinen', + nextText: 'Seuraava»', + currentText: 'Tänään', + monthNames: ['Tammikuu','Helmikuu','Maaliskuu','Huhtikuu','Toukokuu','Kesäkuu', + 'Heinäkuu','Elokuu','Syyskuu','Lokakuu','Marraskuu','Joulukuu'], + monthNamesShort: ['Tammi','Helmi','Maalis','Huhti','Touko','Kesä', + 'Heinä','Elo','Syys','Loka','Marras','Joulu'], + dayNamesShort: ['Su','Ma','Ti','Ke','To','Pe','Su'], + dayNames: ['Sunnuntai','Maanantai','Tiistai','Keskiviikko','Torstai','Perjantai','Lauantai'], + dayNamesMin: ['Su','Ma','Ti','Ke','To','Pe','La'], + dateFormat: 'dd.mm.yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['fi']); +}); +/* French initialisation for the jQuery UI date picker plugin. */ +/* Written by Keith Wood (kbwood@virginbroadband.com.au) and Stéphane Nahmani (sholby@sholby.net). */ +jQuery(function($){ + $.datepicker.regional['fr'] = { + closeText: 'Fermer', + prevText: '<Préc', + nextText: 'Suiv>', + currentText: 'Courant', + monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin', + 'Juillet','Août','Septembre','Octobre','Novembre','Décembre'], + monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun', + 'Jul','Aoû','Sep','Oct','Nov','Déc'], + dayNames: ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'], + dayNamesShort: ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'], + dayNamesMin: ['Di','Lu','Ma','Me','Je','Ve','Sa'], + dateFormat: 'dd/mm/yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['fr']); +});/* Hebrew initialisation for the UI Datepicker extension. */ +/* Written by Amir Hardon (ahardon at gmail dot com). */ +jQuery(function($){ + $.datepicker.regional['he'] = { + closeText: 'סגור', + prevText: '<הקודם', + nextText: 'הבא>', + currentText: 'היום', + monthNames: ['ינואר','פברואר','מרץ','אפריל','מאי','יוני', + 'יולי','אוגוסט','ספטמבר','אוקטובר','נובמבר','דצמבר'], + monthNamesShort: ['1','2','3','4','5','6', + '7','8','9','10','11','12'], + dayNames: ['ראשון','שני','שלישי','רביעי','חמישי','שישי','שבת'], + dayNamesShort: ['א\'','ב\'','ג\'','ד\'','ה\'','ו\'','שבת'], + dayNamesMin: ['א\'','ב\'','ג\'','ד\'','ה\'','ו\'','שבת'], + dateFormat: 'dd/mm/yy', firstDay: 0, + isRTL: true}; + $.datepicker.setDefaults($.datepicker.regional['he']); +}); +/* Croatian i18n for the jQuery UI date picker plugin. */ +/* Written by Vjekoslav Nesek. */ +jQuery(function($){ + $.datepicker.regional['hr'] = { + closeText: 'Zatvori', + prevText: '<', + nextText: '>', + currentText: 'Danas', + monthNames: ['Siječanj','Veljača','Ožujak','Travanj','Svibanj','Lipani', + 'Srpanj','Kolovoz','Rujan','Listopad','Studeni','Prosinac'], + monthNamesShort: ['Sij','Velj','Ožu','Tra','Svi','Lip', + 'Srp','Kol','Ruj','Lis','Stu','Pro'], + dayNames: ['Nedjalja','Ponedjeljak','Utorak','Srijeda','Četvrtak','Petak','Subota'], + dayNamesShort: ['Ned','Pon','Uto','Sri','Čet','Pet','Sub'], + dayNamesMin: ['Ne','Po','Ut','Sr','Če','Pe','Su'], + dateFormat: 'dd.mm.yy.', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['hr']); +});/* Hungarian initialisation for the jQuery UI date picker plugin. */ +/* Written by Istvan Karaszi (jquerycalendar@spam.raszi.hu). */ +jQuery(function($){ + $.datepicker.regional['hu'] = { + closeText: 'bezárás', + prevText: '« vissza', + nextText: 'előre »', + currentText: 'ma', + monthNames: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', + 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'], + monthNamesShort: ['Jan', 'Feb', 'Már', 'Ápr', 'Máj', 'Jún', + 'Júl', 'Aug', 'Szep', 'Okt', 'Nov', 'Dec'], + dayNames: ['Vasámap', 'Hétfö', 'Kedd', 'Szerda', 'Csütörtök', 'Péntek', 'Szombat'], + dayNamesShort: ['Vas', 'Hét', 'Ked', 'Sze', 'Csü', 'Pén', 'Szo'], + dayNamesMin: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'], + dateFormat: 'yy-mm-dd', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['hu']); +}); +/* Armenian(UTF-8) initialisation for the jQuery UI date picker plugin. */ +/* Written by Levon Zakaryan (levon.zakaryan@gmail.com)*/ +jQuery(function($){ + $.datepicker.regional['hy'] = { + closeText: 'Փակել', + prevText: '<Նախ.', + nextText: 'Հաջ.>', + currentText: 'Այսօր', + monthNames: ['Հունվար','Փետրվար','Մարտ','Ապրիլ','Մայիս','Հունիս', + 'Հուլիս','Օգոստոս','Սեպտեմբեր','Հոկտեմբեր','Նոյեմբեր','Դեկտեմբեր'], + monthNamesShort: ['Հունվ','Փետր','Մարտ','Ապր','Մայիս','Հունիս', + 'Հուլ','Օգս','Սեպ','Հոկ','Նոյ','Դեկ'], + dayNames: ['կիրակի','եկուշաբթի','երեքշաբթի','չորեքշաբթի','հինգշաբթի','ուրբաթ','շաբաթ'], + dayNamesShort: ['կիր','երկ','երք','չրք','հնգ','ուրբ','շբթ'], + dayNamesMin: ['կիր','երկ','երք','չրք','հնգ','ուրբ','շբթ'], + dateFormat: 'dd.mm.yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['hy']); +});/* Indonesian initialisation for the jQuery UI date picker plugin. */ +/* Written by Deden Fathurahman (dedenf@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['id'] = { + closeText: 'Tutup', + prevText: '<mundur', + nextText: 'maju>', + currentText: 'hari ini', + monthNames: ['Januari','Februari','Maret','April','Mei','Juni', + 'Juli','Agustus','September','Oktober','Nopember','Desember'], + monthNamesShort: ['Jan','Feb','Mar','Apr','Mei','Jun', + 'Jul','Agus','Sep','Okt','Nop','Des'], + dayNames: ['Minggu','Senin','Selasa','Rabu','Kamis','Jumat','Sabtu'], + dayNamesShort: ['Min','Sen','Sel','Rab','kam','Jum','Sab'], + dayNamesMin: ['Mg','Sn','Sl','Rb','Km','jm','Sb'], + dateFormat: 'dd/mm/yy', firstDay: 0, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['id']); +});/* Icelandic initialisation for the jQuery UI date picker plugin. */ +/* Written by Haukur H. Thorsson (haukur@eskill.is). */ +jQuery(function($){ + $.datepicker.regional['is'] = { + closeText: 'Loka', + prevText: '< Fyrri', + nextText: 'Næsti >', + currentText: 'Í dag', + monthNames: ['Janúar','Febrúar','Mars','Apríl','Maí','Júní', + 'Júlí','Ágúst','September','Október','Nóvember','Desember'], + monthNamesShort: ['Jan','Feb','Mar','Apr','Maí','Jún', + 'Júl','Ágú','Sep','Okt','Nóv','Des'], + dayNames: ['Sunnudagur','Mánudagur','Þriðjudagur','Miðvikudagur','Fimmtudagur','Föstudagur','Laugardagur'], + dayNamesShort: ['Sun','Mán','Þri','Mið','Fim','Fös','Lau'], + dayNamesMin: ['Su','Má','Þr','Mi','Fi','Fö','La'], + dateFormat: 'dd/mm/yy', firstDay: 0, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['is']); +});/* Italian initialisation for the jQuery UI date picker plugin. */ +/* Written by Apaella (apaella@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['it'] = { + closeText: 'Chiudi', + prevText: '<Prec', + nextText: 'Succ>', + currentText: 'Oggi', + monthNames: ['Gennaio','Febbraio','Marzo','Aprile','Maggio','Giugno', + 'Luglio','Agosto','Settembre','Ottobre','Novembre','Dicembre'], + monthNamesShort: ['Gen','Feb','Mar','Apr','Mag','Giu', + 'Lug','Ago','Set','Ott','Nov','Dic'], + dayNames: ['Domenica','Lunedì','Martedì','Mercoledì','Giovedì','Venerdì','Sabato'], + dayNamesShort: ['Dom','Lun','Mar','Mer','Gio','Ven','Sab'], + dayNamesMin: ['Do','Lu','Ma','Me','Gio','Ve','Sa'], + dateFormat: 'dd/mm/yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['it']); +}); +/* Japanese initialisation for the jQuery UI date picker plugin. */ +/* Written by Kentaro SATO (kentaro@ranvis.com). */ +jQuery(function($){ + $.datepicker.regional['ja'] = { + closeText: '閉じる', + prevText: '<前', + nextText: '次>', + currentText: '今日', + monthNames: ['1月','2月','3月','4月','5月','6月', + '7月','8月','9月','10月','11月','12月'], + monthNamesShort: ['1月','2月','3月','4月','5月','6月', + '7月','8月','9月','10月','11月','12月'], + dayNames: ['日曜日','月曜日','火曜日','水曜日','木曜日','金曜日','土曜日'], + dayNamesShort: ['日','月','火','水','木','金','土'], + dayNamesMin: ['日','月','火','水','木','金','土'], + dateFormat: 'yy/mm/dd', firstDay: 0, + isRTL: false, + showMonthAfterYear: true}; + $.datepicker.setDefaults($.datepicker.regional['ja']); +});/* Korean initialisation for the jQuery calendar extension. */ +/* Written by DaeKwon Kang (ncrash.dk@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['ko'] = { + closeText: '닫기', + prevText: '이전달', + nextText: '다음달', + currentText: '오늘', + monthNames: ['1월(JAN)','2월(FEB)','3월(MAR)','4월(APR)','5월(MAY)','6월(JUN)', + '7월(JUL)','8월(AUG)','9월(SEP)','10월(OCT)','11월(NOV)','12월(DEC)'], + monthNamesShort: ['1월(JAN)','2월(FEB)','3월(MAR)','4월(APR)','5월(MAY)','6월(JUN)', + '7월(JUL)','8월(AUG)','9월(SEP)','10월(OCT)','11월(NOV)','12월(DEC)'], + dayNames: ['일','월','화','수','목','금','토'], + dayNamesShort: ['일','월','화','수','목','금','토'], + dayNamesMin: ['일','월','화','수','목','금','토'], + dateFormat: 'yy-mm-dd', firstDay: 0, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['ko']); +});/* Lithuanian (UTF-8) initialisation for the jQuery UI date picker plugin. */ +/* @author Arturas Paleicikas */ +jQuery(function($){ + $.datepicker.regional['lt'] = { + closeText: 'Uždaryti', + prevText: '<Atgal', + nextText: 'Pirmyn>', + currentText: 'Šiandien', + monthNames: ['Sausis','Vasaris','Kovas','Balandis','Gegužė','Birželis', + 'Liepa','Rugpjūtis','Rugsėjis','Spalis','Lapkritis','Gruodis'], + monthNamesShort: ['Sau','Vas','Kov','Bal','Geg','Bir', + 'Lie','Rugp','Rugs','Spa','Lap','Gru'], + dayNames: ['sekmadienis','pirmadienis','antradienis','trečiadienis','ketvirtadienis','penktadienis','šeštadienis'], + dayNamesShort: ['sek','pir','ant','tre','ket','pen','šeš'], + dayNamesMin: ['Se','Pr','An','Tr','Ke','Pe','Še'], + dateFormat: 'yy-mm-dd', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['lt']); +});/* Latvian (UTF-8) initialisation for the jQuery UI date picker plugin. */ +/* @author Arturas Paleicikas */ +jQuery(function($){ + $.datepicker.regional['lv'] = { + closeText: 'Aizvērt', + prevText: 'Iepr', + nextText: 'Nāka', + currentText: 'Šodien', + monthNames: ['Janvāris','Februāris','Marts','Aprīlis','Maijs','Jūnijs', + 'Jūlijs','Augusts','Septembris','Oktobris','Novembris','Decembris'], + monthNamesShort: ['Jan','Feb','Mar','Apr','Mai','Jūn', + 'Jūl','Aug','Sep','Okt','Nov','Dec'], + dayNames: ['svētdiena','pirmdiena','otrdiena','trešdiena','ceturtdiena','piektdiena','sestdiena'], + dayNamesShort: ['svt','prm','otr','tre','ctr','pkt','sst'], + dayNamesMin: ['Sv','Pr','Ot','Tr','Ct','Pk','Ss'], + dateFormat: 'dd-mm-yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['lv']); +});/* Malaysian initialisation for the jQuery UI date picker plugin. */ +/* Written by Mohd Nawawi Mohamad Jamili (nawawi@ronggeng.net). */ +jQuery(function($){ + $.datepicker.regional['ms'] = { + closeText: 'Tutup', + prevText: '<Sebelum', + nextText: 'Selepas>', + currentText: 'hari ini', + monthNames: ['Januari','Februari','Mac','April','Mei','Jun', + 'Julai','Ogos','September','Oktober','November','Disember'], + monthNamesShort: ['Jan','Feb','Mac','Apr','Mei','Jun', + 'Jul','Ogo','Sep','Okt','Nov','Dis'], + dayNames: ['Ahad','Isnin','Selasa','Rabu','Khamis','Jumaat','Sabtu'], + dayNamesShort: ['Aha','Isn','Sel','Rab','kha','Jum','Sab'], + dayNamesMin: ['Ah','Is','Se','Ra','Kh','Ju','Sa'], + dateFormat: 'dd/mm/yy', firstDay: 0, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['ms']); +});/* Dutch (UTF-8) initialisation for the jQuery UI date picker plugin. */ +/* Written by Mathias Bynens */ +jQuery(function($){ + $.datepicker.regional.nl = { + closeText: 'Sluiten', + prevText: '←', + nextText: '→', + currentText: 'Vandaag', + monthNames: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', + 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + monthNamesShort: ['jan', 'feb', 'maa', 'apr', 'mei', 'jun', + 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + dayNames: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + dayNamesShort: ['zon', 'maa', 'din', 'woe', 'don', 'vri', 'zat'], + dayNamesMin: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + dateFormat: 'dd/mm/yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional.nl); +});/* Norwegian initialisation for the jQuery UI date picker plugin. */ +/* Written by Naimdjon Takhirov (naimdjon@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['no'] = { + closeText: 'Lukk', + prevText: '«Forrige', + nextText: 'Neste»', + currentText: 'I dag', + monthNames: ['Januar','Februar','Mars','April','Mai','Juni', + 'Juli','August','September','Oktober','November','Desember'], + monthNamesShort: ['Jan','Feb','Mar','Apr','Mai','Jun', + 'Jul','Aug','Sep','Okt','Nov','Des'], + dayNamesShort: ['Søn','Man','Tir','Ons','Tor','Fre','Lør'], + dayNames: ['Søndag','Mandag','Tirsdag','Onsdag','Torsdag','Fredag','Lørdag'], + dayNamesMin: ['Sø','Ma','Ti','On','To','Fr','Lø'], + dateFormat: 'yy-mm-dd', firstDay: 0, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['no']); +}); +/* Polish initialisation for the jQuery UI date picker plugin. */ +/* Written by Jacek Wysocki (jacek.wysocki@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['pl'] = { + closeText: 'Zamknij', + prevText: '<Poprzedni', + nextText: 'Następny>', + currentText: 'Dziś', + monthNames: ['Styczeń','Luty','Marzec','Kwiecień','Maj','Czerwiec', + 'Lipiec','Sierpień','Wrzesień','Październik','Listopad','Grudzień'], + monthNamesShort: ['Sty','Lu','Mar','Kw','Maj','Cze', + 'Lip','Sie','Wrz','Pa','Lis','Gru'], + dayNames: ['Niedziela','Poniedzialek','Wtorek','Środa','Czwartek','Piątek','Sobota'], + dayNamesShort: ['Nie','Pn','Wt','Śr','Czw','Pt','So'], + dayNamesMin: ['N','Pn','Wt','Śr','Cz','Pt','So'], + dateFormat: 'yy-mm-dd', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['pl']); +}); +/* Brazilian initialisation for the jQuery UI date picker plugin. */ +/* Written by Leonildo Costa Silva (leocsilva@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['pt-BR'] = { + closeText: 'Fechar', + prevText: '<Anterior', + nextText: 'Próximo>', + currentText: 'Hoje', + monthNames: ['Janeiro','Fevereiro','Março','Abril','Maio','Junho', + 'Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'], + monthNamesShort: ['Jan','Fev','Mar','Abr','Mai','Jun', + 'Jul','Ago','Set','Out','Nov','Dez'], + dayNames: ['Domingo','Segunda-feira','Terça-feira','Quarta-feira','Quinta-feira','Sexta-feira','Sabado'], + dayNamesShort: ['Dom','Seg','Ter','Qua','Qui','Sex','Sab'], + dayNamesMin: ['Dom','Seg','Ter','Qua','Qui','Sex','Sab'], + dateFormat: 'dd/mm/yy', firstDay: 0, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['pt-BR']); +});/* Romanian initialisation for the jQuery UI date picker plugin. + * + * Written by Edmond L. (ll_edmond@walla.com) + * and Ionut G. Stan (ionut.g.stan@gmail.com) + */ +jQuery(function($){ + $.datepicker.regional['ro'] = { + closeText: 'Închide', + prevText: '« Luna precedentă', + nextText: 'Luna următoare »', + currentText: 'Azi', + monthNames: ['Ianuarie','Februarie','Martie','Aprilie','Mai','Iunie', + 'Iulie','August','Septembrie','Octombrie','Noiembrie','Decembrie'], + monthNamesShort: ['Ian', 'Feb', 'Mar', 'Apr', 'Mai', 'Iun', + 'Iul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + dayNames: ['Duminică', 'Luni', 'Marţi', 'Miercuri', 'Joi', 'Vineri', 'Sâmbătă'], + dayNamesShort: ['Dum', 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sâm'], + dayNamesMin: ['Du','Lu','Ma','Mi','Jo','Vi','Sâ'], + dateFormat: 'dd MM yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['ro']); +}); +/* Russian (UTF-8) initialisation for the jQuery UI date picker plugin. */ +/* Written by Andrew Stromnov (stromnov@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['ru'] = { + closeText: 'Закрыть', + prevText: '<Пред', + nextText: 'След>', + currentText: 'Сегодня', + monthNames: ['Январь','Февраль','Март','Апрель','Май','Июнь', + 'Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'], + monthNamesShort: ['Янв','Фев','Мар','Апр','Май','Июн', + 'Июл','Авг','Сен','Окт','Ноя','Дек'], + dayNames: ['воскресенье','понедельник','вторник','среда','четверг','пятница','суббота'], + dayNamesShort: ['вск','пнд','втр','срд','чтв','птн','сбт'], + dayNamesMin: ['Вс','Пн','Вт','Ср','Чт','Пт','Сб'], + dateFormat: 'dd.mm.yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['ru']); +});/* Slovak initialisation for the jQuery UI date picker plugin. */ +/* Written by Vojtech Rinik (vojto@hmm.sk). */ +jQuery(function($){ + $.datepicker.regional['sk'] = { + closeText: 'Zavrieť', + prevText: '<Predchádzajúci', + nextText: 'Nasledujúci>', + currentText: 'Dnes', + monthNames: ['Január','Február','Marec','Apríl','Máj','Jún', + 'Júl','August','September','Október','November','December'], + monthNamesShort: ['Jan','Feb','Mar','Apr','Máj','Jún', + 'Júl','Aug','Sep','Okt','Nov','Dec'], + dayNames: ['Nedel\'a','Pondelok','Utorok','Streda','Štvrtok','Piatok','Sobota'], + dayNamesShort: ['Ned','Pon','Uto','Str','Štv','Pia','Sob'], + dayNamesMin: ['Ne','Po','Ut','St','Št','Pia','So'], + dateFormat: 'dd.mm.yy', firstDay: 0, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['sk']); +}); +/* Slovenian initialisation for the jQuery UI date picker plugin. */ +/* Written by Jaka Jancar (jaka@kubje.org). */ +/* c = č, s = š z = ž C = Č S = Š Z = Ž */ +jQuery(function($){ + $.datepicker.regional['sl'] = { + closeText: 'Zapri', + prevText: '<Prejšnji', + nextText: 'Naslednji>', + currentText: 'Trenutni', + monthNames: ['Januar','Februar','Marec','April','Maj','Junij', + 'Julij','Avgust','September','Oktober','November','December'], + monthNamesShort: ['Jan','Feb','Mar','Apr','Maj','Jun', + 'Jul','Avg','Sep','Okt','Nov','Dec'], + dayNames: ['Nedelja','Ponedeljek','Torek','Sreda','Četrtek','Petek','Sobota'], + dayNamesShort: ['Ned','Pon','Tor','Sre','Čet','Pet','Sob'], + dayNamesMin: ['Ne','Po','To','Sr','Če','Pe','So'], + dateFormat: 'dd.mm.yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['sl']); +}); +/* Albanian initialisation for the jQuery UI date picker plugin. */ +/* Written by Flakron Bytyqi (flakron@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['sq'] = { + closeText: 'mbylle', + prevText: '<mbrapa', + nextText: 'Përpara>', + currentText: 'sot', + monthNames: ['Janar','Shkurt','Mars','Prill','Maj','Qershor', + 'Korrik','Gusht','Shtator','Tetor','Nëntor','Dhjetor'], + monthNamesShort: ['Jan','Shk','Mar','Pri','Maj','Qer', + 'Kor','Gus','Sht','Tet','Nën','Dhj'], + dayNames: ['E Diel','E Hënë','E Martë','E Mërkurë','E Enjte','E Premte','E Shtune'], + dayNamesShort: ['Di','Hë','Ma','Më','En','Pr','Sh'], + dayNamesMin: ['Di','Hë','Ma','Më','En','Pr','Sh'], + dateFormat: 'dd.mm.yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['sq']); +}); +/* Serbian i18n for the jQuery UI date picker plugin. */ +/* Written by Dejan Dimić. */ +jQuery(function($){ + $.datepicker.regional['sr-SR'] = { + closeText: 'Zatvori', + prevText: '<', + nextText: '>', + currentText: 'Danas', + monthNames: ['Januar','Februar','Mart','April','Maj','Jun', + 'Jul','Avgust','Septembar','Oktobar','Novembar','Decembar'], + monthNamesShort: ['Jan','Feb','Mar','Apr','Maj','Jun', + 'Jul','Avg','Sep','Okt','Nov','Dec'], + dayNames: ['Nedelja','Ponedeljak','Utorak','Sreda','Četvrtak','Petak','Subota'], + dayNamesShort: ['Ned','Pon','Uto','Sre','Čet','Pet','Sub'], + dayNamesMin: ['Ne','Po','Ut','Sr','Če','Pe','Su'], + dateFormat: 'dd/mm/yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['sr-SR']); +}); +/* Serbian i18n for the jQuery UI date picker plugin. */ +/* Written by Dejan Dimić. */ +jQuery(function($){ + $.datepicker.regional['sr'] = { + closeText: 'Затвори', + prevText: '<', + nextText: '>', + currentText: 'Данас', + monthNames: ['Јануар','Фебруар','Март','Април','Мај','Јун', + 'Јул','Август','Септембар','Октобар','Новембар','Децембар'], + monthNamesShort: ['Јан','Феб','Мар','Апр','Мај','Јун', + 'Јул','Авг','Сеп','Окт','Нов','Дец'], + dayNames: ['Недеља','Понедељак','Уторак','Среда','Четвртак','Петак','Субота'], + dayNamesShort: ['Нед','Пон','Уто','Сре','Чет','Пет','Суб'], + dayNamesMin: ['Не','По','Ут','Ср','Че','Пе','Су'], + dateFormat: 'dd/mm/yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['sr']); +}); +/* Swedish initialisation for the jQuery UI date picker plugin. */ +/* Written by Anders Ekdahl ( anders@nomadiz.se). */ +jQuery(function($){ + $.datepicker.regional['sv'] = { + closeText: 'Stäng', + prevText: '«Förra', + nextText: 'Nästa»', + currentText: 'Idag', + monthNames: ['Januari','Februari','Mars','April','Maj','Juni', + 'Juli','Augusti','September','Oktober','November','December'], + monthNamesShort: ['Jan','Feb','Mar','Apr','Maj','Jun', + 'Jul','Aug','Sep','Okt','Nov','Dec'], + dayNamesShort: ['Sön','Mån','Tis','Ons','Tor','Fre','Lör'], + dayNames: ['Söndag','Måndag','Tisdag','Onsdag','Torsdag','Fredag','Lördag'], + dayNamesMin: ['Sö','Må','Ti','On','To','Fr','Lö'], + dateFormat: 'yy-mm-dd', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['sv']); +}); +/* Thai initialisation for the jQuery UI date picker plugin. */ +/* Written by pipo (pipo@sixhead.com). */ +jQuery(function($){ + $.datepicker.regional['th'] = { + closeText: 'ปิด', + prevText: '« ย้อน', + nextText: 'ถัดไป »', + currentText: 'วันนี้', + monthNames: ['มกราคม','กุมภาพันธ์','มีนาคม','เมษายน','พฤษภาคม','มิถุนายน', + 'กรกฏาคม','สิงหาคม','กันยายน','ตุลาคม','พฤศจิกายน','ธันวาคม'], + monthNamesShort: ['ม.ค.','ก.พ.','มี.ค.','เม.ย.','พ.ค.','มิ.ย.', + 'ก.ค.','ส.ค.','ก.ย.','ต.ค.','พ.ย.','ธ.ค.'], + dayNames: ['อาทิตย์','จันทร์','อังคาร','พุธ','พฤหัสบดี','ศุกร์','เสาร์'], + dayNamesShort: ['อา.','จ.','อ.','พ.','พฤ.','ศ.','ส.'], + dayNamesMin: ['อา.','จ.','อ.','พ.','พฤ.','ศ.','ส.'], + dateFormat: 'dd/mm/yy', firstDay: 0, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['th']); +});/* Turkish initialisation for the jQuery UI date picker plugin. */ +/* Written by Izzet Emre Erkan (kara@karalamalar.net). */ +jQuery(function($){ + $.datepicker.regional['tr'] = { + closeText: 'kapat', + prevText: '<geri', + nextText: 'ileri>', + currentText: 'bugün', + monthNames: ['Ocak','Şubat','Mart','Nisan','Mayıs','Haziran', + 'Temmuz','Ağustos','Eylül','Ekim','Kasım','Aralık'], + monthNamesShort: ['Oca','Şub','Mar','Nis','May','Haz', + 'Tem','Ağu','Eyl','Eki','Kas','Ara'], + dayNames: ['Pazar','Pazartesi','Salı','Çarşamba','Perşembe','Cuma','Cumartesi'], + dayNamesShort: ['Pz','Pt','Sa','Ça','Pe','Cu','Ct'], + dayNamesMin: ['Pz','Pt','Sa','Ça','Pe','Cu','Ct'], + dateFormat: 'dd.mm.yy', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['tr']); +});/* Ukrainian (UTF-8) initialisation for the jQuery UI date picker plugin. */ +/* Written by Maxim Drogobitskiy (maxdao@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['uk'] = { + clearText: 'Очистити', clearStatus: '', + closeText: 'Закрити', closeStatus: '', + prevText: '<', prevStatus: '', + prevBigText: '<<', prevBigStatus: '', + nextText: '>', nextStatus: '', + nextBigText: '>>', nextBigStatus: '', + currentText: 'Сьогодні', currentStatus: '', + monthNames: ['Січень','Лютий','Березень','Квітень','Травень','Червень', + 'Липень','Серпень','Вересень','Жовтень','Листопад','Грудень'], + monthNamesShort: ['Січ','Лют','Бер','Кві','Тра','Чер', + 'Лип','Сер','Вер','Жов','Лис','Гру'], + monthStatus: '', yearStatus: '', + weekHeader: 'Не', weekStatus: '', + dayNames: ['неділя','понеділок','вівторок','середа','четвер','п’ятниця','субота'], + dayNamesShort: ['нед','пнд','вів','срд','чтв','птн','сбт'], + dayNamesMin: ['Нд','Пн','Вт','Ср','Чт','Пт','Сб'], + dayStatus: 'DD', dateStatus: 'D, M d', + dateFormat: 'dd/mm/yy', firstDay: 1, + initStatus: '', isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['uk']); +});/* Chinese initialisation for the jQuery UI date picker plugin. */ +/* Written by Cloudream (cloudream@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['zh-CN'] = { + closeText: '关闭', + prevText: '<上月', + nextText: '下月>', + currentText: '今天', + monthNames: ['一月','二月','三月','四月','五月','六月', + '七月','八月','九月','十月','十一月','十二月'], + monthNamesShort: ['一','二','三','四','五','六', + '七','八','九','十','十一','十二'], + dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'], + dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'], + dayNamesMin: ['日','一','二','三','四','五','六'], + dateFormat: 'yy-mm-dd', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['zh-CN']); +}); +/* Chinese initialisation for the jQuery UI date picker plugin. */ +/* Written by Ressol (ressol@gmail.com). */ +jQuery(function($){ + $.datepicker.regional['zh-TW'] = { + closeText: '關閉', + prevText: '<上月', + nextText: '下月>', + currentText: '今天', + monthNames: ['一月','二月','三月','四月','五月','六月', + '七月','八月','九月','十月','十一月','十二月'], + monthNamesShort: ['一','二','三','四','五','六', + '七','八','九','十','十一','十二'], + dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'], + dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'], + dayNamesMin: ['日','一','二','三','四','五','六'], + dateFormat: 'yy/mm/dd', firstDay: 1, + isRTL: false}; + $.datepicker.setDefaults($.datepicker.regional['zh-TW']); +}); diff --git a/misc/PublicationBulletins/Portail-LeHavre/libjs/jquery.ui.map.full.min.js b/misc/PublicationBulletins/Portail-LeHavre/libjs/jquery.ui.map.full.min.js new file mode 100644 index 00000000..0c896273 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/libjs/jquery.ui.map.full.min.js @@ -0,0 +1,2 @@ +/*! http://code.google.com/p/jquery-ui-map/ | Johan Sll Larsson */ +eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1;};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p;}('(3(d){d.a=3(a,b){k c=a.u(".")[0],a=a.u(".")[1];d[c]=d[c]||{};d[c][a]=3(a,b){J.N&&2.13(a,b)};d[c][a].G=d.p({1w:c,1F:a},b);d.1h[a]=3(b){k g="1E"===1G b,f=Q.G.14.15(J,1),i=2;l(g&&"1I"===b.1H(0,1))4 i;2.1A(3(){k h=d.R(2,a);h||(h=d.R(2,a,j d[c][a](b,2)));g&&(i=h[b].18(h,f))});4 i}};d.a("1z.1B",{m:{1D:"1C",1P:5},1O:3(a,b){b&&(2.m[a]=b,2.6("9").C(2.m));4 2.m[a]},13:3(a,b){2.D=b;o.p(2.m,a);2.m.U=2.E(2.m.U);2.Y();2.V&&2.V()},Y:3(){k a=2;a.q={9:j 8.7.1Q(a.D,a.m),H:[],s:[],r:[],S:j 8.7.1S};8.7.x.1R(a.q.9,"1K",3(){d(a.D).1p("1J",a.q.9)});a.y(a.m.1N,a.q.9)},16:3(a){2.6("M",j 8.7.1M).p(2.E(a));2.6("9").1q(2.6("M"));4 2},1r:3(a){k b=2.6("9").1s();4 b?b.1t(a.17()):!1},1x:3(a,b){2.6("9").1y[b].L(2.z(a));4 2},1u:3(a,b,c){a.9=2.6("9");a.W=2.E(a.W);k c=j(c||8.7.1v)(a),e=2.6("H");c.T?e[c.T]=c:e.L(c);c.M&&2.16(c.17());2.y(b,a.9,c);4 d(c)},w:3(a){2.A(2.6(a));2.O(a,[]);4 2},A:3(a){P(k b 10 a)a.Z(b)&&(a[b]t 8.7.1c?(8.7.x.2c(a[b]),a[b].F&&a[b].F(v)):a[b]t Q&&2.A(a[b]),a[b]=v)},2b:3(a,b,c){k a=2.6(a),e;P(e 10 a)a.Z(e)&&c(a[e],b.11&&a[e][b.I]?-1")){P(k e=a.X(/ /g,"").u(">"),d=0;d "+a,[]).L(j 8.7[a](o.p({9:2.6("9")},b))))},21:3(a,b){(!b?2.6("s > B",j 8.7.B):2.6("s > B",j 8.7.B(b,a))).C(o.p({9:2.6("9")},a))},20:3(a,b,c){2.6("s > "+a,j 8.7.23(b,o.p({9:2.6("9")},c)))},26:3(a,b,c){k d=2,g=2.6("r > 1n",j 8.7.1n),f=2.6("r > 1i",j 8.7.1i);b&&f.C(b);g.25(a,3(a,b){"24"===b?(f.1U(a),f.F(d.6("9"))):f.F(v);c(a,b)})},1Y:3(a,b){2.6("9").1X(2.6("r > 1e",j 8.7.1e(2.z(a),b)))},29:3(a,b){2.6("r > 1f",j 8.7.1f).28(a,b)}});o.1h.p({1d:3(a,b){4 2.n("1d",a,b)},1a:3(a){4 2.n("1a",a)},19:3(a,b){4 2.n("19",a,b)},1b:3(a,b){4 2.n("1b",a,b)},1o:3(a,b){4 2.n("1o",a,b)},1k:3(a){4 2.n("1k",a)},1j:3(a){4 2.n("1j",a)},1m:3(a){8.7.x.1p(2[0],a)},n:3(a,b,c){8.7&&2[0]t 8.7.1c?8.7.x.1L(2[0],a,b):c?2.1g(a,b,c):2.1g(a,b);4 2}})})(o);',62,144,'||this|function|return||get|maps|google|map||||||||||new|var|if|options|addEventListener|jQuery|extend|instance|services|overlays|instanceof|split|null|clear|event|_call|_unwrap|_c|FusionTablesLayer|setOptions|el|_latLng|setMap|prototype|markers|property|arguments|LatLng|push|bounds|length|set|for|Array|data|iw|id|center|_init|position|replace|_create|hasOwnProperty|in|delimiter|value|_setup|slice|call|addBounds|getPosition|apply|dblclick|rightclick|mouseover|MVCObject|click|StreetViewPanorama|Geocoder|bind|fn|DirectionsRenderer|dragend|drag|else|triggerEvent|DirectionsService|mouseout|trigger|fitBounds|inViewport|getBounds|contains|addMarker|Marker|namespace|addControl|controls|ui|each|gmap|roadmap|mapTypeId|string|pluginName|typeof|substring|_|init|bounds_changed|addListener|LatLngBounds|callback|option|zoom|Map|addListenerOnce|InfoWindow|name|setDirections|isFunction|Object|setStreetView|displayStreetView|removeData|loadKML|loadFusion|addShape|KmlLayer|OK|route|displayDirections|openInfoWindow|geocode|search|open|find|clearInstanceListeners|indexOf|inArray|refresh|resize|setCenter|destroy|getCenter'.split('|'),0,{})) \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/libjs/menu.js b/misc/PublicationBulletins/Portail-LeHavre/libjs/menu.js new file mode 100644 index 00000000..dacad592 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/libjs/menu.js @@ -0,0 +1,63 @@ +/* -*- mode: javascript -*- + */ + +function getMouseXY(e) // works on IE6,FF,Moz,Opera7 +{ + if (!e) e = window.event; // works on IE, but not NS (we rely on NS passing us the event) + + if (e) + { + if (e.pageY) + { // this doesn't work on IE6!! (works on FF,Moz,Opera7) + mousey = e.pageY; + algor = '[e.pageX]'; + if (e.clientX || e.clientY) algor += ' [e.clientX] ' + } + else if (e.clientY) + { // works on IE6,FF,Moz,Opera7 +if ( document.documentElement && document.documentElement.scrollTop ) + { + mousey = e.clientY + document.documentElement.scrollTop; + } + + else + { + mousey = e.clientY + document.body.scrollTop; + } + algor = '[e.clientX]'; + if (e.pageX || e.pageY) algor += ' [e.pageX] ' + } + } +} + +var menu_firefox_flicker = false ; + + +var mousey = 0 + +function MenuDisplay(l_element) + { + getMouseXY() + if ( ! menu_firefox_flicker ) + { + l_element.childNodes[1].style.display = 'block' ; + if ( mousey > 600 ) + { + l_element.childNodes[1].style.left = '0px' ; + l_element.childNodes[1].style.display = 'block' ; + l_element.childNodes[1].style.top = - l_element.childNodes[1].offsetHeight + 'px' ; + } + } + else if ( mousey > 600 ) + { + l_element.childNodes[1].style.top = - l_element.childNodes[1].offsetHeight + 'px' ; + } + } + +function MenuHide(l_element) + { + if ( ! menu_firefox_flicker ) + { + l_element.childNodes[1].style.display = 'none' + } + } diff --git a/misc/PublicationBulletins/Portail-LeHavre/libjs/qtip/INSTALL b/misc/PublicationBulletins/Portail-LeHavre/libjs/qtip/INSTALL new file mode 100755 index 00000000..686431f8 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/libjs/qtip/INSTALL @@ -0,0 +1,8 @@ +Installing qTip is relatively straight forward. Simply include the both the jQuery and qTip library files e.g. jquery-qtip-1.0.0.min.js using script html element(s), like so: + + + + +Notice the jQuery library is included before qTip. This is _absolutely essential_ for correct functioning of the library and its accompanying methods. + +It is HIGHLY RECOMMENDED that all JavaScript includes like the one above be placed after all your content at the footer of the page, just before the end tag. This ensures that all content is loaded before manipulation of the DOM occurs. \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/libjs/qtip/LICENSE b/misc/PublicationBulletins/Portail-LeHavre/libjs/qtip/LICENSE new file mode 100755 index 00000000..e9cd5ecb --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/libjs/qtip/LICENSE @@ -0,0 +1,7 @@ +Copyright © 2009 Craig Thompson + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/libjs/qtip/REQUIREMENTS b/misc/PublicationBulletins/Portail-LeHavre/libjs/qtip/REQUIREMENTS new file mode 100755 index 00000000..3c7918fd --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/libjs/qtip/REQUIREMENTS @@ -0,0 +1,5 @@ +JQUERY IS REQUIRED FOR USE OF THIS PLUGIN + +You can grab the latest version here: http://jquery.com + +We recommend version 1.3 due to its significant speed increases in several areas, but qTip supports the use of jQuery version 1.2.6 and above for all you who have decided not to upgrade. \ No newline at end of file diff --git a/misc/PublicationBulletins/Portail-LeHavre/libjs/qtip/jquery.qtip-1.0.0-rc3.min.js b/misc/PublicationBulletins/Portail-LeHavre/libjs/qtip/jquery.qtip-1.0.0-rc3.min.js new file mode 100755 index 00000000..173885b7 --- /dev/null +++ b/misc/PublicationBulletins/Portail-LeHavre/libjs/qtip/jquery.qtip-1.0.0-rc3.min.js @@ -0,0 +1,15 @@ +/* + * jquery.qtip. The jQuery tooltip plugin + * + * Copyright (c) 2009 Craig Thompson + * http://craigsworks.com + * + * Licensed under MIT + * http://www.opensource.org/licenses/mit-license.php + * + * Launch : February 2009 + * Version : 1.0.0-rc3 + * Released: Tuesday 12th May, 2009 - 00:00 + * Debug: jquery.qtip.debug.js + */ +(function(f){f.fn.qtip=function(B,u){var y,t,A,s,x,w,v,z;if(typeof B=="string"){if(typeof f(this).data("qtip")!=="object"){f.fn.qtip.log.error.call(self,1,f.fn.qtip.constants.NO_TOOLTIP_PRESENT,false)}if(B=="api"){return f(this).data("qtip").interfaces[f(this).data("qtip").current]}else{if(B=="interfaces"){return f(this).data("qtip").interfaces}}}else{if(!B){B={}}if(typeof B.content!=="object"||(B.content.jquery&&B.content.length>0)){B.content={text:B.content}}if(typeof B.content.title!=="object"){B.content.title={text:B.content.title}}if(typeof B.position!=="object"){B.position={corner:B.position}}if(typeof B.position.corner!=="object"){B.position.corner={target:B.position.corner,tooltip:B.position.corner}}if(typeof B.show!=="object"){B.show={when:B.show}}if(typeof B.show.when!=="object"){B.show.when={event:B.show.when}}if(typeof B.show.effect!=="object"){B.show.effect={type:B.show.effect}}if(typeof B.hide!=="object"){B.hide={when:B.hide}}if(typeof B.hide.when!=="object"){B.hide.when={event:B.hide.when}}if(typeof B.hide.effect!=="object"){B.hide.effect={type:B.hide.effect}}if(typeof B.style!=="object"){B.style={name:B.style}}B.style=c(B.style);s=f.extend(true,{},f.fn.qtip.defaults,B);s.style=a.call({options:s},s.style);s.user=f.extend(true,{},B)}return f(this).each(function(){if(typeof B=="string"){w=B.toLowerCase();A=f(this).qtip("interfaces");if(typeof A=="object"){if(u===true&&w=="destroy"){while(A.length>0){A[A.length-1].destroy()}}else{if(u!==true){A=[f(this).qtip("api")]}for(y=0;y0))}if(typeof s.options.show.solo=="object"){z=f(s.options.show.solo)}else{if(s.options.show.solo===true){z=f("div.qtip").not(s.elements.tooltip)}}if(z){z.each(function(){if(f(this).qtip("api").status.rendered===true){f(this).qtip("api").hide()}})}if(typeof s.options.show.effect.type=="function"){s.options.show.effect.type.call(s.elements.tooltip,s.options.show.effect.length);s.elements.tooltip.queue(function(){w();f(this).dequeue()})}else{switch(s.options.show.effect.type.toLowerCase()){case"fade":s.elements.tooltip.fadeIn(s.options.show.effect.length,w);break;case"slide":s.elements.tooltip.slideDown(s.options.show.effect.length,function(){w();if(s.options.position.type!=="static"){s.updatePosition(y,true)}});break;case"grow":s.elements.tooltip.show(s.options.show.effect.length,w);break;default:s.elements.tooltip.show(null,w);break}s.elements.tooltip.addClass(s.options.style.classes.active)}return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_SHOWN,"show")},hide:function(y){var x;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"hide")}else{if(s.elements.tooltip.css("display")==="none"){return s}}clearTimeout(s.timers.show);s.elements.tooltip.stop(true,false);x=s.beforeHide.call(s,y);if(x===false){return s}function w(){s.onHide.call(s,y)}s.cache.toggle=0;if(typeof s.options.hide.effect.type=="function"){s.options.hide.effect.type.call(s.elements.tooltip,s.options.hide.effect.length);s.elements.tooltip.queue(function(){w();f(this).dequeue()})}else{switch(s.options.hide.effect.type.toLowerCase()){case"fade":s.elements.tooltip.fadeOut(s.options.hide.effect.length,w);break;case"slide":s.elements.tooltip.slideUp(s.options.hide.effect.length,w);break;case"grow":s.elements.tooltip.hide(s.options.hide.effect.length,w);break;default:s.elements.tooltip.hide(null,w);break}s.elements.tooltip.removeClass(s.options.style.classes.active)}return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_HIDDEN,"hide")},updatePosition:function(w,x){var C,G,L,J,H,E,y,I,B,D,K,A,F,z;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updatePosition")}else{if(s.options.position.type=="static"){return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.CANNOT_POSITION_STATIC,"updatePosition")}}G={position:{left:0,top:0},dimensions:{height:0,width:0},corner:s.options.position.corner.target};L={position:s.getPosition(),dimensions:s.getDimensions(),corner:s.options.position.corner.tooltip};if(s.options.position.target!=="mouse"){if(s.options.position.target.get(0).nodeName.toLowerCase()=="area"){J=s.options.position.target.attr("coords").split(",");for(C=0;CG.dimensions.width){G.dimensions.width=J[C]}if(J[C]G.dimensions.height){G.dimensions.height=J[C]}if(J[C]0){if(L.corner.search(/Left/)!==-1){y.left-=s.options.style.border.radius}else{if(L.corner.search(/Right/)!==-1){y.left+=s.options.style.border.radius}}if(L.corner.search(/Top/)!==-1){y.top-=s.options.style.border.radius}else{if(L.corner.search(/Bottom/)!==-1){y.top+=s.options.style.border.radius}}}if(I){if(L.corner.search(/top/)!==-1){y.top-=I}else{if(L.corner.search(/bottom/)!==-1){y.top+=I}}if(L.corner.search(/left/)!==-1){y.left-=I}else{if(L.corner.search(/right/)!==-1){y.left+=I}}if(L.corner.search(/leftMiddle|rightMiddle/)!==-1){y.top-=1}}if(s.options.position.adjust.screen===true){y=o.call(s,y,G,L)}if(s.options.position.target==="mouse"&&s.options.position.adjust.mouse===true){if(s.options.position.adjust.screen===true&&s.elements.tip){K=s.elements.tip.attr("rel")}else{K=s.options.position.corner.tooltip}y.left+=(K.search(/right/i)!==-1)?-6:6;y.top+=(K.search(/bottom/i)!==-1)?-6:6}if(!s.elements.bgiframe&&f.browser.msie&&parseInt(f.browser.version.charAt(0))==6){f("select, object").each(function(){A=f(this).offset();A.bottom=A.top+f(this).height();A.right=A.left+f(this).width();if(y.top+L.dimensions.height>=A.top&&y.left+L.dimensions.width>=A.left){k.call(s)}})}y.left+=s.options.position.adjust.x;y.top+=s.options.position.adjust.y;F=s.getPosition();if(y.left!=F.left||y.top!=F.top){z=s.beforePositionUpdate.call(s,w);if(z===false){return s}s.cache.position=y;if(x===true){s.status.animated=true;s.elements.tooltip.animate(y,200,"swing",function(){s.status.animated=false})}else{s.elements.tooltip.css(y)}s.onPositionUpdate.call(s,w);if(typeof w!=="undefined"&&w.type&&w.type!=="mousemove"){f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_POSITION_UPDATED,"updatePosition")}}return s},updateWidth:function(w){var x;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updateWidth")}else{if(w&&typeof w!=="number"){return f.fn.qtip.log.error.call(s,2,"newWidth must be of type number","updateWidth")}}x=s.elements.contentWrapper.siblings().add(s.elements.tip).add(s.elements.button);if(!w){if(typeof s.options.style.width.value=="number"){w=s.options.style.width.value}else{s.elements.tooltip.css({width:"auto"});x.hide();if(f.browser.msie){s.elements.wrapper.add(s.elements.contentWrapper.children()).css({zoom:"normal"})}w=s.getDimensions().width+1;if(!s.options.style.width.value){if(w>s.options.style.width.max){w=s.options.style.width.max}if(w").get(0).getContext){z=s.elements.tooltip.find(".qtip-tip canvas:first");x=z.get(0).getContext("2d");x.clearRect(0,0,300,300);y=z.parent("div[rel]:first").attr("rel");B=b(y,s.options.style.tip.size.width,s.options.style.tip.size.height);h.call(s,z,B,s.options.style.tip.color||s.options.style.border.color)}else{if(f.browser.msie){z=s.elements.tooltip.find('.qtip-tip [nodeName="shape"]');z.attr("fillcolor",s.options.style.tip.color||s.options.style.border.color)}}}if(s.options.style.border.radius>0){s.elements.tooltip.find(".qtip-betweenCorners").css({backgroundColor:s.options.style.border.color});if(f("").get(0).getContext){A=g(s.options.style.border.radius);s.elements.tooltip.find(".qtip-wrapper canvas").each(function(){x=f(this).get(0).getContext("2d");x.clearRect(0,0,300,300);y=f(this).parent("div[rel]:first").attr("rel");r.call(s,f(this),A[y],s.options.style.border.radius,s.options.style.border.color)})}else{if(f.browser.msie){s.elements.tooltip.find('.qtip-wrapper [nodeName="arc"]').each(function(){f(this).attr("fillcolor",s.options.style.border.color)})}}}return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_STYLE_UPDATED,"updateStyle")},updateContent:function(A,y){var z,x,w;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updateContent")}else{if(!A){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.NO_CONTENT_PROVIDED,"updateContent")}}z=s.beforeContentUpdate.call(s,A);if(typeof z=="string"){A=z}else{if(z===false){return}}if(f.browser.msie){s.elements.contentWrapper.children().css({zoom:"normal"})}if(A.jquery&&A.length>0){A.clone(true).appendTo(s.elements.content).show()}else{s.elements.content.html(A)}x=s.elements.content.find("img[complete=false]");if(x.length>0){w=0;x.each(function(C){f('').load(function(){if(++w==x.length){B()}})})}else{B()}function B(){s.updateWidth();if(y!==false){if(s.options.position.type!=="static"){s.updatePosition(s.elements.tooltip.is(":visible"),true)}if(s.options.style.tip.corner!==false){n.call(s)}}}s.onContentUpdate.call(s);return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_CONTENT_UPDATED,"loadContent")},loadContent:function(w,z,A){var y;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"loadContent")}y=s.beforeContentLoad.call(s);if(y===false){return s}if(A=="post"){f.post(w,z,x)}else{f.get(w,z,x)}function x(B){s.onContentLoad.call(s);f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_CONTENT_LOADED,"loadContent");s.updateContent(B)}return s},updateTitle:function(w){if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updateTitle")}else{if(!w){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.NO_CONTENT_PROVIDED,"updateTitle")}}returned=s.beforeTitleUpdate.call(s);if(returned===false){return s}if(s.elements.button){s.elements.button=s.elements.button.clone(true)}s.elements.title.html(w);if(s.elements.button){s.elements.title.prepend(s.elements.button)}s.onTitleUpdate.call(s);return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_TITLE_UPDATED,"updateTitle")},focus:function(A){var y,x,w,z;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"focus")}else{if(s.options.position.type=="static"){return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.CANNOT_FOCUS_STATIC,"focus")}}y=parseInt(s.elements.tooltip.css("z-index"));x=6000+f("div.qtip[qtip]").length-1;if(!s.status.focused&&y!==x){z=s.beforeFocus.call(s,A);if(z===false){return s}f("div.qtip[qtip]").not(s.elements.tooltip).each(function(){if(f(this).qtip("api").status.rendered===true){w=parseInt(f(this).css("z-index"));if(typeof w=="number"&&w>-1){f(this).css({zIndex:parseInt(f(this).css("z-index"))-1})}f(this).qtip("api").status.focused=false}});s.elements.tooltip.css({zIndex:x});s.status.focused=true;s.onFocus.call(s,A);f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_FOCUSED,"focus")}return s},disable:function(w){if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"disable")}if(w){if(!s.status.disabled){s.status.disabled=true;f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_DISABLED,"disable")}else{f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.TOOLTIP_ALREADY_DISABLED,"disable")}}else{if(s.status.disabled){s.status.disabled=false;f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_ENABLED,"disable")}else{f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.TOOLTIP_ALREADY_ENABLED,"disable")}}return s},destroy:function(){var w,x,y;x=s.beforeDestroy.call(s);if(x===false){return s}if(s.status.rendered){s.options.show.when.target.unbind("mousemove.qtip",s.updatePosition);s.options.show.when.target.unbind("mouseout.qtip",s.hide);s.options.show.when.target.unbind(s.options.show.when.event+".qtip");s.options.hide.when.target.unbind(s.options.hide.when.event+".qtip");s.elements.tooltip.unbind(s.options.hide.when.event+".qtip");s.elements.tooltip.unbind("mouseover.qtip",s.focus);s.elements.tooltip.remove()}else{s.options.show.when.target.unbind(s.options.show.when.event+".qtip-create")}if(typeof s.elements.target.data("qtip")=="object"){y=s.elements.target.data("qtip").interfaces;if(typeof y=="object"&&y.length>0){for(w=0;w0){s.elements.target.data("qtip").current=y.length-1}else{s.elements.target.removeData("qtip")}s.onDestroy.call(s);f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_DESTROYED,"destroy");return s.elements.target},getPosition:function(){var w,x;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"getPosition")}w=(s.elements.tooltip.css("display")!=="none")?false:true;if(w){s.elements.tooltip.css({visiblity:"hidden"}).show()}x=s.elements.tooltip.offset();if(w){s.elements.tooltip.css({visiblity:"visible"}).hide()}return x},getDimensions:function(){var w,x;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"getDimensions")}w=(!s.elements.tooltip.is(":visible"))?true:false;if(w){s.elements.tooltip.css({visiblity:"hidden"}).show()}x={height:s.elements.tooltip.outerHeight(),width:s.elements.tooltip.outerWidth()};if(w){s.elements.tooltip.css({visiblity:"visible"}).hide()}return x}})}function p(){var s,w,u,t,v,y,x;s=this;s.beforeRender.call(s);s.status.rendered=true;s.elements.tooltip='';s.elements.tooltip=f(s.elements.tooltip);s.elements.tooltip.appendTo(s.options.position.container);s.elements.tooltip.data("qtip",{current:0,interfaces:[s]});s.elements.wrapper=s.elements.tooltip.children("div:first");s.elements.contentWrapper=s.elements.wrapper.children("div:first").css({background:s.options.style.background});s.elements.content=s.elements.contentWrapper.children("div:first").css(q(s.options.style));if(f.browser.msie){s.elements.wrapper.add(s.elements.content).css({zoom:1})}if(s.options.hide.when.event=="unfocus"){s.elements.tooltip.attr("unfocus",true)}if(typeof s.options.style.width.value=="number"){s.updateWidth()}if(f("").get(0).getContext||f.browser.msie){if(s.options.style.border.radius>0){m.call(s)}else{s.elements.contentWrapper.css({border:s.options.style.border.width+"px solid "+s.options.style.border.color})}if(s.options.style.tip.corner!==false){e.call(s)}}else{s.elements.contentWrapper.css({border:s.options.style.border.width+"px solid "+s.options.style.border.color});s.options.style.border.radius=0;s.options.style.tip.corner=false;f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.CANVAS_VML_NOT_SUPPORTED,"render")}if((typeof s.options.content.text=="string"&&s.options.content.text.length>0)||(s.options.content.text.jquery&&s.options.content.text.length>0)){u=s.options.content.text}else{if(typeof s.elements.target.attr("title")=="string"&&s.elements.target.attr("title").length>0){u=s.elements.target.attr("title").replace("\\n","
      ");s.elements.target.attr("title","")}else{if(typeof s.elements.target.attr("alt")=="string"&&s.elements.target.attr("alt").length>0){u=s.elements.target.attr("alt").replace("\\n","
      ");s.elements.target.attr("alt","")}else{u=" ";f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.NO_VALID_CONTENT,"render")}}}if(s.options.content.title.text!==false){j.call(s)}s.updateContent(u);l.call(s);if(s.options.show.ready===true){s.show()}if(s.options.content.url!==false){t=s.options.content.url;v=s.options.content.data;y=s.options.content.method||"get";s.loadContent(t,v,y)}s.onRender.call(s);f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_RENDERED,"render")}function m(){var F,z,t,B,x,E,u,G,D,y,w,C,A,s,v;F=this;F.elements.wrapper.find(".qtip-borderBottom, .qtip-borderTop").remove();t=F.options.style.border.width;B=F.options.style.border.radius;x=F.options.style.border.color||F.options.style.tip.color;E=g(B);u={};for(z in E){u[z]='
      ';if(f("").get(0).getContext){u[z]+=''}else{if(f.browser.msie){G=B*2+3;u[z]+=''}}u[z]+="
      "}D=F.getDimensions().width-(Math.max(t,B)*2);y='
      ';w='
      '+u.topLeft+u.topRight+y;F.elements.wrapper.prepend(w);C='
      '+u.bottomLeft+u.bottomRight+y;F.elements.wrapper.append(C);if(f("").get(0).getContext){F.elements.wrapper.find("canvas").each(function(){A=E[f(this).parent("[rel]:first").attr("rel")];r.call(F,f(this),A,B,x)})}else{if(f.browser.msie){F.elements.tooltip.append('')}}s=Math.max(B,(B+(t-B)));v=Math.max(t-B,0);F.elements.contentWrapper.css({border:"0px solid "+x,borderWidth:v+"px "+s+"px"})}function r(u,w,s,t){var v=u.get(0).getContext("2d");v.fillStyle=t;v.beginPath();v.arc(w[0],w[1],s,0,Math.PI*2,false);v.fill()}function e(v){var t,s,x,u,w;t=this;if(t.elements.tip!==null){t.elements.tip.remove()}s=t.options.style.tip.color||t.options.style.border.color;if(t.options.style.tip.corner===false){return}else{if(!v){v=t.options.style.tip.corner}}x=b(v,t.options.style.tip.size.width,t.options.style.tip.size.height);t.elements.tip='
      ';if(f("").get(0).getContext){t.elements.tip+=''}else{if(f.browser.msie){u=t.options.style.tip.size.width+","+t.options.style.tip.size.height;w="m"+x[0][0]+","+x[0][1];w+=" l"+x[1][0]+","+x[1][1];w+=" "+x[2][0]+","+x[2][1];w+=" xe";t.elements.tip+='';t.elements.tip+='';t.elements.contentWrapper.css("position","relative")}}t.elements.tooltip.prepend(t.elements.tip+"
      ");t.elements.tip=t.elements.tooltip.find("."+t.options.style.classes.tip).eq(0);if(f("").get(0).getContext){h.call(t,t.elements.tip.find("canvas:first"),x,s)}if(v.search(/top/)!==-1&&f.browser.msie&&parseInt(f.browser.version.charAt(0))===6){t.elements.tip.css({marginTop:-4})}n.call(t,v)}function h(t,v,s){var u=t.get(0).getContext("2d");u.fillStyle=s;u.beginPath();u.moveTo(v[0][0],v[0][1]);u.lineTo(v[1][0],v[1][1]);u.lineTo(v[2][0],v[2][1]);u.fill()}function n(u){var t,w,s,x,v;t=this;if(t.options.style.tip.corner===false||!t.elements.tip){return}if(!u){u=t.elements.tip.attr("rel")}w=positionAdjust=(f.browser.msie)?1:0;t.elements.tip.css(u.match(/left|right|top|bottom/)[0],0);if(u.search(/top|bottom/)!==-1){if(f.browser.msie){if(parseInt(f.browser.version.charAt(0))===6){positionAdjust=(u.search(/top/)!==-1)?-3:1}else{positionAdjust=(u.search(/top/)!==-1)?1:2}}if(u.search(/Middle/)!==-1){t.elements.tip.css({left:"50%",marginLeft:-(t.options.style.tip.size.width/2)})}else{if(u.search(/Left/)!==-1){t.elements.tip.css({left:t.options.style.border.radius-w})}else{if(u.search(/Right/)!==-1){t.elements.tip.css({right:t.options.style.border.radius+w})}}}if(u.search(/top/)!==-1){t.elements.tip.css({top:-positionAdjust})}else{t.elements.tip.css({bottom:positionAdjust})}}else{if(u.search(/left|right/)!==-1){if(f.browser.msie){positionAdjust=(parseInt(f.browser.version.charAt(0))===6)?1:((u.search(/left/)!==-1)?1:2)}if(u.search(/Middle/)!==-1){t.elements.tip.css({top:"50%",marginTop:-(t.options.style.tip.size.height/2)})}else{if(u.search(/Top/)!==-1){t.elements.tip.css({top:t.options.style.border.radius-w})}else{if(u.search(/Bottom/)!==-1){t.elements.tip.css({bottom:t.options.style.border.radius+w})}}}if(u.search(/left/)!==-1){t.elements.tip.css({left:-positionAdjust})}else{t.elements.tip.css({right:positionAdjust})}}}s="padding-"+u.match(/left|right|top|bottom/)[0];x=t.options.style.tip.size[(s.search(/left|right/)!==-1)?"width":"height"];t.elements.tooltip.css("padding",0);t.elements.tooltip.css(s,x);if(f.browser.msie&&parseInt(f.browser.version.charAt(0))==6){v=parseInt(t.elements.tip.css("margin-top"))||0;v+=parseInt(t.elements.content.css("margin-top"))||0;t.elements.tip.css({marginTop:v})}}function j(){var s=this;if(s.elements.title!==null){s.elements.title.remove()}s.elements.title=f('
      ').css(q(s.options.style.title,true)).css({zoom:(f.browser.msie)?1:0}).prependTo(s.elements.contentWrapper);if(s.options.content.title.text){s.updateTitle.call(s,s.options.content.title.text)}if(s.options.content.title.button!==false&&typeof s.options.content.title.button=="string"){s.elements.button=f('').css(q(s.options.style.button,true)).html(s.options.content.title.button).prependTo(s.elements.title).click(function(t){if(!s.status.disabled){s.hide(t)}})}}function l(){var t,v,u,s;t=this;v=t.options.show.when.target;u=t.options.hide.when.target;if(t.options.hide.fixed){u=u.add(t.elements.tooltip)}if(t.options.hide.when.event=="inactive"){s=["click","dblclick","mousedown","mouseup","mousemove","mouseout","mouseenter","mouseleave","mouseover"];function y(z){if(t.status.disabled===true){return}clearTimeout(t.timers.inactive);t.timers.inactive=setTimeout(function(){f(s).each(function(){u.unbind(this+".qtip-inactive");t.elements.content.unbind(this+".qtip-inactive")});t.hide(z)},t.options.hide.delay)}}else{if(t.options.hide.fixed===true){t.elements.tooltip.bind("mouseover.qtip",function(){if(t.status.disabled===true){return}clearTimeout(t.timers.hide)})}}function x(z){if(t.status.disabled===true){return}if(t.options.hide.when.event=="inactive"){f(s).each(function(){u.bind(this+".qtip-inactive",y);t.elements.content.bind(this+".qtip-inactive",y)});y()}clearTimeout(t.timers.show);clearTimeout(t.timers.hide);t.timers.show=setTimeout(function(){t.show(z)},t.options.show.delay)}function w(z){if(t.status.disabled===true){return}if(t.options.hide.fixed===true&&t.options.hide.when.event.search(/mouse(out|leave)/i)!==-1&&f(z.relatedTarget).parents("div.qtip[qtip]").length>0){z.stopPropagation();z.preventDefault();clearTimeout(t.timers.hide);return false}clearTimeout(t.timers.show);clearTimeout(t.timers.hide);t.elements.tooltip.stop(true,true);t.timers.hide=setTimeout(function(){t.hide(z)},t.options.hide.delay)}if((t.options.show.when.target.add(t.options.hide.when.target).length===1&&t.options.show.when.event==t.options.hide.when.event&&t.options.hide.when.event!=="inactive")||t.options.hide.when.event=="unfocus"){t.cache.toggle=0;v.bind(t.options.show.when.event+".qtip",function(z){if(t.cache.toggle==0){x(z)}else{w(z)}})}else{v.bind(t.options.show.when.event+".qtip",x);if(t.options.hide.when.event!=="inactive"){u.bind(t.options.hide.when.event+".qtip",w)}}if(t.options.position.type.search(/(fixed|absolute)/)!==-1){t.elements.tooltip.bind("mouseover.qtip",t.focus)}if(t.options.position.target==="mouse"&&t.options.position.type!=="static"){v.bind("mousemove.qtip",function(z){t.cache.mouse={x:z.pageX,y:z.pageY};if(t.status.disabled===false&&t.options.position.adjust.mouse===true&&t.options.position.type!=="static"&&t.elements.tooltip.css("display")!=="none"){t.updatePosition(z)}})}}function o(u,v,A){var z,s,x,y,t,w;z=this;if(A.corner=="center"){return v.position}s=f.extend({},u);y={x:false,y:false};t={left:(s.left=f.fn.qtip.cache.screen.width+f.fn.qtip.cache.screen.scroll.left),top:(s.top=f.fn.qtip.cache.screen.height+f.fn.qtip.cache.screen.scroll.top)};x={left:(t.left&&(A.corner.search(/right/i)!=-1||(A.corner.search(/right/i)==-1&&!t.right))),right:(t.right&&(A.corner.search(/left/i)!=-1||(A.corner.search(/left/i)==-1&&!t.left))),top:(t.top&&A.corner.search(/top/i)==-1),bottom:(t.bottom&&A.corner.search(/bottom/i)==-1)};if(x.left){if(z.options.position.target!=="mouse"){s.left=v.position.left+v.dimensions.width}else{s.left=z.cache.mouse.x}y.x="Left"}else{if(x.right){if(z.options.position.target!=="mouse"){s.left=v.position.left-A.dimensions.width}else{s.left=z.cache.mouse.x-A.dimensions.width}y.x="Right"}}if(x.top){if(z.options.position.target!=="mouse"){s.top=v.position.top+v.dimensions.height}else{s.top=z.cache.mouse.y}y.y="top"}else{if(x.bottom){if(z.options.position.target!=="mouse"){s.top=v.position.top-A.dimensions.height}else{s.top=z.cache.mouse.y-A.dimensions.height}y.y="bottom"}}if(s.left<0){s.left=u.left;y.x=false}if(s.top<0){s.top=u.top;y.y=false}if(z.options.style.tip.corner!==false){s.corner=new String(A.corner);if(y.x!==false){s.corner=s.corner.replace(/Left|Right|Middle/,y.x)}if(y.y!==false){s.corner=s.corner.replace(/top|bottom/,y.y)}if(s.corner!==z.elements.tip.attr("rel")){e.call(z,s.corner)}}return s}function q(u,t){var v,s;v=f.extend(true,{},u);for(s in v){if(t===true&&s.search(/(tip|classes)/i)!==-1){delete v[s]}else{if(!t&&s.search(/(width|border|tip|title|classes|user)/i)!==-1){delete v[s]}}}return v}function c(s){if(typeof s.tip!=="object"){s.tip={corner:s.tip}}if(typeof s.tip.size!=="object"){s.tip.size={width:s.tip.size,height:s.tip.size}}if(typeof s.border!=="object"){s.border={width:s.border}}if(typeof s.width!=="object"){s.width={value:s.width}}if(typeof s.width.max=="string"){s.width.max=parseInt(s.width.max.replace(/([0-9]+)/i,"$1"))}if(typeof s.width.min=="string"){s.width.min=parseInt(s.width.min.replace(/([0-9]+)/i,"$1"))}if(typeof s.tip.size.x=="number"){s.tip.size.width=s.tip.size.x;delete s.tip.size.x}if(typeof s.tip.size.y=="number"){s.tip.size.height=s.tip.size.y;delete s.tip.size.y}return s}function a(){var s,t,u,x,v,w;s=this;u=[true,{}];for(t=0;t0){v.tip.size.width+=1}if(v.tip.size.height%2>0){v.tip.size.height+=1}if(v.tip.corner===true){v.tip.corner=(s.options.position.corner.tooltip==="center")?false:s.options.position.corner.tooltip}return v}function b(v,u,t){var s={bottomRight:[[0,0],[u,t],[u,0]],bottomLeft:[[0,0],[u,0],[0,t]],topRight:[[0,t],[u,0],[u,t]],topLeft:[[0,0],[0,t],[u,t]],topMiddle:[[0,t],[u/2,0],[u,t]],bottomMiddle:[[0,0],[u,0],[u/2,t]],rightMiddle:[[0,0],[u,t/2],[0,t]],leftMiddle:[[u,0],[u,t],[0,t/2]]};s.leftTop=s.bottomRight;s.rightTop=s.bottomLeft;s.leftBottom=s.topRight;s.rightBottom=s.topLeft;return s[v]}function g(s){var t;if(f("").get(0).getContext){t={topLeft:[s,s],topRight:[0,s],bottomLeft:[s,0],bottomRight:[0,0]}}else{if(f.browser.msie){t={topLeft:[-90,90,0],topRight:[-90,90,-s],bottomLeft:[90,270,0],bottomRight:[90,270,-s]}}}return t}function k(){var s,t,u;s=this;u=s.getDimensions();t='\ + \ + '); + } + + + diff --git a/static/libjs/jQuery-tagEditor/jquery.caret.min.js b/static/libjs/jQuery-tagEditor/jquery.caret.min.js new file mode 100644 index 00000000..0f9ef48d --- /dev/null +++ b/static/libjs/jQuery-tagEditor/jquery.caret.min.js @@ -0,0 +1,2 @@ +// http://code.accursoft.com/caret - 1.3.3 +!function(e){e.fn.caret=function(e){var t=this[0],n="true"===t.contentEditable;if(0==arguments.length){if(window.getSelection){if(n){t.focus();var o=window.getSelection().getRangeAt(0),r=o.cloneRange();return r.selectNodeContents(t),r.setEnd(o.endContainer,o.endOffset),r.toString().length}return t.selectionStart}if(document.selection){if(t.focus(),n){var o=document.selection.createRange(),r=document.body.createTextRange();return r.moveToElementText(t),r.setEndPoint("EndToEnd",o),r.text.length}var e=0,c=t.createTextRange(),r=document.selection.createRange().duplicate(),a=r.getBookmark();for(c.moveToBookmark(a);0!==c.moveStart("character",-1);)e++;return e}return t.selectionStart?t.selectionStart:0}if(-1==e&&(e=this[n?"text":"val"]().length),window.getSelection)n?(t.focus(),window.getSelection().collapse(t.firstChild,e)):t.setSelectionRange(e,e);else if(document.body.createTextRange)if(n){var c=document.body.createTextRange();c.moveToElementText(t),c.moveStart("character",e),c.collapse(!0),c.select()}else{var c=t.createTextRange();c.move("character",e),c.select()}return n||t.focus(),e}}(jQuery); diff --git a/static/libjs/jQuery-tagEditor/jquery.tag-editor.css b/static/libjs/jQuery-tagEditor/jquery.tag-editor.css new file mode 100644 index 00000000..949995d2 --- /dev/null +++ b/static/libjs/jQuery-tagEditor/jquery.tag-editor.css @@ -0,0 +1,45 @@ +/* surrounding tag container */ +.tag-editor { + list-style-type: none; padding: 0 5px 0 0; margin: 0; overflow: hidden; border: 1px solid #eee; cursor: text; + font: normal 14px sans-serif; color: #555; background: #fff; line-height: 20px; +} + +/* core styles usually need no change */ +.tag-editor li { display: block; float: left; overflow: hidden; margin: 3px 0; } +.tag-editor div { float: left; padding: 0 4px; } +.tag-editor .placeholder { padding: 0 8px; color: #bbb; } +.tag-editor .tag-editor-spacer { padding: 0; width: 8px; overflow: hidden; color: transparent; background: none; } +.tag-editor input { + vertical-align: inherit; border: 0; outline: none; padding: 0; margin: 0; cursor: text; + font-family: inherit; font-weight: inherit; font-size: inherit; font-style: inherit; + box-shadow: none; background: none; color: #444; +} +/* hide original input field or textarea visually to allow tab navigation */ +.tag-editor-hidden-src { position: absolute !important; left: -99999px; } +/* hide IE10 "clear field" X */ +.tag-editor ::-ms-clear { display: none; } + +/* tag style */ +.tag-editor .tag-editor-tag { + padding-left: 5px; color: #46799b; background: #e0eaf1; white-space: nowrap; + overflow: hidden; cursor: pointer; border-radius: 2px 0 0 2px; +} + +/* delete icon */ +.tag-editor .tag-editor-delete { background: #e0eaf1; cursor: pointer; border-radius: 0 2px 2px 0; padding-left: 3px; padding-right: 4px; } +.tag-editor .tag-editor-delete i { line-height: 18px; display: inline-block; } +.tag-editor .tag-editor-delete i:before { font-size: 16px; color: #8ba7ba; content: "×"; font-style: normal; } +.tag-editor .tag-editor-delete:hover i:before { color: #d65454; } +.tag-editor .tag-editor-tag.active+.tag-editor-delete, .tag-editor .tag-editor-tag.active+.tag-editor-delete i { visibility: hidden; cursor: text; } + +.tag-editor .tag-editor-tag.active { background: none !important; } + +/* jQuery UI autocomplete - code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css */ +.ui-autocomplete { position: absolute; top: 0; left: 0; cursor: default; font-size: 14px; } +.ui-front { z-index: 9999; } +.ui-menu { list-style: none; padding: 1px; margin: 0; display: block; outline: none; } +.ui-menu .ui-menu-item a { text-decoration: none; display: block; padding: 2px .4em; line-height: 1.4; min-height: 0; /* support: IE7 */ } +.ui-widget-content { border: 1px solid #bbb; background: #fff; color: #555; } +.ui-widget-content a { color: #46799b; } +.ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { background: #e0eaf1; } +.ui-helper-hidden-accessible { display: none; } diff --git a/static/libjs/jQuery-tagEditor/jquery.tag-editor.js b/static/libjs/jQuery-tagEditor/jquery.tag-editor.js new file mode 100644 index 00000000..048f5384 --- /dev/null +++ b/static/libjs/jQuery-tagEditor/jquery.tag-editor.js @@ -0,0 +1,370 @@ +/* + jQuery tagEditor v1.0.20 + Copyright (c) 2014 Simon Steinberger / Pixabay + GitHub: https://github.com/Pixabay/jQuery-tagEditor + License: http://www.opensource.org/licenses/mit-license.php +*/ + +(function($){ + // auto grow input (stackoverflow.com/questions/931207) + $.fn.tagEditorInput=function(){var t=" ",e=$(this),n=parseInt(e.css("fontSize")),i=$("").css({position:"absolute",top:-9999,left:-9999,width:"auto",fontSize:e.css("fontSize"),fontFamily:e.css("fontFamily"),fontWeight:e.css("fontWeight"),letterSpacing:e.css("letterSpacing"),whiteSpace:"nowrap"}),s=function(){if(t!==(t=e.val())){i.text(t);var s=i.width()+n;20>s&&(s=20),s!=e.width()&&e.width(s)}};return i.insertAfter(e),e.bind("keyup keydown focus",s)}; + + // plugin with val as parameter for public methods + $.fn.tagEditor = function(options, val, blur){ + + // helper + function escape(tag) { + return tag.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); + } + + // build options dictionary with default values + var blur_result, o = $.extend({}, $.fn.tagEditor.defaults, options), selector = this; + + // store regex and default delimiter in options for later use + o.dregex = new RegExp('['+o.delimiter.replace('-', '\-')+']', 'g'); + + // public methods + if (typeof options == 'string') { + // depending on selector, response may contain tag lists of multiple editor instances + var response = []; + selector.each(function(){ + // the editor is the next sibling to the hidden, original field + var el = $(this), o = el.data('options'), ed = el.next('.tag-editor'); + if (options == 'getTags') + response.push({field: el[0], editor: ed, tags: ed.data('tags')}); + else if (options == 'addTag') { + if (o.maxTags && ed.data('tags').length >= o.maxTags) return false; + // insert new tag + $('
    14.  '+o.delimiter[0]+'
    15. ').appendTo(ed).find('.tag-editor-tag') + .html('').addClass('active').find('input').val(val).blur(); + if (!blur) ed.click(); + else $('.placeholder', ed).remove(); + } else if (options == 'removeTag') { + // trigger delete on matching tag, then click editor to create a new tag + $('.tag-editor-tag', ed).filter(function(){return $(this).text()==val;}).closest('li').find('.tag-editor-delete').click(); + if (!blur) ed.click(); + } else if (options == 'destroy') { + el.removeClass('tag-editor-hidden-src').removeData('options').off('focus.tag-editor').next('.tag-editor').remove(); + } + }); + return options == 'getTags' ? response : this; + } + + // delete selected tags on backspace, delete, ctrl+x + if (window.getSelection) $(document).off('keydown.tag-editor').on('keydown.tag-editor', function(e){ + if (e.which == 8 || e.which == 46 || e.ctrlKey && e.which == 88) { + try { + var sel = getSelection(), el = document.activeElement.tagName == 'BODY' ? $(sel.getRangeAt(0).startContainer.parentNode).closest('.tag-editor') : 0; + } catch(e){ el = 0; } + if (sel.rangeCount > 0 && el && el.length) { + var tags = [], splits = sel.toString().split(el.prev().data('options').dregex); + for (i=0; i').insertAfter(el); + el.addClass('tag-editor-hidden-src') // hide original field + .data('options', o) // set data on hidden field + .on('focus.tag-editor', function(){ ed.click(); }); // simulate tabindex + + // add dummy item for min-height on empty editor + ed.append('
    16.  
    17. '); + + // markup for new tag + var new_tag = '
    18.  '+o.delimiter[0]+'
    19. '; + + // helper: update global data + function set_placeholder(){ + if (o.placeholder && !tag_list.length && !$('.deleted, .placeholder, input', ed).length) + ed.append('
    20. '+o.placeholder+'
    21. '); + } + + // helper: update global data + function update_globals(init){ + var old_tags = tag_list.toString(); + tag_list = $('.tag-editor-tag:not(.deleted)', ed).map(function(i, e) { + var val = $.trim($(this).hasClass('active') ? $(this).find('input').val() : $(e).text()); + if (val) return val; + }).get(); + ed.data('tags', tag_list); + el.val(tag_list.join(o.delimiter[0])); + // change callback except for plugin init + if (!init) if (old_tags != tag_list.toString()) o.onChange(el, ed, tag_list); + set_placeholder(); + } + + ed.click(function(e, closest_tag){ + var d, dist = 99999, loc; + + // do not create tag when user selects tags by text selection + if (window.getSelection && getSelection() != '') return; + + if (o.maxTags && ed.data('tags').length >= o.maxTags) { ed.find('input').blur(); return false; } + + blur_result = true + $('input:focus', ed).blur(); + if (!blur_result) return false; + blur_result = true + + // always remove placeholder on click + $('.placeholder', ed).remove(); + if (closest_tag && closest_tag.length) + loc = 'before'; + else { + // calculate tag closest to click position + $('.tag-editor-tag', ed).each(function(){ + var tag = $(this), to = tag.offset(), tag_x = to.left, tag_y = to.top; + if (e.pageY >= tag_y && e.pageY <= tag_y+tag.height()) { + if (e.pageX < tag_x) loc = 'before', d = tag_x - e.pageX; + else loc = 'after', d = e.pageX - tag_x - tag.width(); + if (d < dist) dist = d, closest_tag = tag; + } + }); + } + + if (loc == 'before') { + $(new_tag).insertBefore(closest_tag.closest('li')).find('.tag-editor-tag').click(); + } else if (loc == 'after') + $(new_tag).insertAfter(closest_tag.closest('li')).find('.tag-editor-tag').click(); + else // empty editor + $(new_tag).appendTo(ed).find('.tag-editor-tag').click(); + return false; + }); + + ed.on('click', '.tag-editor-delete', function(e){ + // delete icon is hidden when input is visible; place cursor near invisible delete icon on click + if ($(this).prev().hasClass('active')) { $(this).closest('li').find('input').caret(-1); return false; } + + var li = $(this).closest('li'), tag = li.find('.tag-editor-tag'); + if (o.beforeTagDelete(el, ed, tag_list, tag.text()) === false) return false; + tag.addClass('deleted').animate({width: 0}, o.animateDelete, function(){ li.remove(); set_placeholder(); }); + update_globals(); + return false; + }); + + // delete on right mouse click or ctrl+click + if (o.clickDelete) + ed.on('mousedown', '.tag-editor-tag', function(e){ + if (e.ctrlKey || e.which > 1) { + var li = $(this).closest('li'), tag = li.find('.tag-editor-tag'); + if (o.beforeTagDelete(el, ed, tag_list, tag.text()) === false) return false; + tag.addClass('deleted').animate({width: 0}, o.animateDelete, function(){ li.remove(); set_placeholder(); }); + update_globals(); + return false; + } + }); + + ed.on('click', '.tag-editor-tag', function(e){ + // delete on right click or ctrl+click -> exit + if (o.clickDelete && (e.ctrlKey || e.which > 1)) return false; + + if (!$(this).hasClass('active')) { + var tag = $(this).text(); + // guess cursor position in text input + var left_percent = Math.abs(($(this).offset().left - e.pageX)/$(this).width()), caret_pos = parseInt(tag.length*left_percent), + input = $(this).html('').addClass('active').find('input'); + input.data('old_tag', tag).tagEditorInput().focus().caret(caret_pos); + if (o.autocomplete) { + var aco = $.extend({}, o.autocomplete); + // extend user provided autocomplete select method + var ac_select = 'select' in aco ? o.autocomplete.select : ''; + aco.select = function(e, ui){ if (ac_select) ac_select(e, ui); setTimeout(function(){ + ed.trigger('click', [$('.active', ed).find('input').closest('li').next('li').find('.tag-editor-tag')]); + }, 20); }; + input.autocomplete(aco); + } + } + return false; + }); + + // helper: split into multiple tags, e.g. after paste + function split_cleanup(input){ + var li = input.closest('li'), sub_tags = input.val().replace(/ +/, ' ').split(o.dregex), + old_tag = input.data('old_tag'), old_tags = tag_list.slice(0), exceeded = false, cb_val; // copy tag_list + for (var i=0; i
       '+o.delimiter[0]+'
      '+escape(tag)+'
      '); + if (o.maxTags && old_tags.length >= o.maxTags) { exceeded = true; break; } + } + input.attr('maxlength', o.maxLength).removeData('old_tag').val('') + if (exceeded) input.blur(); else input.focus(); + update_globals(); + } + + ed.on('blur', 'input', function(e){ + e.stopPropagation(); + var input = $(this), old_tag = input.data('old_tag'), tag = $.trim(input.val().replace(/ +/, ' ').replace(o.dregex, o.delimiter[0])); + if (!tag) { + if (old_tag && o.beforeTagDelete(el, ed, tag_list, old_tag) === false) { + input.val(old_tag).focus(); + blur_result = false; + update_globals(); + return; + } + try { input.closest('li').remove(); } catch(e){} + if (old_tag) update_globals(); + } + else if (tag.indexOf(o.delimiter[0])>=0) { split_cleanup(input); return; } + else if (tag != old_tag) { + if (o.forceLowercase) tag = tag.toLowerCase(); + cb_val = o.beforeTagSave(el, ed, tag_list, old_tag, tag); + tag = cb_val || tag; + if (cb_val === false) { + if (old_tag) { + input.val(old_tag).focus(); + blur_result = false; + update_globals(); + return; + } + try { input.closest('li').remove(); } catch(e){} + if (old_tag) update_globals(); + } + // remove duplicates + else if (o.removeDuplicates) + $('.tag-editor-tag:not(.active)', ed).each(function(){ if ($(this).text() == tag) $(this).closest('li').remove(); }); + } + input.parent().html(escape(tag)).removeClass('active'); + if (tag != old_tag) update_globals(); + set_placeholder(); + }); + + var pasted_content; + ed.on('paste', 'input', function(e){ + $(this).removeAttr('maxlength'); + pasted_content = $(this); + setTimeout(function(){ split_cleanup(pasted_content); }, 30); + }); + + // keypress delimiter + var inp; + ed.on('keypress', 'input', function(e){ + if (o.delimiter.indexOf(String.fromCharCode(e.which))>=0) { + inp = $(this); + setTimeout(function(){ split_cleanup(inp); }, 20); + } + }); + + ed.on('keydown', 'input', function(e){ + var $t = $(this); + + // left/up key + backspace key on empty field + if ((e.which == 37 || !o.autocomplete && e.which == 38) && !$t.caret() || e.which == 8 && !$t.val()) { + var prev_tag = $t.closest('li').prev('li').find('.tag-editor-tag'); + if (prev_tag.length) prev_tag.click().find('input').caret(-1); + else if ($t.val() && !(o.maxTags && ed.data('tags').length >= o.maxTags)) $(new_tag).insertBefore($t.closest('li')).find('.tag-editor-tag').click(); + return false; + } + // right/down key + else if ((e.which == 39 || !o.autocomplete && e.which == 40) && ($t.caret() == $t.val().length)) { + var next_tag = $t.closest('li').next('li').find('.tag-editor-tag'); + if (next_tag.length) next_tag.click().find('input').caret(0); + else if ($t.val()) ed.click(); + return false; + } + // tab key + else if (e.which == 9) { + // shift+tab + if (e.shiftKey) { + var prev_tag = $t.closest('li').prev('li').find('.tag-editor-tag'); + if (prev_tag.length) prev_tag.click().find('input').caret(0); + else if ($t.val() && !(o.maxTags && ed.data('tags').length >= o.maxTags)) $(new_tag).insertBefore($t.closest('li')).find('.tag-editor-tag').click(); + // allow tabbing to previous element + else { + el.attr('disabled', 'disabled'); + setTimeout(function(){ el.removeAttr('disabled'); }, 30); + return; + } + return false; + // tab + } else { + var next_tag = $t.closest('li').next('li').find('.tag-editor-tag'); + if (next_tag.length) next_tag.click().find('input').caret(0); + else if ($t.val()) ed.click(); + else return; // allow tabbing to next element + return false; + } + } + // del key + else if (e.which == 46 && (!$.trim($t.val()) || ($t.caret() == $t.val().length))) { + var next_tag = $t.closest('li').next('li').find('.tag-editor-tag'); + if (next_tag.length) next_tag.click().find('input').caret(0); + else if ($t.val()) ed.click(); + return false; + } + // enter key + else if (e.which == 13) { + ed.trigger('click', [$t.closest('li').next('li').find('.tag-editor-tag')]); + + // trigger blur if maxTags limit is reached + if (o.maxTags && ed.data('tags').length >= o.maxTags) ed.find('input').blur(); + + return false; + } + // pos1 + else if (e.which == 36 && !$t.caret()) ed.find('.tag-editor-tag').first().click(); + // end + else if (e.which == 35 && $t.caret() == $t.val().length) ed.find('.tag-editor-tag').last().click(); + // esc + else if (e.which == 27) { + $t.val($t.data('old_tag') ? $t.data('old_tag') : '').blur(); + return false; + } + }); + + // create initial tags + var tags = o.initialTags.length ? o.initialTags : el.val().split(o.dregex); + for (var i=0; i= o.maxTags) break; + var tag = $.trim(tags[i].replace(/ +/, ' ')); + if (tag) { + if (o.forceLowercase) tag = tag.toLowerCase(); + tag_list.push(tag); + ed.append('
    22.  '+o.delimiter[0]+'
      '+escape(tag)+'
    23. '); + } + } + update_globals(true); // true -> no onChange callback + + // init sortable + if (o.sortable && $.fn.sortable) ed.sortable({ + distance: 5, cancel: '.tag-editor-spacer, input', helper: 'clone', + update: function(){ update_globals(); } + }); + }); + }; + + $.fn.tagEditor.defaults = { + initialTags: [], + maxTags: 0, + maxLength: 50, + delimiter: ',;', + placeholder: '', + forceLowercase: true, + removeDuplicates: true, + clickDelete: false, + animateDelete: 175, + sortable: true, // jQuery UI sortable + autocomplete: null, // options dict for jQuery UI autocomplete + + // callbacks + onChange: function(){}, + beforeTagSave: function(){}, + beforeTagDelete: function(){} + }; +}(jQuery)); diff --git a/static/libjs/jQuery-tagEditor/jquery.tag-editor.min.js b/static/libjs/jQuery-tagEditor/jquery.tag-editor.min.js new file mode 100644 index 00000000..1d083d24 --- /dev/null +++ b/static/libjs/jQuery-tagEditor/jquery.tag-editor.min.js @@ -0,0 +1,3 @@ +// jQuery tagEditor v1.0.20 +// https://github.com/Pixabay/jQuery-tagEditor +!function(t){t.fn.tagEditorInput=function(){var e=" ",i=t(this),a=parseInt(i.css("fontSize")),r=t("").css({position:"absolute",top:-9999,left:-9999,width:"auto",fontSize:i.css("fontSize"),fontFamily:i.css("fontFamily"),fontWeight:i.css("fontWeight"),letterSpacing:i.css("letterSpacing"),whiteSpace:"nowrap"}),l=function(){if(e!==(e=i.val())){r.text(e);var t=r.width()+a;20>t&&(t=20),t!=i.width()&&i.width(t)}};return r.insertAfter(i),i.bind("keyup keydown focus",l)},t.fn.tagEditor=function(e,a,r){function l(t){return t.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}var n,o=t.extend({},t.fn.tagEditor.defaults,e),c=this;if(o.dregex=new RegExp("["+o.delimiter.replace("-","-")+"]","g"),"string"==typeof e){var s=[];return c.each(function(){var i=t(this),l=i.data("options"),n=i.next(".tag-editor");if("getTags"==e)s.push({field:i[0],editor:n,tags:n.data("tags")});else if("addTag"==e){if(l.maxTags&&n.data("tags").length>=l.maxTags)return!1;t('
    24.  '+l.delimiter[0]+'
    25. ').appendTo(n).find(".tag-editor-tag").html('').addClass("active").find("input").val(a).blur(),r?t(".placeholder",n).remove():n.click()}else"removeTag"==e?(t(".tag-editor-tag",n).filter(function(){return t(this).text()==a}).closest("li").find(".tag-editor-delete").click(),r||n.click()):"destroy"==e&&i.removeClass("tag-editor-hidden-src").removeData("options").off("focus.tag-editor").next(".tag-editor").remove()}),"getTags"==e?s:this}return window.getSelection&&t(document).off("keydown.tag-editor").on("keydown.tag-editor",function(e){if(8==e.which||46==e.which||e.ctrlKey&&88==e.which){try{var a=getSelection(),r="BODY"==document.activeElement.tagName?t(a.getRangeAt(0).startContainer.parentNode).closest(".tag-editor"):0}catch(e){r=0}if(a.rangeCount>0&&r&&r.length){var l=[],n=a.toString().split(r.prev().data("options").dregex);for(i=0;i
      '+o.placeholder+"
      ")}function i(i){var a=c.toString();c=t(".tag-editor-tag:not(.deleted)",s).map(function(e,i){var a=t.trim(t(this).hasClass("active")?t(this).find("input").val():t(i).text());return a?a:void 0}).get(),s.data("tags",c),r.val(c.join(o.delimiter[0])),i||a!=c.toString()&&o.onChange(r,s,c),e()}function a(e){for(var a,n=e.closest("li"),d=e.val().replace(/ +/," ").split(o.dregex),g=e.data("old_tag"),f=c.slice(0),h=!1,u=0;u
       '+o.delimiter[0]+'
      '+l(v)+'
      '),o.maxTags&&f.length>=o.maxTags)){h=!0;break}e.attr("maxlength",o.maxLength).removeData("old_tag").val(""),h?e.blur():e.focus(),i()}var r=t(this),c=[],s=t("
        ').insertAfter(r);r.addClass("tag-editor-hidden-src").data("options",o).on("focus.tag-editor",function(){s.click()}),s.append('
      •  
      • ');var d='
      •  '+o.delimiter[0]+'
      • ';s.click(function(e,i){var a,r,l=99999;if(!window.getSelection||""==getSelection())return o.maxTags&&s.data("tags").length>=o.maxTags?(s.find("input").blur(),!1):(n=!0,t("input:focus",s).blur(),n?(n=!0,t(".placeholder",s).remove(),i&&i.length?r="before":t(".tag-editor-tag",s).each(function(){var n=t(this),o=n.offset(),c=o.left,s=o.top;e.pageY>=s&&e.pageY<=s+n.height()&&(e.pageXa&&(l=a,i=n))}),"before"==r?t(d).insertBefore(i.closest("li")).find(".tag-editor-tag").click():"after"==r?t(d).insertAfter(i.closest("li")).find(".tag-editor-tag").click():t(d).appendTo(s).find(".tag-editor-tag").click(),!1):!1)}),s.on("click",".tag-editor-delete",function(){if(t(this).prev().hasClass("active"))return t(this).closest("li").find("input").caret(-1),!1;var a=t(this).closest("li"),l=a.find(".tag-editor-tag");return o.beforeTagDelete(r,s,c,l.text())===!1?!1:(l.addClass("deleted").animate({width:0},o.animateDelete,function(){a.remove(),e()}),i(),!1)}),o.clickDelete&&s.on("mousedown",".tag-editor-tag",function(a){if(a.ctrlKey||a.which>1){var l=t(this).closest("li"),n=l.find(".tag-editor-tag");return o.beforeTagDelete(r,s,c,n.text())===!1?!1:(n.addClass("deleted").animate({width:0},o.animateDelete,function(){l.remove(),e()}),i(),!1)}}),s.on("click",".tag-editor-tag",function(e){if(o.clickDelete&&(e.ctrlKey||e.which>1))return!1;if(!t(this).hasClass("active")){var i=t(this).text(),a=Math.abs((t(this).offset().left-e.pageX)/t(this).width()),r=parseInt(i.length*a),n=t(this).html('').addClass("active").find("input");if(n.data("old_tag",i).tagEditorInput().focus().caret(r),o.autocomplete){var c=t.extend({},o.autocomplete),d="select"in c?o.autocomplete.select:"";c.select=function(e,i){d&&d(e,i),setTimeout(function(){s.trigger("click",[t(".active",s).find("input").closest("li").next("li").find(".tag-editor-tag")])},20)},n.autocomplete(c)}}return!1}),s.on("blur","input",function(d){d.stopPropagation();var g=t(this),f=g.data("old_tag"),h=t.trim(g.val().replace(/ +/," ").replace(o.dregex,o.delimiter[0]));if(h){if(h.indexOf(o.delimiter[0])>=0)return void a(g);if(h!=f)if(o.forceLowercase&&(h=h.toLowerCase()),cb_val=o.beforeTagSave(r,s,c,f,h),h=cb_val||h,cb_val===!1){if(f)return g.val(f).focus(),n=!1,void i();try{g.closest("li").remove()}catch(d){}f&&i()}else o.removeDuplicates&&t(".tag-editor-tag:not(.active)",s).each(function(){t(this).text()==h&&t(this).closest("li").remove()})}else{if(f&&o.beforeTagDelete(r,s,c,f)===!1)return g.val(f).focus(),n=!1,void i();try{g.closest("li").remove()}catch(d){}f&&i()}g.parent().html(l(h)).removeClass("active"),h!=f&&i(),e()});var g;s.on("paste","input",function(){t(this).removeAttr("maxlength"),g=t(this),setTimeout(function(){a(g)},30)});var f;s.on("keypress","input",function(e){o.delimiter.indexOf(String.fromCharCode(e.which))>=0&&(f=t(this),setTimeout(function(){a(f)},20))}),s.on("keydown","input",function(e){var i=t(this);if((37==e.which||!o.autocomplete&&38==e.which)&&!i.caret()||8==e.which&&!i.val()){var a=i.closest("li").prev("li").find(".tag-editor-tag");return a.length?a.click().find("input").caret(-1):!i.val()||o.maxTags&&s.data("tags").length>=o.maxTags||t(d).insertBefore(i.closest("li")).find(".tag-editor-tag").click(),!1}if((39==e.which||!o.autocomplete&&40==e.which)&&i.caret()==i.val().length){var l=i.closest("li").next("li").find(".tag-editor-tag");return l.length?l.click().find("input").caret(0):i.val()&&s.click(),!1}if(9==e.which){if(e.shiftKey){var a=i.closest("li").prev("li").find(".tag-editor-tag");if(a.length)a.click().find("input").caret(0);else{if(!i.val()||o.maxTags&&s.data("tags").length>=o.maxTags)return r.attr("disabled","disabled"),void setTimeout(function(){r.removeAttr("disabled")},30);t(d).insertBefore(i.closest("li")).find(".tag-editor-tag").click()}return!1}var l=i.closest("li").next("li").find(".tag-editor-tag");if(l.length)l.click().find("input").caret(0);else{if(!i.val())return;s.click()}return!1}if(!(46!=e.which||t.trim(i.val())&&i.caret()!=i.val().length)){var l=i.closest("li").next("li").find(".tag-editor-tag");return l.length?l.click().find("input").caret(0):i.val()&&s.click(),!1}if(13==e.which)return s.trigger("click",[i.closest("li").next("li").find(".tag-editor-tag")]),o.maxTags&&s.data("tags").length>=o.maxTags&&s.find("input").blur(),!1;if(36!=e.which||i.caret()){if(35==e.which&&i.caret()==i.val().length)s.find(".tag-editor-tag").last().click();else if(27==e.which)return i.val(i.data("old_tag")?i.data("old_tag"):"").blur(),!1}else s.find(".tag-editor-tag").first().click()});for(var h=o.initialTags.length?o.initialTags:r.val().split(o.dregex),u=0;u=o.maxTags);u++){var v=t.trim(h[u].replace(/ +/," "));v&&(o.forceLowercase&&(v=v.toLowerCase()),c.push(v),s.append('
      •  '+o.delimiter[0]+'
        '+l(v)+'
      • '))}i(!0),o.sortable&&t.fn.sortable&&s.sortable({distance:5,cancel:".tag-editor-spacer, input",helper:"clone",update:function(){i()}})})},t.fn.tagEditor.defaults={initialTags:[],maxTags:0,maxLength:50,delimiter:",;",placeholder:"",forceLowercase:!0,removeDuplicates:!0,clickDelete:!1,animateDelete:175,sortable:!0,autocomplete:null,onChange:function(){},beforeTagSave:function(){},beforeTagDelete:function(){}}}(jQuery); \ No newline at end of file diff --git a/static/libjs/jQuery-tagEditor/readme.md b/static/libjs/jQuery-tagEditor/readme.md new file mode 100644 index 00000000..8990f89d --- /dev/null +++ b/static/libjs/jQuery-tagEditor/readme.md @@ -0,0 +1,119 @@ +jQuery-tagEditor +================ + +A powerful and lightweight tag editor plugin for jQuery. + +Compatible with jQuery 1.7.0+ in Firefox, Safari, Chrome, Opera, Internet Explorer 8+. IE7 technically works, but no care has gone into CSS/layout bugs. +Released under the MIT License: http://www.opensource.org/licenses/mit-license.php + +This plugin was developed by and for [Pixabay.com](https://pixabay.com/) - an international repository for sharing free public domain images. +We have implemented this plugin in production and we share this piece of software - in the spirit of Pixabay - freely with others. + +## Demo and Documentation + +https://goodies.pixabay.com/jquery/tag-editor/demo.html + +## Features + +* Lightweight: 8.5 kB of JavaScript - less than 3.2 kB gzipped +* Edit in place tags +* Intuitive navigation between tags with cursor keys, Tab, Shift+Tab, Enter, Pos1, End, Backspace, Del, and ESC +* Optional jQuery UI sortable +* Optional jQuery UI autocomplete +* Copy-paste or delete multiple selected tags +* Duplicate tags check +* Custom delimiter/s +* Placeholder +* Custom style for faulty tags +* Public methods for reading, adding and removing tags + destroy function +* Callbacks +* Allows tabindex for form navigation +* Graceful degradation if JavaScript is disabled + +## Changelog + +### Version 1.0.20 - 2016/01/30 + +* Fixed #62: tagEditor is blocking key events on other input and textarea elements on page. + +### Version 1.0.19 - 2015/12/02 + +* Fixed #60: Tag editor fails to handle HTML operator chars. + +### Version 1.0.18 - 2015/08/12 + +* Pull #43: Escape HTML special characters on input. + +### Version 1.0.17 - 2015/07/14 + +* Allow beforeTagSave() to return `false` for discarding certain tag values. + +### Version 1.0.16 - 2015/07/01 + +* Fix #5, #35, #37, #38: "TypeError: owner is null" backspace browser history issue. + +### Version 1.0.15 - 2015/05/24 + +* Fix #31, #33, #34: Added maxTags, removeDuplicates, and animateDelete options. + +### Version 1.0.14 - 2015/04/05 + +* Fix #24: Auto-close tag after selecting autocomplete suggestion by mouse click. + +### Version 1.0.13 - 2015/01/26 + +* Fix #9: Added bower support. + +### Version 1.0.12 - 2015/01/16 + +* Fix #17: Make use of tabindex for form navigation. + +### Version 1.0.11 - 2015/01/08 + +* Use beforeTagSave return value for overwriting new tags. + +### Version 1.0.10 - 2015/01/04 + +* Fix for IE8 + +### Version 1.0.9 - 2014/12/17 + +* Optimized internal input autogrow function. + +### Version 1.0.8 - 2014/12/14 + +* Added bower.json file. + +### Version 1.0.7 - 2014/11/26 + +* Removing accursoft's caret plugin (http://code.accursoft.com/caret) from tagEditor source (and adding caret as a dependency). + +### Version 1.0.6 - 2014/10/22 + +* Fixed: Detection for selected field (.tag-editor) on backspace/delete keypress failed in some cases. + +### Version 1.0.5 - 2014/09/30 + +* Merged pull - Added logic for selected field to be .tag-editor only: + https://github.com/jegarts/jQuery-tagEditor/commit/498435b562d72c3e502863312b0b2ccbb9e80cab + +### Version 1.0.4 - 2014/09/24 + +* Merged pull stop aco.select form calling itself: + https://github.com/jegarts/jQuery-tagEditor/commit/fd0340ba46272290cedc8991f58769945d0fc2c2 + +### Version 1.0.3 - 2014/09/13 + +* Removed unnecessary vendor prefixes in CSS stylesheet. + +### Version 1.0.2 - 2014/07/16 + +* Fixed removal of placeholder after calling addTags. + +### Version 1.0.1 - 2014/07/16 + +* Fixed tagEditor for IE8 and IE7. IE7 still has some obvious layout alignment bugs, that can be fixed by conditional CSS rules. + +### Version 1.0.0-beta - 2014/07/15 + +* First release diff --git a/static/libjs/jQuery-tagEditor/tag-editor.jquery.json b/static/libjs/jQuery-tagEditor/tag-editor.jquery.json new file mode 100644 index 00000000..b39c0017 --- /dev/null +++ b/static/libjs/jQuery-tagEditor/tag-editor.jquery.json @@ -0,0 +1,31 @@ +{ + "name": "tag-editor", + "title": "tagEditor", + "description": "A powerful and lightweight tag editor plugin for jQuery.", + "version": "1.0.20", + "dependencies": { + "jquery": ">=1.7", + "caret": ">=1.3.2" + }, + "keywords": [ + "tags", + "keywords", + "editor", + "ui", + "tagging", + "jQuery" + ], + "author": { + "name": "Simon Steinberger", + "url": "https://pixabay.com/users/Simon/", + "email": "simon@pixabay.com" + }, + "licenses": [ + { + "type": "MIT", + "url": "http://www.opensource.org/licenses/mit-license.php" + } + ], + "homepage": "https://goodies.pixabay.com/jquery/tag-editor/demo.html", + "demo": "https://goodies.pixabay.com/jquery/tag-editor/demo.html" +} \ No newline at end of file diff --git a/static/libjs/jinplace-1.2.1.min.js b/static/libjs/jinplace-1.2.1.min.js new file mode 100644 index 00000000..30f81fa3 --- /dev/null +++ b/static/libjs/jinplace-1.2.1.min.js @@ -0,0 +1,12 @@ +/* + Copyright ? 2013, 2014 the jinplace team and contributors. + MIT Licence */ +(function(d,s,n,g){function k(b,a){var c=this.element=d(b),c=this.elementOptions(c);c.activator=d(c.activator||b);this.opts=c=d.extend({},d.fn.jinplace.defaults,a,c);this.bindElement(c)}var p="type url data loadurl elementId object attribute okButton cancelButton inputClass activator textOnly placeholder submitFunction".split(" ");k.prototype={elementOptions:function(b){function a(a){return"-"+a.toLowerCase()}var c={};d.each(p,function(d,f){c[f]=b.attr("data-"+f.replace(/[A-Z]/g,a))});c.elementId= +b.attr("id");c.textOnly&&(c.textOnly="false"!==c.textOnly);return c},bindElement:function(b){b.activator.off("click.jip").on("click.jip",d.proxy(this.clickHandler,this));var a=this.element;""==d.trim(a.html())&&(a.html(b.placeholder),b.placeholder=a.html())},clickHandler:function(b){b.preventDefault();b.stopPropagation();d(b.currentTarget).off("click.jip").on("click.jip",function(a){a.preventDefault()});var a=this,c=a.opts,e=d.extend({},l,d.fn.jinplace.editors[c.type]);a.origValue=a.element.html(); +a.fetchData(c).done(function(b){b=e.makeField(a.element,b);e.inputField||(e.inputField=b);b.addClass(c.inputClass);var d=q(c,b,e.buttonsAllowed);a.element.html(d);d.on("jip:submit submit",function(b){a.submit(e,c);return!1}).on("jip:cancel",function(b){a.cancel(e);return!1}).on("keyup",function(b){27==b.keyCode&&a.cancel(e)});e.activate(d,b);e.blurEvent(b,d,e.blurAction||(c.okButton?c.cancelButton?g:"jip:cancel":"submit"))})},fetchData:function(b){var a;a=b.data?b.data:b.loadurl?b.loadFunction(b): +b.textOnly?d.trim(this.element.text()):d.trim(this.element.html().replace(/&/gi,"&"));var c=function(a){return a==b.placeholder?"":a};a=d.when(a);return a.pipe?a.pipe(c):a.then(c)},cancel:function(b){this.element.html(this.origValue);b.finish();this.bindElement(this.opts)},submit:function(b,a){var c=this,e,f=d.Deferred().reject();try{e=a.submitFunction.call(g,a,b.value()),e===g&&(e=f)}catch(t){e=f}d.when(e).done(function(d,e,f){c.element.trigger("jinplace:done",[d,e,f]);c.onUpdate(b,a,d)}).fail(function(a, +d,e){c.element.trigger("jinplace:fail",[a,d,e]);c.cancel(b)}).always(function(a,b,d){c.element.trigger("jinplace:always",[a,b,d])})},onUpdate:function(b,a,c){this.setContent(c);b.finish();this.bindElement(a)},setContent:function(b){var a=this.element;b?this.opts.textOnly?a.text(b):a.html(b):a.html(this.opts.placeholder)}};var m=function(b,a){var c={id:b.elementId,object:b.object,attribute:b.attribute};d.isPlainObject(a)?d.extend(c,a):a!==g&&(c.value=a);return c};d.fn.jinplace=function(b){return this.each(function(){d.data(this, +"plugin_jinplace")||d.data(this,"plugin_jinplace",new k(this,b))})};d.fn.jinplace.defaults={url:n.location.pathname,type:"input",textOnly:!0,placeholder:"[ --- ]",submitFunction:function(b,a){return d.ajax(b.url,{type:"post",data:m(b,a),dataType:"text",headers:{"Cache-Control":"no-cache"}})},loadFunction:function(b){return d.ajax(b.loadurl,{data:m(b)})}};var q=function(b,a,c){a=d("
        ").attr("style","display: inline;").attr("action","javascript:void(0);").append(a);c&&r(a,b);return a},r=function(b, +a){var c=function(a,c){b.append(a);a.one("click",function(a){a.stopPropagation();b.trigger(c)})},e=a.okButton;e&&(e=d("").attr("type","button").attr("value",e).addClass("jip-button jip-ok-button"),c(e,"submit"));if(e=a.cancelButton)e=d("").attr("type","button").attr("value",e).addClass("jip-button jip-cancel-button"),c(e,"jip:cancel")},l={makeField:function(b,a){return d("").attr("type","text").val(a)},activate:function(b,a){a.focus()},value:function(){return this.inputField.val()}, +blurEvent:function(b,a,c){if(c&&"ignore"!=c)b.on("blur",function(d){var f=setTimeout(function(){b.trigger(c)},300);a.on("click",function(){clearTimeout(f)})})},finish:function(){}};d.fn.jinplace.editorBase=l;d.fn.jinplace.editors={input:{buttonsAllowed:!0},textarea:{buttonsAllowed:!0,makeField:function(b,a){return d("