diff --git a/browzos_options/ICON-OpenWorld.tar.bz2 b/browzos_options/ICON-OpenWorld.tar.bz2 new file mode 100644 index 0000000..7844d3c Binary files /dev/null and b/browzos_options/ICON-OpenWorld.tar.bz2 differ diff --git a/browzos_options/calc/calc4chem.css b/browzos_options/calc/calc4chem.css new file mode 100644 index 0000000..ecf40e1 --- /dev/null +++ b/browzos_options/calc/calc4chem.css @@ -0,0 +1,1285 @@ +/* ------------------------------------------ */ +/* ENIG. PERIODIC TABLE OF THE ELEMENTS */ +/* www.periodni.com - 8-2014 */ +/* ------------------------------------------ */ + +/* RESET */ +/*==============*/ +/* Global reset of paddings and margins for all HTML elements */ +/* based on Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) */ +html * { + position: relative; + margin: 0; + padding: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + + +/* Basic Styles */ +/*==============*/ + +body { + /*background-color: #fff;*/ + color: #000; + /* 87.5% = 14px 93.75% = 15px "Roboto",*/ + font: normal 87.5% "Helvetica Neue","Helvetica",Helvetica,Arial,sans-serif; + line-height: 1; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} + +img { + max-width: 100%; + height: auto; + display: inline-block; + vertical-align: middle; +} + +/* Default paragraph styles */ +p, li, table, th, td, dl, dd { + font-weight: normal; + font-size: 1em; + line-height: 1.4; + padding: 0.25em 0.5em;} +p, dd { + text-align: justify; +} + +/* Default Link Styles */ +a, a:visited { + color: #00c; + text-decoration: none; + background-color: transparent; + line-height: inherit; +} +a:hover, a:focus { + color: #c00; + outline: 0; +} +a img {border: none;} +a img:hover, a img:focus { + opacity: 0.75; +} + +/* Default header styles */ +h1, h2, h3, h4, h5, h6 { + color: #234567; + letter-spacing: 1px; + line-height: 1.25em; + font-weight: normal; + margin: 1.25em 0.5em 0.75em 0.5em; +} +/*h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {font-weight: inherit;}*/ +h1 { + font-size: 2em; + text-align: center; + text-transform: uppercase; +} +h2 {font-size: 1.5em;} +h3 {font-size: 1.25em;} +h4 {font-size: 1.1em;} +h5, b, strong {font-weight: bold;} +h6, i, em {font-style: italic;} + +.h1-opis { + font-size: 1.2em; + margin-top: -0.75em; + margin-bottom: 1.5em; + color: #060; + text-align: center; +} + +/* Define sub and superscript */ +sup, sub { + font-size: 75%; + line-height: 0; +} +sup {top: -0.7em;} +sub {top: 0.35em;} + +hr { + height: 1px; + border: none; + color: #d4d4d4; + background-color: #d4d4d4; + margin: 0.5em; + clear:both; +} + +pre, code, .kod { + font: normal 87.5% Consolas, "Lucida Console", "Courier New", Courier; + overflow: auto; +} + +/* Default Table Styles */ +/* +table { + border-collapse: collapse; + border-spacing: 0; +} +*/ +table { + width: 100%; + margin: 0.5em 0; + background-color: #fff; +} +caption { + font-style: italic; + padding: 10px 0; +} + +td {border-bottom: 1px solid #ccc;} + +td a, th a { + display: block; +} + +/* Shading even or odd rows */ +/* even (2,4,6) odd (1,3,5) */ +tr:nth-child(odd), +.even tr:nth-child(even) { + background-color: #eee; +} +th { + font-size: 0.95em; + font-weight: bold; + background-color: #ddd; + border-bottom: 1px solid #aaa; + text-align: center; + vertical-align: middle; +} +/* Solid border after last row */ +/*tr:last-child td {border-bottom: 1px solid #aaa;}*/ + +.cacols th, .cacols td { + text-align: center; +} +.cols50 th {width: 50%} +.cols33 th {width: 33.33%} + +/* centriranje teksta u 1., 2., 3 odnosno 4. koloni */ +.ccol1 td:nth-child(1), +.ccol2 td:nth-child(2), +.ccol3 td:nth-child(3), +.ccol4 td:nth-child(4) { + text-align: center; +} + +/* centriranje teksta u 1., 2. odnosno 3. koloni */ +.lcol1 td:nth-child(1), +.lcol2 td:nth-child(2), +.lcol3 td:nth-child(3) { + text-align: left; +} + +/* centriranje teksta u 1., 2. odnosno 3. koloni */ +.rcol1 td:nth-child(1), +.rcol2 td:nth-child(2), +.rcol3 td:nth-child(3) { + text-align: right; +} +/* boldiranje teksta u 1., 2. odnosno 3. koloni */ +.bcol1 td:nth-child(1), +.bcol2 td:nth-child(2), +.bcol3 td:nth-child(3) { + font-weight: bold; +} + +ul, ol { + list-style-position: outside; + margin-left: 2em; +} + +/* Shading odd rows */ +/* even (2,4,6) odd (1,3,5) */ +.odd li:nth-child(odd), +.even li:nth-child(even) { + background-color: #eee; +} + +dt { + /*font-weight: bold;*/ +} + +dd { + margin-left: 1em; + margin-bottom: 1em; +} +/* Margin/padding reset caused too small select boxes. */ +option {padding-left: 0.4em;} +select {padding: 1px;} + +input[type="submit"] {padding: 0.15em;} +input[type="text"] {line-height: 1.4;} + + + +/* ------------------------------ */ +/* STRUCTURE AND MENU */ +/* ------------------------------ */ + +body { + background-color: #000000; +} + +/* Nested div-s need for absolute positioning */ +#headlayer { + max-width: 960px; + margin: 5px auto; + background-color: #fff; + border-top: 1px solid #ccc; + border-left: 1px solid #e6e6e6; + -webkit-border-radius: 1em; + -moz-border-radius: 1em; + border-radius: 1em; + -webkit-box-shadow: 5px 5px 5px #888; + -moz-box-shadow: 5px 5px 5px #888; + box-shadow: 5px 5px 5px #888; +} +#menulayer { + width: 100%; + background-color: #eef; + border-top: 1px solid #cacaba; + -webkit-border-radius: 1em; + -moz-border-radius: 1em; + border-radius: 1em; +} +#bodylayer { + margin-top: 2.25em; + padding: 5px; + width: 100%; + background-color: #fff; + border: 1px solid #ddd; + -moz-box-shadow: inset 0 0 10px #ddd; + -webkit-box-shadow: inset 0 0 10px #ddd; + box-shadow: inset 0 0 10px #ddd; +} + +/* ------------------------ */ +/* Main frame - data body */ +#ebody { + width: auto; + top: 0; + margin-right: 180px; + border: none; +} + +/* Grid - based on Foundation http://foundation.zurb.com/docs/ */ +.xrow { + width: 100%; + margin: 0; + padding: 0; +} +.xrow:before, .xrow:after { + content: " "; + display: block; +} +.xrow:after {clear: both;} + +[class^="ycols"] { + position: relative; + padding: 0; + margin: 0; + float: left; +} +.ycols1 {width: 8.33333%;} +.ycols2 {width: 16.66667%;} +.ycols3 {width: 25%;} +.ycols4 {width: 33.33333%;} +.ycols5 {width: 41.66667%;} +.ycols6 {width: 50%;} +.ycols7 {width: 58.33333%;} +.ycols8 {width: 66.66667%;} +.ycols9 {width: 75%;} +.ycols10 {width: 83.33333%;} +.ycols11 {width: 91.66667%;} +.ycols12 {width: 100%;} + + +.txt-l {text-align: left !important; } +.txt-r {text-align: right !important;} +.txt-c {text-align: center !important;} +.txt-j {text-align: justify !important;} + +.img-l { + float: left; + margin: 10px 20px 10px 10px; + z-index: 100; +} +.img-r { + float: right; + margin: 10px 10px 10px 20px; + z-index: 100; +} +.img-c { + text-align: center; + margin: 20px; +} +.img-caption, .img-p { + font-style: italic; + padding: 10px 0; +} + +.blok {display: inline-block;} +.div-l, .left {float: left !important;} +.div-r, .right {float: right !important;} +.div-c, .center { + float: none; + margin-left: auto; + margin-right: auto; +} + +.crven {color: #f00;} +.hide {display: none;} +.clear {clear: both;} +.clear-left {clear: left;} + +.mobonly {display: none;} + +/* ------------------------------ */ +/* PSE LOGO */ + +.logo { + padding-left: 8px; + float: left; + width: 350px; +} +.logo img { + margin: 8px; + float: left; + z-index: 10; +} +.logo ul { + padding-top: 24px; + list-style-type: none; +} +.logo li { + padding: 1px 0 1px 0; + letter-spacing: 1px; + font-size: 0.9em; + font-style: italic; + color: #262; + background-color: #fff; +} +.logo a { + display: block; + color: #262; +} +/*.logo li:nth-child(4) {color: #373;}*/ +/*.logo li:nth-child(5) {color: #484;}*/ + +.cglogo img { + width:auto; + padding: 8px 15px; + float: left; +} +.h1-logo { + display: inline-block; + font: 2.25em "Georgia","Times New Roman",serif; + letter-spacing: 1px; + color: #ddd; + padding-top: 30px; + margin: 0; + text-align: left; + text-shadow: 2px 2px 2px #888; +} + +.social {padding: 5px;} +.social img { + margin: 5px; + padding: 3px; +} +/*.social img:hover {opacity: 0.8;}*/ + + +.gotop { + border-top: 1px solid #d4d4d4; + border-bottom: 1px solid #d4d4d4; + /*padding: 0.5em;*/ +} +/* +.gotop .ycols6:first-child { + margin: 0.5em 0; +} +*/ +.gotop [class^="ycols"] { + padding: 0.5em; +} + +.gotop img { + margin: 0.5em; + float: left; +} + +.gotop p {float: right;} +.gotop ul {margin-left: 0;} +.gotop li { + float: left; + list-style: none; +} + +.citat { + font-size: 0.95em; + color: #226622; + text-align: left; + /* Avoid text overflow */ + word-wrap: break-word; + word-break: break-all; +} +.copyme { + text-align: center; + color: #99b; + font-size: 0.85em; + margin-top: 15px; + } +.copyme a:link {color: #a8a9b9} +.copyme a:hover {color: #f00} + + +/* --------------------- */ +/* MAIN MENU */ +/* --------------------- */ +.topmenu { + position: absolute; + top: 0; + font-size: 0.90em; +} +#leftnav, #rightnav { + margin: 0; + padding: 0; + float: left; + z-index: 30; +} + +#rightnav { + position: absolute; + right: 0; +} + +#menu {display: none;} + +#leftnav li , #rightnav li { + float: left; + list-style: none; +} + +/* main level link */ +#leftnav a, #rightnav a { + color: #567; + text-decoration: none; + display: block; + padding: 0.25em 0.35em; + border: 1px solid transparent; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} + +/* main level link hover */ +#leftnav li:hover > a, #rightnav li:hover > a { + background-color: #eef; + color: #444; + border-top: 1px solid #f8f8f8; + -moz-box-shadow: 3px 1px 3px #888; + -webkit-box-shadow: 3px 1px 3px #888; + box-shadow: 3px 1px 3px #888; +} + +#leftnav ul a:hover, #rightnav ul a:hover { + background-color: #0078ff !important; + color: #fff !important; + border-top: 1px solid #0078ff; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +/* dropdown */ +#leftnav li:hover > ul, #rightnav li:hover > ul { + display: block; + margin-top: 2px; + left: 7px; +} +#leftnav ul li:hover > ul { + display: block; + margin: 0px; + top: 0px; + left: 175px; +} +#rightnav li:hover > ul { + left: auto; + right: 7px; + width: 175px; +} + +/* level 2 list box */ +#leftnav ul, #rightnav ul { + display: none; + position: absolute; + margin: 0; + padding: 0; + width: 200px; + background-color: #eef; + border: 1px solid #b4b4b4; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + -moz-box-shadow: 5px 5px 5px #888; + -webkit-box-shadow: 5px 5px 5px #888; + box-shadow: 5px 5px 5px #888; + z-index: 20; +} +#leftnav ul li, #rightnav ul li { + float: none; + margin: 0; + padding: 0; + border-top: 1px solid #ddd; +} +#leftnav ul li.separator, #rightnav ul li.separator { + padding-bottom: 4px; + border-bottom: 1px solid #888; +} + +/* rounded corners of first and last link */ +#leftnav ul li:first-child > a, +#rightnav ul li:first-child > a { + -webkit-border-top-left-radius: 10px; + -moz-border-top-left-radius: 10px; + border-top-left-radius: 10px; + -webkit-border-top-right-radius: 10px; + -moz-border-top-right-radius: 10px; + border-top-right-radius: 10px; +} +#leftnav ul li:last-child > a, +#rightnav ul li:last-child > a { + -webkit-border-bottom-left-radius: 10px; + -moz-border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + -webkit-border-bottom-right-radius: 10px; + -moz-border-bottom-right-radius: 10px; + border-bottom-left-radius: 10px; +} + +/* images in language list */ +#lang img {padding-bottom: 3px;} + +#lang ul > li { + padding-left: 25px; + margin: 0 4px; + width: 98%; +} +#edata ul > li { + width: 100%; +} + +#lang li:nth-child(1) {background: url("http://www.periodni.com/slike/s_de.jpg") no-repeat left center;} +#lang li:nth-child(2) {background: url("http://www.periodni.com/slike/s_en.jpg") no-repeat left center;} +#lang li:nth-child(3) {background: url("http://www.periodni.com/slike/s_fr.jpg") no-repeat left center;} +#lang li:nth-child(4) {background: url("http://www.periodni.com/slike/s_hr.jpg") no-repeat left center;} +#lang li:nth-child(5) {background: url("http://www.periodni.com/slike/s_it.jpg") no-repeat left center;} +#lang li:nth-child(6) {background: url("http://www.periodni.com/slike/s_es.jpg") no-repeat left center;} + +/* remove rounded corners of first and last language link */ +#lang ul li:first-child > a { + -webkit-border-top-left-radius: 0; + -moz-border-top-left-radius: 0; + border-top-left-radius: 0; +} +#lang ul li:last-child > a { + -webkit-border-bottom-left-radius: 0; + -moz-border-bottom-left-radius: 0; + border-bottom-left-radius: 0; +} + +/* clearfix */ +#leftnav:after, #rightnav:after, #row:after , .h1-opis:after { + content: "."; + display: block; + clear: both; + visibility: hidden; + line-height: 0; + height: 0; +} + + +/* ---------------------- */ +/* Bottom menu */ + +.bottomenu { + width: 50%; + float: left; + font-size: 0.85em; + padding: 1em; +} +.bottomenu p { + font-weight: bold; + letter-spacing: 1px; + text-transform: uppercase; +} +.bottomenu ul + p { + padding-top: 2em; +} +.bottomenu ul {margin-left: 1em;} +.bottomenu li { + padding: 0.4em 0; + color: #444; + background-color: #eef; +} +.bottomenu li a {padding: 0;} + +/* ------------------------ */ +/* List navigation links */ + +.lilink {margin: 1px;} +.lilink .aktiv {background-color: #dde;} +.lilink ul {list-style-type: none;} +.lilink li { + display: inline-block; + /*width: 100%;*/ + padding: 0; + margin: 0 1px; + color: #444; + background-color: #eef; + -webkit-box-shadow: 3px 1px 3px #888; + -moz-box-shadow: 3px 1px 3px #888; + box-shadow: 3px 1px 3px #888; + text-align: center; + float: left; +} + +.lilink a, .lilink a:visited { + display: block; + color: #234; + width: 100%; + padding: 0.25em; + text-decoration: none; +} +.lilink li:active > a, .lilink li:hover > a { + background-color: #08f; + color: #fff; + border-style: none; +} + + +/* ------------------ */ +/* Google */ + +#gsearch { + position: absolute; + float: right; + top: 35px; + right: 20px; +} + +.googleup { + position: absolute; + width: 160px; + height: 600px; + top: 10px; + right:-172px; +} +.googledown { + width: 728px; + height: 90px; + margin: 24px auto; +} + + +/* ------------------ */ +/* EU Cookie Law */ +#cookie-law { + max-width:960px; + background:#eeeadd; + margin:5px auto 0; + border-radius: 15px; + -webkit-border-radius: 15px; + -moz-border-radius: 15px; +} +#cookie-law .tekst { + margin-right:120px; + padding:8px; + text-align:center; + color:#682008; +} +#cookie-law .botun { + position: absolute; + top: 15px; + right: 10px; +} +.close-cookie-banner { + width:110px; + text-align:center; + background:#fff; + padding:8px; + border: 1px solid #0cc; + border-radius: 10px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; +} + +/* -------------------------------- */ +/* Special for Math and Chemistry */ +.eq-l, .eq-c, .eq-tab { + line-height: 1.4; + padding: 0.25em 0.5em; + font-size: 1.1em; +} +.eq-l {text-align: left;} +.eq-c {text-align: center;} + +.eq-c i { + letter-spacing: 1px; +} +.eq-tab { + padding-left: 50px; + text-align: left; +} +.dblarrow { + font-size: 125%; + top: -0.4ex; + margin: 0 2px; +} +.dblarrow:after { + content:"\2190"; /* &larrow; */ + position: absolute; + left: 0; + top: 0.5ex; +} + + +/* Calc4Chem - Scientific calculator for chemists written by Eni Generalic - http://www.periodni.com/ */ +/* If you use a variant of this in your page, then please email me (enig@periodni.com) */ + +/* -------------- */ +/* Calc4Chem */ +/* -------------- */ + +#calc4chem { +position: relative; +top: 15px; +width: 576px; +height: 433px; +margin: auto; +border: 3px outset; +background: #e4e4e4; +text-align: center; +color: #eee; +border-radius: 10px; +} + +#kalkulator { + position: relative; + text-align: center; + background: #e4e4e4; + color: #fff; + margin: 8px; +} + +#biljeska { + position: absolute; + top: 4px; + left: 296px; + width: 256px; + height: 365px; + background: #e4e4e4; + color: #fff; +} + +#konstante { + position: absolute; + top: 11px; + left: 301px; + width: 264px; + height: 368px; + border: 1px ridge #999; + background: #fff; + color: #fff; + display: none; + text-align: center; + overflow: auto; +} + +#numformat { + position: absolute; + top: 132px; + width: 270px; + height: 278px; + border: 1px ridge #999; + border-radius: 6px; + display: none; + background: #c0d9d9; + text-align: center; + color: #000; +} + +.displej { + position: relative; + top: 0; + left: 0; + width: 265px; + height: 130px; + border: 3px ridge #eee; + background: #eee; + border-radius: 6px; +} + +.tipkovnica { + position: relative; + width: 275px; + height: 228px; + background: #e4e4e4; +} + +.zadatak { + position: relative; + width: 265px; + height: 30px; + border: 3px ridge #eee; + background: #fff; + padding-top: 1px; + border-radius: 6px; +} +.zadatak input[type="text"] { +width: 100%; +color: #060; +padding: 2px 5px 3px 5px; +font-size: 0.95em; +border: none; +} + +.tekstarea { + position: absolute; + top: 5px; + left: 4px; + width: 258px; + height: 367px; + padding: 0 0 0 5px; + border: 3px groove #ccc; + background: #fff; + font: normal 9pt/150% monospace; + color: #345678; + overflow: auto; +} + +.constnaslov { +width: 230px; +color: #00f; +text-align: center; +font: normal 12px/125% sans-serif; +padding: 8px; +border-bottom: 1px solid #cacaba; +} +.const { +width: 235px; +text-align: center; +font: 11px/125% sans-serif; +color: #900; +padding: 5px; +border-bottom: 1px solid #cacaba; +display: block; +cursor: pointer; +} +.const a:link { + display: block; + color: #900; + text-decoration: none; +} +.const a:visited { + display: block; + color: #036; + text-decoration: none; +} +.const a:active { + display: block; + color: #900; + text-decoration: none; +} +.const a:hover { + display: block; + background: #eaeafa; + color: #900; + text-decoration: none; +} + +.crta { + position: absolute; + top: 0; + left: 288px; + width: 1px; + height: 415px; + border: 1px inset; + background: #fff; + color: #fff; +} + +#numauto, #numsci, #numfix, #nummem, #numrad, #numdeg, #numgrad, #numspace { + position: relative; + float: left; + width: 27px; + height: 10px; + margin: 4px 0 1px 3px; + font: normal 9px sans-serif; + text-align: center; + color: #ccc; + background: #eee; + display: inline; + cursor: pointer; + border: none; +} +#numauto {color: #000;} +#numrad {color: #000;} +#nummem { + width: 44px; + margin-left: 12px; + margin-right: 12px; +} + +.displej a, .displej a:visited , .displej a:active { + color: inherit; + display: block; + text-decoration: none; + cursor:pointer; + } + +.displej a:hover { + color: #f00; + text-decoration: none; + cursor:pointer; + } + +.upit, .rezultat, .oldtask, .oldresult { + position: relative; + width: 256px; + background: #eee; + letter-spacing: 1px; + padding: 2px 4px; + margin: 0; + border: 0; + +} + +.upit, .oldtask { + text-align: left; + font: normal 9pt sans-serif; + color: #0a0; + padding-top: 6px; + background: #f1f1f1; + cursor: pointer; +} + +.rezultat, .oldresult { + text-align: right; + font: normal 11pt sans-serif; + color: #00a; + cursor: pointer; +} + +.formatbr { + position: absolute; + top: 55px; + left: 12px; + width: 245px; + height: 110px; + border: 1px ridge #777; + border-radius: 10px; + background: #e8e8e8; +} + +.formatnaslov { + position: relative; + margin-top: 15px; + font: normal 14pt cursive; + text-align: center; +} + +#automatik, #scienc, #fiksed { + position: absolute; + left: 6px; + font: normal 10pt sans-serif; + color: #000; +} +#automatik {top: 10px;} +#scienc {top: 44px;} +#fiksed {top: 78px;} + +.decnaslov { + position: absolute; + top: 10px; + left: 145px; + width: 110px; + text-align: center; + font: normal 10pt sans-serif; + color: #000; +} +.decimal { + position: absolute; + top: 32px; + left: 167px; + width: 60px; + height: 22px; +} +#separator { + position: absolute; + top: 78px; + left: 85px; + width: 150px; + text-align: right; + font: normal 10pt sans-serif; +} + +.degrad { + position: absolute; + top: 180px; + left: 12px; + width: 245px; + height: 25px; + padding-top: 3px; + border: 1px ridge #777; + border-radius: 10px; + background: #e8e8e8; +} + +#rad, #deg, #grad { + position: relative; + margin: 0 7px; + font: normal 10pt sans-serif; +} + +.cls { + position: absolute; + top: 220px; + left: 35px; + width: 90px; + height: 28px; + font: normal 9pt sans-serif; + color: #c00; +} + +.nfclose { + position: absolute; + top: 220px; + left: 140px; + height: 28px; + width: 90px; + font: normal 9pt sans-serif; + color: #000; +} +.n2copy { + position: absolute; + left: 0; + top: 260px; + width: 100%; + color: #aaa; + font: normal 9pt sans-serif; +} + +.n1, .n2, .n3 { + position: absolute; + top: 379px; + height: 28px; + width: 84px; + font: normal 9pt sans-serif; + color: #000; + } +.n1 { + left: 4px; +} +.n2 { + left: 91px; +} +.n3 { + left: 178px; +} + +.resetrow {clear: both;} +.spacerow { + clear: both; + height: 8px; +} + +.funbtn { + position: relative; + float: left; + height: 25px; + width: 45px; + padding: 0; + color: #234; + font: normal 8pt verdana; + } + +.numbtn, .modebtn, .plusbtn, .membtn, .expbtn, .enterbtn { + position: relative; + float: left; + height: 40px; + width: 45px; + padding: 0; + font: normal 10pt sans-serif; + color: #000; +} + +.modebtn, .enterbtn { + width: 90px; + } + +.membtn { + color: #a00; + } + +.plusbtn { + font: normal 14pt sans-serif; + color: #00f; + } + +.numbtn { + font: normal 12pt verdana; + color: #262; + } + + +/* ------------------------ */ +/* MEDIA */ +/* ------------------------ */ + +/* --- TABLET --- */ +@media only screen and (max-width: 767px) { + + /*.ycols1, + .ycols2, + .ycols3, + .ycols4, + .ycols5, + .ycols6, + .ycols7, + .ycols8, + .ycols9, + .ycols10, + .ycols11, + .ycols12 {width: 100%;}*/ + [class^="ycols"] { + width: 100%; + } + + .fix1 {width: 8.33333% !important;} + .fix2 {width: 16.66667% !important;} + .fix3 {width: 25% !important;} + .fix4 {width: 33.33333% !important;} + .fix5 {width: 41.66667% !important;} + .fix6 {width: 50% !important;} + .fix7 {width: 58.33333% !important;} + .fix8 {width: 66.66667% !important;} + .fix9 {width: 75% !important;} + .fix10 {width: 83.33333% !important;} + .fix11 {width: 91.66667% !important;} + .fix12 {width: 100% !important;} + + body { + /* 87.5% = 14px 93.75% = 15px 100% = 16px */ + font-size: 100%; + } + + .mobhide {display: none;} + .mobonly { + display: block; + font-size: 1rem; + } + + #ebody {margin: auto;} + #rightnav {font-size: 1rem;} + + .bottnav li {width: 32.5%;} + .bottomenu div + div {padding-top: 2em;} + + #gsearch { + position: relative; + display: table; + float: none; + top: auto; + right: auto; + margin: 10px auto; + } + + .googleup, .googledown { + position: relative; + width: 100%; + height: auto; + top: 0; + left: 0; + text-align: center; + margin: 15px 0; + } + .cglogo {text-align: center;} + .cglogo img {float: none;} + .h1-logo { + display: block; + font-size: 1.75em; + padding: 0 0 10px 0; + text-align: center; + } + h1 {font-size: 1.5em;} + h2 {font-size: 1.2em;} + h3 {font-size: 1em;} + +} + + +/* --- MOBITEL STYLES --- */ +@media only screen and (max-width: 485px) { + + /* + .tabhide {display: none;} + .tabshow {display: block;} + .mobhide {display: none;} + .mobonly {display: block;} + */ + .img-l, .img-r { + text-align: center !important; + float: none !important; + } + #rightnav { + position: relative; + float: right; + } + #rightnav a {padding: 0.25em 0.25em;} + +} + + +/* --- PRINT STYLES --- */ +@media print { + .noprint {display: none !important;} + .print-only {display: block !important;} + + #gsearch, .googleup, .googledown, .bottomenu, .mobonly { + display: none; + } + #ebody {margin: auto;} + + pre, blockquote { + border: 1px solid #999999; + page-break-inside: avoid; + } + + thead {display: table-header-group;} + + tr, img { + page-break-inside: avoid; + } + + img {max-width: 100% !important;} + + p, h2, h3 { + orphans: 3; + widows: 3; + } + + h2, h3 { + page-break-after: avoid; + } +} + diff --git a/browzos_options/calc/calc4chem.js b/browzos_options/calc/calc4chem.js new file mode 100644 index 0000000..4ca28e3 --- /dev/null +++ b/browzos_options/calc/calc4chem.js @@ -0,0 +1,964 @@ +// Scientific Calculator for Chemists written by Eni Generalic +// URL: http://www.periodni.com/scientific_calculator.html +// Create: 1999/10/14; Update: 2014/07/31 +// If you use a variant of this in your page, please email me at enig@periodni.com +// Atomic Weights of the Elements 2007, Pure & Appl. Chem., Vol. 80, No. 11, (2009) 2131-2156. + +var EniG = ""; +//Auto Sci Fix +var asf = 0; +var decimala = 2; +// Rad Deg Grad +var rdg = 0; +// Digit Grouping +var dg = 0; +var pi = 3.14159265358979324; +var e = 2.7182818284590452; + +window.onload = start(); + +function start(){ + var dan = new Date(); + document.racunalo.notes.value += " " + dan.toLocaleString() + "\n\n"; + document.racunalo.zadatak.focus(); +} + +function Memory(operator) { + + switch(operator) { + case 1: // MS + var memorija = document.racunalo.rezultat.value; + if (memorija==0 || ChemSlovo(memorija.charAt(0))) {memorija = ""} + + document.racunalo.nummem.title = memorija; + + if (memorija.length) { + document.getElementById('nummem').style.color = '#000'; + } + else { + document.getElementById('nummem').style.color = '#ccc'; + } + break; + case 2: // MR + var memorija = document.racunalo.nummem.title; + + DodajBroj(memorija); + break; + case 3: // CLS + var dan = new Date(); + + document.racunalo.notes.value = EniG; + document.racunalo.notes.value += "\n " + dan.toLocaleString() + "\n\n"; + break; + case 4: // Mode + vidi('numformat'); + break; + case 5: // Reset + document.racunalo.reset(); + + var dan = new Date(); + document.racunalo.notes.value += " " + dan.toLocaleString() + "\n\n"; + + decimala = 2; + asf = 0; + rdg = 0; + dg = 0; + document.racunalo.nummem.title = ""; + document.racunalo.oldresult.title = "0"; + document.racunalo.rezultat.title = "0"; + + document.getElementById('numauto').style.color = '#000'; + document.getElementById('numsci').style.color = '#ccc'; + document.getElementById('numfix').style.color = '#ccc'; + document.getElementById('nummem').style.color = '#ccc'; + document.getElementById('numrad').style.color = '#000'; + document.getElementById('numdeg').style.color = '#ccc'; + document.getElementById('numgrad').style.color = '#ccc'; + + document.getElementById('numformat').style.display = 'none'; + break; + } + + document.racunalo.zadatak.focus(); +} + +function DodajBroj(noviznak) { + var selstart = document.racunalo.zadatak.selectionStart; + var selend = document.racunalo.zadatak.selectionEnd; + var strinput = document.racunalo.zadatak.value; + + if (selstart || selend) { + document.racunalo.zadatak.value = strinput.slice(0, selstart) + noviznak + strinput.slice(selend); + selstart += noviznak.length; + document.racunalo.zadatak.setSelectionRange(selstart, selstart); + } + else { + // IE before version 9 have undefined selstart + document.racunalo.zadatak.value = strinput + noviznak + } + document.racunalo.zadatak.focus(); +} + + +function Izracunaj(funkcija) { + var pitanje = ""; + var odgovor = "0"; + var chem = false; + var math = false; + //var mem = 0; + var mm = ""; + var mmup = ""; + var mmdn = ""; + + document.racunalo.oldtask.value = document.racunalo.upit.value; + document.racunalo.oldresult.value = document.racunalo.rezultat.value; + document.racunalo.oldresult.title = document.racunalo.rezultat.title; + + var zadatak = document.racunalo.zadatak.value; +//Error handling in JavaScript + try + { + // Remove all spaces from expression + //zadatak = zadatak.replace(/ /g,''); + zadatak = Cleaning(zadatak); + + if (zadatak.length==0) { + if (funkcija > 1) { + zadatak = document.racunalo.rezultat.value; + } + else { + document.racunalo.upit.value = ""; + document.racunalo.rezultat.value = ""; + document.racunalo.rezultat.title = "0"; + document.racunalo.zadatak.focus(); + return; + } + } + else if (UbaciRezultat(zadatak.charAt(0))) { + odgovor = document.racunalo.rezultat.value; + if (odgovor.length==0) { + //document.racunalo.rezultat.title = "0"; + document.racunalo.zadatak.focus(); + return; + } + zadatak = document.racunalo.rezultat.value + zadatak; + } + + // Cleaning input text + for (var i=0; i 0) mmdn = zadatak.charAt(i-1); + if (i < zadatak.length-1) mmup = zadatak.charAt(i+1); + + if (mm == ",") {mm = "."} + else if (mm == "}" || mm == "]") {mm = ")"} + else if (mm == "{" || mm == "[") {mm = "("} + //else if (mm == " " || mm == "=") {mm = ""} + else if (mm == String.fromCharCode(247)) {mm = "/"} + else if (mm == String.fromCharCode(215)) {mm = "*"} + else if (mm == "*" && mmup == "*") {mm = "^"; i++} + else if (mm == "+" && mmup == "-") {mm = "-"; i++} + else if (mm == "E" && KemSimbol(mmup)) {mm = "e"} + //else if (mm == "$" || mm == "&" || mm == "@") {mm = ""} + //else if (mm == "#" || mm == "?" || mm == "=") {mm = ""} + + if (mm == "." && BrojAtoma(mmdn)==false) {mm = "0."} + else if (VelikoSlovo(mm)) {chem = true} + else if (Operator(mm)) {math = true} + + if (pitanje == "0") { + if (Operator(mm)) {} + else if (mm != ".") {pitanje = ""} + } + + pitanje += mm; + } + + if (funkcija > 1) { + if (math) {pitanje = "(" + pitanje + ")"} + pitanje = DodajFunkcije(funkcija, pitanje); + } + + if (chem) { + odgovor = MolnaMasa(pitanje); + } + else { + odgovor = CalcZagrada(pitanje); + } + + document.racunalo.upit.value = pitanje; + + + //odgovor = odgovor.toString(); + document.racunalo.rezultat.title = odgovor; + + //odgovor = IzgledBroja(odgovor); + //document.racunalo.rezultat.value = odgovor; + NumberFormat(0); + + document.racunalo.zadatak.value = ""; + document.racunalo.zadatak.focus(); + } + catch(err) { + + var txt = "There was an error on this page.\n\n"; + txt += "Error description: " + err.message + "\n\n"; + txt += "Click OK to continue.\n\n"; + alert(txt); + + document.racunalo.zadatak.value = pitanje; + document.racunalo.zadatak.focus(); + } + +} + + +function DodajFunkcije(funkcija, pitanje) { + + switch(funkcija) { + case 2: // Square + pitanje = pitanje + "^2"; + break; + case 3: // Square root + pitanje = pitanje + "^(1/2)"; + break; + case 4: // Sign change + pitanje = pitanje + "*(-1)"; + break; + case 5: // Natural logarithm + pitanje = "ln" + pitanje; + break; + case 6: // Natural antilogarithm + pitanje = "e^" + pitanje; + break; + case 7: // Reciprocal + pitanje = "1/" + pitanje; + break; + case 8: // Common logarithm + pitanje = "log" + pitanje; + break; + case 9: // Common antilogarithm + pitanje = "10^" + pitanje; + break; + case 10: // Arc tangent + pitanje = "atan" + pitanje; + break; + case 11: // Arc cosine + pitanje = "acos" + pitanje; + break; + case 12: // Arc sine + pitanje = "asin" + pitanje; + break; + //case 13: // Parts per million + // pitanje = pitanje + "ppm"; + // break; + case 14: // Tangent + pitanje = "tan" + pitanje; + break; + case 15: // Cosine + pitanje = "cos" + pitanje; + break; + case 16: // Sine + pitanje = "sin" + pitanje; + break; + case 17: // Percent + pitanje = pitanje + "%"; + break; + case 20: // Factorial + pitanje = pitanje + "!"; + break; + case 21: // Power + var eksponent = prompt("Unesite eksponent / Please enter exponent", 3); + pitanje = pitanje + "^" + eksponent; + break; + case 22: // Root + var eksponent = prompt("Unesite korijen / Please enter root", 3); + pitanje = pitanje + "^(1/" + eksponent + ")"; + break; + } + + return pitanje; +} + +function CalcZagrada(izraz) { + var intZagClose = 0 + var intZagOpen = 0 + var broj = 0 + //var strNoviXbroj = "" + var strResult = ""; + + do { +// 2+(2*(2+4)+3)^3+5 + izraz = izraz.replace(/--/g,"-1*-"); + intZagClose = izraz.indexOf(")"); + if (intZagClose != -1) { + for (var i = intZagClose; i >= 0; i--) { + if (izraz.charAt(i)=="(") { + intZagOpen = i; + strResult = izraz.substring(intZagOpen+1,intZagClose); + break; + } + } + } + else { + strResult = izraz; + } + + strResult = strResult + "*1"; + strResult = CalcPostotak(strResult); + strResult = CalcTrigonometry(strResult); + strResult = CalcLogaritam(strResult); + strResult = CalcPotencija(strResult); + + broj = EvalString(strResult); + strResult = broj.toString(); + + if (intZagClose != -1) { + izraz = izraz.replace(izraz.substring(intZagOpen, intZagClose+1), strResult); + } + else { + izraz = strResult; + } + } + while (intZagClose > 0) + //izraz = molmasa.toString(); + return izraz; +} + + +function EvalString(upit) { + with (Math) { + //var broj = eval(upit); + return eval(upit); + } + //upit = broj.toString(); + //return broj; +} + + +function CalcPotencija(ulaz) { + var intZagClose = 0 + var intZagOpen = 0; + var fPower = 0; + + var intPozicija = ulaz.indexOf("^"); //2+2^3+2,15^2+4 + + while (intPozicija > 0) { + for (var i = intPozicija - 1; i >= 0; i--) { + if (Operator(ulaz.charAt(i)) && ulaz.charAt(i-1)!="e") { + //if (i > 0 && Operator(ulaz.charAt(i-1))) { + intZagOpen = i+1; + break; + } + } + + if (ulaz.charAt(i) == "-"){ + if (i == 0) {intZagOpen = 0;} + else if (i > 0 && Operator(ulaz.charAt(i-1))) {intZagOpen = i;} + } + + var strBase = ulaz.substring(intZagOpen,intPozicija); + if (strBase == 'e') {strBase = e}; //cps + + for (var i = intPozicija + 2; i < ulaz.length; i++) { + if (Operator(ulaz.charAt(i)) && ulaz.charAt(i-1)!="e") {intZagClose = i-1; break} + } + var strExponent = ulaz.substring(intPozicija+1,intZagClose+1); + + fPower = Math.pow(strBase, strExponent); + + ulaz = ulaz.replace(ulaz.substring(intZagOpen, intZagClose+1), fPower); + + intPozicija = ulaz.indexOf("^"); + } + + return ulaz; +} + + +function CalcPostotak(ulaz) { + var intZagClose = 0; + var intZagOpen = 0; + var fPostotak = 0; + + var strDesnoFun = new Array ("!", "%"); + + for (var f = 0; f < 2; f++) { + var intPozicija = ulaz.indexOf(strDesnoFun[f]); + + while (intPozicija > 0) { + for (var i = intPozicija - 1; i >= 0; i--) { + if (Operator(ulaz.charAt(i)) && ulaz.charAt(i-1)!="e") {intZagOpen = i+1; break} + } + var strNoviXbroj = ulaz.substring(intZagOpen,intPozicija); + intZagClose = intPozicija+1; + //with (Math) { + if (f == 0) { + fPostotak = CalcFactorial(strNoviXbroj); + } + else { + fPostotak = strNoviXbroj / 100; + } + //} + ulaz = ulaz.replace(ulaz.substring(intZagOpen, intZagClose), fPostotak); + intPozicija = ulaz.indexOf(strDesnoFun[f]); + } + } + + return ulaz; +} + + +function CalcTrigonometry(kut) { + var intZagClose = 0 + var fAngle = 0 + var strNoviKut = "" + var strKrozPi = ")" + var strPiKroz = ")"; + + switch(rdg) + { + case 1: + strKrozPi = ")*180/pi"; + strPiKroz = "*pi/180)"; + break; + case 2: + strKrozPi = ")*200/pi"; + strPiKroz = "*pi/200)"; + break; + //default: + // strKrozPi = ")"; + // strPiKroz = ")"; + } + + var strTrigFun = new Array ("sin", "cos", "tan"); + + for (var f = 0; f < 3; f++) { + intPozicija = kut.indexOf(strTrigFun[f]); + + if (intPozicija >= 0) { + if (VelikoSlovo(kut.charAt(intPozicija+4))) {return kut;} + + do { +//document.racunalo.notes.value += enter + intKut + enter; cos(2*(3+5)+3*(2+4))+1 2+(2*(2+cos(4))+3)+(3^3+5)*4 + intZagClose = kut.length; + for (var i = intPozicija+4; i < intZagClose; i++) { + + if (Operator(kut.charAt(i)) && kut.charAt(i-1)!="e") { + intZagClose = i; + strNoviKut = kut.substring(intPozicija+3, intZagClose); + break; + } + } + + if (intPozicija>0 && kut.charAt(intPozicija-1)=="a") { + intPozicija = intPozicija - 1; + strNoviKut = "a" + strTrigFun[f] + "(" + strNoviKut + strKrozPi; + } + else { + strNoviKut = strTrigFun[f] + "(" + strNoviKut + strPiKroz; + } + + fAngle = EvalString(strNoviKut); + fAngle = Math.round(fAngle * Math.pow(10,14)) / Math.pow(10,14); + //strNoviKut = fAngle.toString(); + + kut = kut.replace(kut.substring(intPozicija, intZagClose), fAngle); + + intPozicija = kut.indexOf(strTrigFun[f]); + + } + while (intPozicija != -1); + } + } + + return kut; +} + + +function CalcLogaritam(ulaz) { + var intZagClose = 0 + var fLogaritam = 0 + var strNoviLog = "" + var strKrozPi = ")" + var strPiKroz = ")"; +// var e = 2.71828182845905 + + var strLogFun = new Array ("ln", "log"); + + for (var f = 0; f < 2; f++) { + var intPozicija = ulaz.indexOf(strLogFun[f]); + + if (intPozicija >= 0) { + if (VelikoSlovo(ulaz.charAt(intPozicija+3+f))) {return ulaz;} + + do { + intZagClose = ulaz.length; + for (var i = intPozicija; i < intZagClose; i++) { + + if (Operator(ulaz.charAt(i)) && ulaz.charAt(i-1)!="e") { + intZagClose = i; + strNoviLog = ulaz.substring(intPozicija+strLogFun[f].length, intZagClose); + break; + } + } + + if (f == 0) { + if (intPozicija>0 && ulaz.charAt(intPozicija-1)=="a") { + intPozicija = intPozicija - 1; + strNoviLog = "pow(E," + strNoviLog + ")"; + } + else { + strNoviLog = "log(" + strNoviLog + ")"; + } + } + else { + if (intPozicija>0 && ulaz.charAt(intPozicija-1)=="a") { + intPozicija = intPozicija - 1; + strNoviLog = "pow(10," + strNoviLog + ")"; + } + else { + strNoviLog = "log(" + strNoviLog + ")/LN10"; + } + } + + fLogaritam = EvalString(strNoviLog); + + ulaz = ulaz.replace(ulaz.substring(intPozicija, intZagClose), fLogaritam); + intPozicija = ulaz.indexOf(strLogFun[f]); + + } + while (intPozicija != -1); + } + } + + return ulaz; +} + + +function CalcFactorial(n) { + if ((n == 0) || (n == 1)) { + return 1; + } + else { + var odgovor = (n * CalcFactorial(n-1)); + return odgovor; + } +} + + +function MolnaMasa(atom) { +try +{ + with (Math) { + var atominfo = false; + var mm=""; + var mmdn=""; + var mmup=""; + var znak=""; + var izraz=""; + var Pi=pi; + var H=1.0079; + var He=4.0026; + var Li=6.941; + var Be=9.0122; + var B=10.811; + var C=12.011; + var N=14.007; + var O=15.999; + var F=18.998; + var Ne=20.18; + var Na=22.99; + var Mg=24.305; + var Al=26.982; + var Si=28.086; + var P=30.974; + var S=32.065; + var Cl=35.453; + var Ar=39.948; + var K=39.098; + var Ca=40.078; + var Sc=44.956; + var Ti=47.867; + var V=50.942; + var Cr=51.996; + var Mn=54.938; + var Fe=55.845; + var Co=58.933; + var Ni=58.693; + var Cu=63.546; + var Zn=65.38; + var Ga=69.723; + var Ge=72.64; + var As=74.922; + var Se=78.96; + var Br=79.904; + var Kr=83.798; + var Rb=85.468; + var Sr=87.62; + var Y=88.906; + var Zr=91.224; + var Nb=92.906; + var Mo=95.96; + var Tc=98; + var Ru=101.07; + var Rh=102.91; + var Pd=106.42; + var Ag=107.87; + var Cd=112.41; + var In=114.82; + var Sn=118.71; + var Sb=121.76; + var Te=127.6; + var I=126.9; + var Xe=131.29; + var Cs=132.91; + var Ba=137.33; + var La=138.91; + var Ce=140.12; + var Pr=140.91; + var Nd=144.24; + var Pm=145; + var Sm=150.36; + var Eu=151.96; + var Gd=157.25; + var Tb=158.93; + var Dy=162.5; + var Ho=164.93; + var Er=167.26; + var Tm=168.93; + var Yb=173.05; + var Lu=174.97; + var Hf=178.49; + var Ta=180.95; + var W=183.84; + var Re=186.21; + var Os=190.23; + var Ir=192.22; + var Pt=195.08; + var Au=196.97; + var Hg=200.59; + var Tl=204.38; + var Pb=207.2; + var Bi=208.98; + var Po=209; + var At=210; + var Rn=222; + var Fr=223; + var Ra=226; + var Ac=227; + var Th=232.04; + var Pa=231.04; + var U=238.03; + var Np=237; + var Pu=244; + var Am=243; + var Cm=247; + var Bk=247; + var Cf=251; + var Es=252; + var Fm=257; + var Md=258; + var No=259; + var Lr=262; + var Rf=267; + var Db=268; + var Sg=271; + var Bh=272; + var Hs=277; + var Mt=276; + var Ds=281; + var Rg=280; + var Cn=285; + // Pure Appl. Chem., Vol. 81, No. 11, (2009) 2131-2156 + + for (var i=0; i= 0) {return true} + return false; +} + +function UbaciRezultat(znak) { + var matoperator="^*/+"; + for (var i=0; i -1) { + rezultat = "." + rezultat; + tri = 0; + for (var i=dotplace-1; i>=0; i--) { + if (tri == 3) { + rezultat = broj.charAt(i) + " " + rezultat; + tri = 0; + } + else { + rezultat = broj.charAt(i) + rezultat; + } + tri++; + } + } + rezultat = rezultat.replace("- ", "-"); + broj = rezultat; + + return broj; +} + + +function Cleaning(zadatak) { + var strAllowed="1234567890!^*/+-.,×÷{[()]}abcdefghiklmnoprstuvwxyzABCDEFGHIKLMNOPRSTUVWXYZ"; + var rezultat=""; + for (var i=0; i + +Scientific calculator for chemists @ periodni.com + + + + +
+ +
+ +
+ +
+ + + + + + + + + + +
 
+ + + + +
+ +
+ +
+ + +
+
 
+ + + + + + +
+ + + + + + +
+ + + + + + +
 
+ + + + + + +
+ + + + + +
+ + + + + + +
+ + + + + +
+
+ +
+

Number formats

+
+ + + + + + + +
Decimals
+ + + + +
+ +
+ + + +
+ + + +
www.periodni.com   2013
+
+ +
+ +
+ + + + + + + +
+ +
+
PHYSICAL CONSTANTS
+
Absolute zero
-273.16 °C
+
Acceleration of free fall, standard
9.806 65 m/s2
+
Atomic mass unit
1.660 538 782×10-27 kg
+
Avogadro constant
6.022 141 79×1023 1/mol
+
Base of natural logarithms
2.718 281 828 459...
+
Boltzmann constant
1.380 6504×10-23 J/K
+
Classical electron radius
2.817 940 2894×10-15 m
+
Electron mass
9.109 382 15×10-31 kg
+
Electron-proton mass ratio
5.446 170 2177×10-4
+
Electronvolt
1.602 176 487×10-19 J
+
Elementary charge
1.602 176 487×10-19 C
+
Faraday constant
96 485.3399 C/mol
+
First radiation constant
3.741 771 18×10-16 W m2
+
Molar gas constant
8.314 472 J/mol K
+
Molar volume
(Ideal gas, T = 273.15 K, p = 101.325 kPa)
22.413 996×10-3 m3/mol
+
Newtonian constant of gravitation
6.674 28×10-11 N m2/kg2
+
Permeability of vacuum
12.566 370 614...×10-7 N/A2
+
Permittivity of vacuum
8.854 187 817...×10-12 F/m
+
PI
3.141 592 653 589 793 238...
+
Planck constants
6.626 068 96×10-34 J s
+
Second radiation constant
0.014 387 752 m K
+
Solar constant
1366 W/m2
+
Speed of light in vacuum
299 792 458 m/s
+
Speed of sound in air at STP
331.5 + 0.6 * T/°C m/s
+
Standard pressure
101 325 Pa
+
+ +
+ +
+ +
+
+

© 1998-2016 by Eni Generalić (www.periodni.com)

+ + \ No newline at end of file diff --git a/browzos_options/clock/anaclock.swf b/browzos_options/clock/anaclock.swf new file mode 100644 index 0000000..90b23f6 Binary files /dev/null and b/browzos_options/clock/anaclock.swf differ diff --git a/browzos_options/clock/index.html b/browzos_options/clock/index.html new file mode 100644 index 0000000..130d2cb --- /dev/null +++ b/browzos_options/clock/index.html @@ -0,0 +1,12 @@ + +The Clock + + + + +
+ +
+ \ No newline at end of file diff --git a/browzos_options/dock/calc.png b/browzos_options/dock/calc.png new file mode 100644 index 0000000..7de1c44 Binary files /dev/null and b/browzos_options/dock/calc.png differ diff --git a/browzos_options/dock/chat.png b/browzos_options/dock/chat.png new file mode 100644 index 0000000..9cb1d3b Binary files /dev/null and b/browzos_options/dock/chat.png differ diff --git a/browzos_options/dock/forum.png b/browzos_options/dock/forum.png new file mode 100644 index 0000000..749c825 Binary files /dev/null and b/browzos_options/dock/forum.png differ diff --git a/browzos_options/dock/games.png b/browzos_options/dock/games.png new file mode 100644 index 0000000..26e2a98 Binary files /dev/null and b/browzos_options/dock/games.png differ diff --git a/browzos_options/dock/help.png b/browzos_options/dock/help.png new file mode 100644 index 0000000..d60425f Binary files /dev/null and b/browzos_options/dock/help.png differ diff --git a/browzos_options/dock/iepngfix.htc b/browzos_options/dock/iepngfix.htc new file mode 100644 index 0000000..5e8edc7 --- /dev/null +++ b/browzos_options/dock/iepngfix.htc @@ -0,0 +1,103 @@ + + + + + diff --git a/browzos_options/dock/index.html b/browzos_options/dock/index.html new file mode 100644 index 0000000..b58045f --- /dev/null +++ b/browzos_options/dock/index.html @@ -0,0 +1,17 @@ +The Dock Icons + + + + + + +
+ +
+ \ No newline at end of file diff --git a/browzos_options/dock/info.png b/browzos_options/dock/info.png new file mode 100644 index 0000000..7233d45 Binary files /dev/null and b/browzos_options/dock/info.png differ diff --git a/browzos_options/dock/linkdock.js b/browzos_options/dock/linkdock.js new file mode 100644 index 0000000..1b2dabc --- /dev/null +++ b/browzos_options/dock/linkdock.js @@ -0,0 +1,204 @@ +// LinkDock - V 1.5 +// By Brian Gosselin of http://scriptasylum.com +// Release Info: +// V 1.0 - Initial release. +// V 1.1 - Minor code changes for magnification smoothness and accuracy. +// V 1.2 - Added option to include text under the links as they are hovered over. +// Fixed a magnification bug when the page is too small to fit the whole menu. +// V 1.3 - Fixed a bug where you get script errors if you hover over an image before the +// page finishes loading. +// V 1.4 - Added a tweak by RAJ to smoothen the entry into the menu. +// V 1.5 - Fixed mouse bug where the mouse wasn't tracking right when dock was wider than the +// page and the page was scrolled to the right. + +// ENTER LINK ATTRIBUTES IN THE ARRAY BELOW; EACH LINE CONTAINS ALL THE +// PARAMETERS FOR ONE LINK. USE THE FOLLOWING FORMAT: +// [ 'LINK_URL' , 'IMAGE_URL' , 'URL_TARGET', 'TEXT_UNDER_LINK' ] +// VALID VALUES FOR 'URL_TARGET' ARE: +// '_blank' (NEW WINDOW) +// 'name' (THE NAME OF AN EXISTING WINDOW OR FRAME) +// '' (CURRENT PAGE) +// IF YOU DO NOT WANT TEXT DISPLAYED UNDER A LINK, SIMPLY USE AN EMPTY STRING AS THE PARAMETER FOR 'TEXT_UNDER_LINK'. + +var linkList=[ +[ '../rte/' , 'wproc.png' , 'content', 'Word Processor' ], +[ '../sprdsht/' , 'sprdsht.png' , 'content', 'Spreadsheet' ], +[ '../calc/' , 'calc.png' , 'content', 'Scientific Calculator' ], +[ '../instmess/' , 'chat.png' , 'content', 'Instant Messenger' ], +[ '../games/' , 'games.png' , 'content', 'Online Games' ], +[ '../media/' , 'media.png' , 'content', 'Media Player' ], +[ '../bzcmd/' , 'term.png' , 'content', 'Command Line' ], +[ '../help/' , 'help.png' , '_top', 'Help' ], +//Uncomment the following line after installing the forum software properly onto the server. +//[ '../forum/' , 'forum.png' , '_top', 'Forum' ], +[ 'javascript:alert(\'BrowzOS -- This site is in the public domain.\')' , 'info.png' , 'content', 'About BrowzOS' ] +] + +// CHANGE THE OTHER VALUES BELOW TO SUIT YOUR APPLICATION + +var startSize=32; // THE STARTING WIDTH *AND* HEIGHT OF EACH IMAGE (THE IMAGES WILL BE SCALED). +// +var endSize=64; // THE ENDING WIDTH *AND* HEIGHT OF EACH IMAGE (THE IMAGES WILL BE SCALED). +var useText=true; // true = USE TEXT UNDER THE LINK, false = NO TEXT UNDER THE LINK. +var defText='Hover over an icon...' // DEFAULT TEXT TO APPEAR UNDER THE LINKS WHEN NOT HOVERED OVER. + // USE AN EMPTY STRING FOR NO TEXT. +var textGap=10; // PIXEL GAP FROM BOTTOM OF MENU TO TOP OF OPTIONAL TEXT (WHEN defText IS SET TO true). +var effectW=3.5; // THE NUMBER OF ICONS AFFECTED BY OF THE MAGNIFICATION AT ONCE (APPROXIMATE). USE VALUES BETWEEN 2 AND 5. + +// BELOW IS THE STYLE-SHEET RULE FOR HOW THE TEXT IS TO BE DISPLAYED. USE VALID CSS RULES. + +var textStyle="font-family:verdana; font-size:11pt; color:black; font-weight:bold"; + + +//********** DO NOT EDIT BEYOND THIS POINT **********\\ + + +var w3c=(document.getElementById)?true:false; +var ie4=(document.all && !w3c)?true:false; +var ie5=(document.all && w3c)?true:false; +var ns4=(document.layers)?true:false; +var mx=0; +var overEl=false; +var enterEl=false; +var id=0; +var elList=new Array(); +var elText; +var pgLoaded=false; +if(defText=='')defText=' '; +effectW=Math.max(2,Math.min(5,effectW))+.5; +var wA=effectW*endSize/2; +var mX=wA/1.5; + +function getMxy(v){ +mx=(ie5||ie4)?event.clientX:v.pageX; +} + +function getEl(s){ +if(ns4)return findLayer(s,document); +else return (ie4)?document.all[s]:document.getElementById(s); +} + +function getW(e){ +return parseInt(e.style.width); +} + +function setImgS(i,x){ +elList[i].style.width=x; +elList[i].style.height=x; +document.images['linkDockI'+i].width=x; +document.images['linkDockI'+i].height=x; +} + +function getL(el){ +var x=0; +var sx=(document.all)?document.body.scrollLeft:0; +while(el.offsetParent!=null){ +x+=el.offsetLeft; +el=el.offsetParent; +} +return x+el.offsetLeft-sx; +} + +function rAll(){ +// decrease size of zoomed images gradually +for(i=0;istartSize) { +id=setTimeout('rAll()',10); +curSize--; +// RAJ --> +setImgS(i,curSize); +}}} + +function dockMagnify(){ +var tEl,n1,n2; +// +if(overEl){ +for(i=0;i=mx-wA)&&(getL(tEl)<=mx+wA)){ +n1=getL(tEl)+getW(tEl)/2+10; +n2=mx-wA; +// +setImgS(i,Math.max(n1,startSize)); +}else setImgS(i,startSize); +}}} + +function mOver(){ +overEl=true; +clearTimeout(id); +} + +function mOut(){ +overEl=false; +id=setTimeout('rAll()',100); +} + +// FUNCTION TO FIND NESTED LAYERS IN NS4 BY MIKE HALL +function findLayer(name,doc){ +var i,layer; +for(i=0;i0)if((layer=findLayer(name,layer.document))!=null)return layer; +} +return null; +} + +function writeText(text){ +if(useText && pgLoaded){ +text=(text<0)?defText:linkList[text][3]; +if(text=='')text=' '; +if(ns4){ +elText.document.open(); +elText.document.write('
'+text+'
'); +elText.document.close(); +} +else elText.innerHTML=text; +}} + +function writeHTML(){ +var t=''; +if(w3c||ie4){ +t+='
'; +for(i=0;i'; +t+=''; +t+=''; +} +t+='
'; +if(useText)t+='
'+defText+'
'; +}else{ +t+=''; +for(i=0;i'; +t+=''; +if(useText)t+=''; +t+='
'; +} +document.write(t); +} + +window.onload=function(){ +if(w3c||ie4){ +for(j=0;j-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "', sizingMethod='image')", +this.src = "transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "', sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +); +} \ No newline at end of file diff --git a/browzos_options/dock/releasenotes.txt b/browzos_options/dock/releasenotes.txt new file mode 100644 index 0000000..4c09b30 --- /dev/null +++ b/browzos_options/dock/releasenotes.txt @@ -0,0 +1,83 @@ + + **** Release Notes **** + +Popup Windows +Author: Brian Gosselin +Site URL: http://scriptasylum.com + +Browser compatibility: +IE5+, NS4+, NS6+, Mozilla1+ (with limited functionality in some older browsers) + +Supported Features: + +(V 4.5) +> Added the ability to specify the image URLs for the min, max, close, and resize icons. If the + icons reside in a different directory, show the path relative to where the script file + is stored. + + +(V 4.4) +> Added fadeboxin() and fadeboxout() functions which gradually fade in/out popups. To fade + them in, they must be hidden; to fade them out, they must be visible. Only one popup can + be faded in or out of view at one time due to browser rendering speed. If there are mul- + tiple popups on one page, the scrollbars of the other popups flicker in IE as a popup + is faded in/out. As a result, all popups will have their scrollbars temporarily disabled + until the fading in/out is complete. This only effect IE browsers. + + +(V 4.3) +> Added functions to move and resize any popup dynamically via link or script, similar to how + you can use a function to show/hide a popup. + + +(V 4.2) +> Added function to allow the content (or URL) of a popup to be changed dynamically via link or + script, similar to how you can use a function to show/hide a popup. +> Fixed bug: When a window is created initially hidden, then shown via link, the windows now gain + focus. + + +(V 4.1) +> Removed the arrays for element references for better readability. +> Fixed an IFRAME bug for external URL windows. +> Cleaned up the code a little. +> Removed most of the comments and instructions from the script file. These now appear in + this "releasenotes" text file. + + +(V 4.0) +> Can now load external HTML files, even when using "old-browser" mode. Thanks to + DynamicDrive.com for a workaround for dragging when using an iframe. +> Added cookie functionality so popups will not reappear if the user goes back to + the page (selectable). Thanks to DynamicDrive.com for this idea. +> Removed minimize/maximize buttons if popup is non-resizeable. +> Optimized "old-browser" mode to utilize more of the user defined settings such as + window position and resizability. Also supports the selectable "external page mode". +> Added a wizard to set up your window parameters. Just plug in the values you want, test + by creating a popup, then paste the generated code into your page! Easy as that! + + +(V 3.1) +> Added resizability to the windows. +> Popups can be created in their hidden/visible state and then opened/closed via + javascript function if desired. +> Degrades gracefully in older browsers by creating normal browser windows (selectable). +> Thanks to Robert V. Zwink for a tweak which adds better compliance for Netscape 3.04 + + +(V 3.0) +> New look and feel to make the windows look more like real windows. + + +(V 2.0) +> Fixed several bugs, mostly with the drag-n-drop functionality. + + +(v 1.0) +> Initial release. +> Seperate Drag and Drop, minimize/restore/close, and fonts/colors/dimensions/placement + for each popup. +> Allows any number of popups per page limited only by browser/computer performance. +> Popups are created *after* page loadtime allowing for truly dynamic windows. For + popups that appear when the page is loaded, put the popup constructor in a window.onload + function. diff --git a/browzos_options/dock/spacer.png b/browzos_options/dock/spacer.png new file mode 100644 index 0000000..bf867a3 Binary files /dev/null and b/browzos_options/dock/spacer.png differ diff --git a/browzos_options/dock/sprdsht.png b/browzos_options/dock/sprdsht.png new file mode 100644 index 0000000..c0ccb7a Binary files /dev/null and b/browzos_options/dock/sprdsht.png differ diff --git a/browzos_options/dock/swfobject.js b/browzos_options/dock/swfobject.js new file mode 100644 index 0000000..ca8b227 --- /dev/null +++ b/browzos_options/dock/swfobject.js @@ -0,0 +1,216 @@ +/** + * SWFObject v2.0: Flash Player detection and embed - http://blog.deconcept.com/swfobject/ + * + * SWFObject is (c) 2006 Geoff Stearns and is released under the MIT License: + * http://www.opensource.org/licenses/mit-license.php + * + */ +if(typeof deconcept == "undefined") var deconcept = new Object(); +if(typeof deconcept.util == "undefined") deconcept.util = new Object(); +if(typeof deconcept.SWFObjectUtil == "undefined") deconcept.SWFObjectUtil = new Object(); +deconcept.SWFObject = function(swf, id, w, h, ver, c, quality, xiRedirectUrl, redirectUrl, detectKey) { + if (!document.getElementById) { return; } + this.DETECT_KEY = detectKey ? detectKey : 'detectflash'; + this.skipDetect = deconcept.util.getRequestParameter(this.DETECT_KEY); + this.params = new Object(); + this.variables = new Object(); + this.attributes = new Array(); + if(swf) { this.setAttribute('swf', swf); } + if(id) { this.setAttribute('id', id); } + if(w) { this.setAttribute('width', w); } + if(h) { this.setAttribute('height', h); } + if(ver) { this.setAttribute('version', new deconcept.PlayerVersion(ver.toString().split("."))); } + this.installedVer = deconcept.SWFObjectUtil.getPlayerVersion(); + if (!window.opera && document.all && this.installedVer.major > 7) { + // only add the onunload cleanup if the Flash Player version supports External Interface and we are in IE + deconcept.SWFObject.doPrepUnload = true; + } + if(c) { this.addParam('bgcolor', c); } + var q = quality ? quality : 'high'; + this.addParam('quality', q); + this.setAttribute('useExpressInstall', false); + this.setAttribute('doExpressInstall', false); + var xir = (xiRedirectUrl) ? xiRedirectUrl : window.location; + this.setAttribute('xiRedirectUrl', xir); + this.setAttribute('redirectUrl', ''); + if(redirectUrl) { this.setAttribute('redirectUrl', redirectUrl); } +} +deconcept.SWFObject.prototype = { + useExpressInstall: function(path) { + this.xiSWFPath = !path ? "expressinstall.swf" : path; + this.setAttribute('useExpressInstall', true); + }, + setAttribute: function(name, value){ + this.attributes[name] = value; + }, + getAttribute: function(name){ + return this.attributes[name]; + }, + addParam: function(name, value){ + this.params[name] = value; + }, + getParams: function(){ + return this.params; + }, + addVariable: function(name, value){ + this.variables[name] = value; + }, + getVariable: function(name){ + return this.variables[name]; + }, + getVariables: function(){ + return this.variables; + }, + getVariablePairs: function(){ + var variablePairs = new Array(); + var key; + var variables = this.getVariables(); + for(key in variables){ + variablePairs.push(key +"="+ variables[key]); + } + return variablePairs; + }, + getSWFHTML: function() { + var swfNode = ""; + if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) { // netscape plugin architecture + if (this.getAttribute("doExpressInstall")) { + this.addVariable("MMplayerType", "PlugIn"); + this.setAttribute('swf', this.xiSWFPath); + } + swfNode = ' 0){ swfNode += 'flashvars="'+ pairs +'"'; } + swfNode += '/>'; + } else { // PC IE + if (this.getAttribute("doExpressInstall")) { + this.addVariable("MMplayerType", "ActiveX"); + this.setAttribute('swf', this.xiSWFPath); + } + swfNode = ''; + swfNode += ''; + var params = this.getParams(); + for(var key in params) { + swfNode += ''; + } + var pairs = this.getVariablePairs().join("&"); + if(pairs.length > 0) {swfNode += '';} + swfNode += ""; + } + return swfNode; + }, + write: function(elementId){ + if(this.getAttribute('useExpressInstall')) { + // check to see if we need to do an express install + var expressInstallReqVer = new deconcept.PlayerVersion([6,0,65]); + if (this.installedVer.versionIsValid(expressInstallReqVer) && !this.installedVer.versionIsValid(this.getAttribute('version'))) { + this.setAttribute('doExpressInstall', true); + this.addVariable("MMredirectURL", escape(this.getAttribute('xiRedirectUrl'))); + document.title = document.title.slice(0, 47) + " - Flash Player Installation"; + this.addVariable("MMdoctitle", document.title); + } + } + if(this.skipDetect || this.getAttribute('doExpressInstall') || this.installedVer.versionIsValid(this.getAttribute('version'))){ + var n = (typeof elementId == 'string') ? document.getElementById(elementId) : elementId; + n.innerHTML = this.getSWFHTML(); + return true; + }else{ + if(this.getAttribute('redirectUrl') != "") { + document.location.replace(this.getAttribute('redirectUrl')); + } + } + return false; + } +} + +/* ---- detection functions ---- */ +deconcept.SWFObjectUtil.getPlayerVersion = function(){ + var PlayerVersion = new deconcept.PlayerVersion([0,0,0]); + if(navigator.plugins && navigator.mimeTypes.length){ + var x = navigator.plugins["Shockwave Flash"]; + if(x && x.description) { + PlayerVersion = new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split(".")); + } + }else{ + // do minor version lookup in IE, but avoid fp6 crashing issues + // see http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/ + try{ + var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"); + }catch(e){ + try { + var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"); + PlayerVersion = new deconcept.PlayerVersion([6,0,21]); + axo.AllowScriptAccess = "always"; // throws if player version < 6.0.47 (thanks to Michael Williams @ Adobe for this code) + } catch(e) { + if (PlayerVersion.major == 6) { + return PlayerVersion; + } + } + try { + axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); + } catch(e) {} + } + if (axo != null) { + PlayerVersion = new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(",")); + } + } + return PlayerVersion; +} +deconcept.PlayerVersion = function(arrVersion){ + this.major = arrVersion[0] != null ? parseInt(arrVersion[0]) : 0; + this.minor = arrVersion[1] != null ? parseInt(arrVersion[1]) : 0; + this.rev = arrVersion[2] != null ? parseInt(arrVersion[2]) : 0; +} +deconcept.PlayerVersion.prototype.versionIsValid = function(fv){ + if(this.major < fv.major) return false; + if(this.major > fv.major) return true; + if(this.minor < fv.minor) return false; + if(this.minor > fv.minor) return true; + if(this.rev < fv.rev) return false; + return true; +} +/* ---- get value of query string param ---- */ +deconcept.util = { + getRequestParameter: function(param) { + var q = document.location.search || document.location.hash; + if(q) { + var pairs = q.substring(1).split("&"); + for (var i=0; i < pairs.length; i++) { + if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) { + return pairs[i].substring((pairs[i].indexOf("=")+1)); + } + } + } + return ""; + } +} +/* fix for video streaming bug */ +deconcept.SWFObjectUtil.cleanupSWFs = function() { + var objects = document.getElementsByTagName("OBJECT"); + for (var i=0; i < objects.length; i++) { + objects[i].style.display = 'none'; + for (var x in objects[i]) { + if (typeof objects[i][x] == 'function') { + objects[i][x] = function(){}; + } + } + } +} +// fixes bug in fp9 see http://blog.deconcept.com/2006/07/28/swfobject-143-released/ +if (deconcept.SWFObject.doPrepUnload) { + deconcept.SWFObjectUtil.prepUnload = function() { + __flash_unloadHandler = function(){}; + __flash_savedUnloadHandler = function(){}; + window.attachEvent("onunload", deconcept.SWFObjectUtil.cleanupSWFs); + } + window.attachEvent("onbeforeunload", deconcept.SWFObjectUtil.prepUnload); +} +/* add Array.push if needed (ie5) */ +if (Array.prototype.push == null) { Array.prototype.push = function(item) { this[this.length] = item; return this.length; }} + +/* add some aliases for ease of use/backwards compatibility */ +var getQueryParamValue = deconcept.util.getRequestParameter; +var FlashObject = deconcept.SWFObject; // for legacy support +var SWFObject = deconcept.SWFObject; diff --git a/browzos_options/dock/term.png b/browzos_options/dock/term.png new file mode 100644 index 0000000..f86c784 Binary files /dev/null and b/browzos_options/dock/term.png differ diff --git a/browzos_options/dock/wproc.png b/browzos_options/dock/wproc.png new file mode 100644 index 0000000..daf84b2 Binary files /dev/null and b/browzos_options/dock/wproc.png differ diff --git a/browzos_options/drag/calc.png b/browzos_options/drag/calc.png new file mode 100644 index 0000000..7de1c44 Binary files /dev/null and b/browzos_options/drag/calc.png differ diff --git a/browzos_options/drag/chat.png b/browzos_options/drag/chat.png new file mode 100644 index 0000000..9cb1d3b Binary files /dev/null and b/browzos_options/drag/chat.png differ diff --git a/browzos_options/drag/drag.js b/browzos_options/drag/drag.js new file mode 100644 index 0000000..7d7074e --- /dev/null +++ b/browzos_options/drag/drag.js @@ -0,0 +1,79 @@ +var dO=new Object(); +dO.snapthresh=20; // THIS VALUE IS THE SNAPTO INCREMENT. +dO.snapto=false; // SET TO true TO ENABLE SNAPTO, false TO DISABLE IT. + +dO.currID=null; +dO.z=0; +dO.xo=0; +dO.yo=0; +dO.ns4=(document.layers)?true:false; +dO.ns6=(document.getElementById&&!document.all)?true:false; +dO.ie4=(document.all&&!document.getElementById)?true:false; +dO.ie5=(document.all&&document.getElementById)?true:false; +dO.w3c=(document.getElementById)?true:false; + +function invsnap(){ +dO.snapto=!dO.snapto; +} + +//NEAT FUNCTION BY MIKE HALL (OF BRAINJAR.COM) THAT FINDS NESTED LAYERS FOR NS4.x +function findnestedlayer(name,doc){ +var i,layer; +for(i=0;i0) +if((layer=findlayer(name,layer.document))!=null) +return layer; +} +return null; +} + +function trckM(e){ +if(dO.currID!=null){ +var x=(dO.ie4||dO.ie5)?event.clientX+document.body.scrollLeft:e.pageX; +var y=(dO.ie4||dO.ie5)?event.clientY+document.body.scrollTop:e.pageY; +if(dO.snapto){ +x=Math.ceil(x/dO.snapthresh)*dO.snapthresh; +y=Math.ceil(y/dO.snapthresh)*dO.snapthresh; +} +if(dO.ns4)dO.currID.moveTo(x-dO.xo, y-dO.yo); +else{ +dO.currID.style.top=y-dO.yo+'px'; +dO.currID.style.left=x-dO.xo+'px'; +}} +return false; +} + +function drgI(e){ +if(dO.currID==null){ +var tx=(dO.ns4)? this.left : parseInt(this.style.left); +var ty=(dO.ns4)? this.top : parseInt(this.style.top); +dO.currID=this; +if(dO.ns4)this.zIndex=document.images.length+(dO.z++); +else this.style.zIndex=document.images.length+(dO.z++); +dO.xo=((dO.ie4||dO.ie5)?event.clientX+document.body.scrollLeft:e.pageX)-tx; +dO.yo=((dO.ie4||dO.ie5)?event.clientY+document.body.scrollTop:e.pageY)-ty; +if(dO.snapto){ +dO.xo=Math.ceil(dO.xo/dO.snapthresh)*dO.snapthresh; +dO.yo=Math.ceil(dO.yo/dO.snapthresh)*dO.snapthresh; +} +return false; +}} + +function dragElement(id){ +this.idRef=(dO.ns4)? findnestedlayer(id,document) : (dO.ie4)? document.all[id] : document.getElementById(id); +if(dO.ns4)this.idRef.captureEvents(Event.MOUSEDOWN | Event.MOUSEUP); +this.idRef.onmousedown=drgI; +this.idRef.onmouseup=function(){dO.currID=null} +} + +if(dO.ns4)document.captureEvents(Event.MOUSEMOVE); +document.onmousemove=trckM; + + +window.onresize=function(){ +if(dO.ns4)setTimeout('history.go(0)',300); +} + + diff --git a/browzos_options/drag/forum.png b/browzos_options/drag/forum.png new file mode 100644 index 0000000..749c825 Binary files /dev/null and b/browzos_options/drag/forum.png differ diff --git a/browzos_options/drag/games.png b/browzos_options/drag/games.png new file mode 100644 index 0000000..26e2a98 Binary files /dev/null and b/browzos_options/drag/games.png differ diff --git a/browzos_options/drag/help.png b/browzos_options/drag/help.png new file mode 100644 index 0000000..d60425f Binary files /dev/null and b/browzos_options/drag/help.png differ diff --git a/browzos_options/drag/iepngfix.htc b/browzos_options/drag/iepngfix.htc new file mode 100644 index 0000000..5e8edc7 --- /dev/null +++ b/browzos_options/drag/iepngfix.htc @@ -0,0 +1,103 @@ + + + + + diff --git a/browzos_options/drag/index.html b/browzos_options/drag/index.html new file mode 100644 index 0000000..dc4cafd --- /dev/null +++ b/browzos_options/drag/index.html @@ -0,0 +1,66 @@ + +The Desktop + + + + + + + + + +
+
+
spreadsheet
+
+Spreadsheet
+
+
+ +
+
+
vid
+
+Media Player
+
+
+
+
+
games
+
+Online Games
+
+
+ + +
+
\ No newline at end of file diff --git a/browzos_options/drag/info.png b/browzos_options/drag/info.png new file mode 100644 index 0000000..7233d45 Binary files /dev/null and b/browzos_options/drag/info.png differ diff --git a/browzos_options/drag/logout.png b/browzos_options/drag/logout.png new file mode 100644 index 0000000..fddbc2b Binary files /dev/null and b/browzos_options/drag/logout.png differ diff --git a/browzos_options/drag/media.png b/browzos_options/drag/media.png new file mode 100644 index 0000000..d09995a Binary files /dev/null and b/browzos_options/drag/media.png differ diff --git a/browzos_options/drag/png_fix.css b/browzos_options/drag/png_fix.css new file mode 100644 index 0000000..6571ad6 --- /dev/null +++ b/browzos_options/drag/png_fix.css @@ -0,0 +1,10 @@ +* html img, +* html .png{ +position:relative; +behavior: expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "', sizingMethod='image')", +this.src = "transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "', sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +); +} \ No newline at end of file diff --git a/browzos_options/drag/spacer.png b/browzos_options/drag/spacer.png new file mode 100644 index 0000000..bf867a3 Binary files /dev/null and b/browzos_options/drag/spacer.png differ diff --git a/browzos_options/drag/sprdsht.png b/browzos_options/drag/sprdsht.png new file mode 100644 index 0000000..c0ccb7a Binary files /dev/null and b/browzos_options/drag/sprdsht.png differ diff --git a/browzos_options/drag/swfobject.js b/browzos_options/drag/swfobject.js new file mode 100644 index 0000000..ca8b227 --- /dev/null +++ b/browzos_options/drag/swfobject.js @@ -0,0 +1,216 @@ +/** + * SWFObject v2.0: Flash Player detection and embed - http://blog.deconcept.com/swfobject/ + * + * SWFObject is (c) 2006 Geoff Stearns and is released under the MIT License: + * http://www.opensource.org/licenses/mit-license.php + * + */ +if(typeof deconcept == "undefined") var deconcept = new Object(); +if(typeof deconcept.util == "undefined") deconcept.util = new Object(); +if(typeof deconcept.SWFObjectUtil == "undefined") deconcept.SWFObjectUtil = new Object(); +deconcept.SWFObject = function(swf, id, w, h, ver, c, quality, xiRedirectUrl, redirectUrl, detectKey) { + if (!document.getElementById) { return; } + this.DETECT_KEY = detectKey ? detectKey : 'detectflash'; + this.skipDetect = deconcept.util.getRequestParameter(this.DETECT_KEY); + this.params = new Object(); + this.variables = new Object(); + this.attributes = new Array(); + if(swf) { this.setAttribute('swf', swf); } + if(id) { this.setAttribute('id', id); } + if(w) { this.setAttribute('width', w); } + if(h) { this.setAttribute('height', h); } + if(ver) { this.setAttribute('version', new deconcept.PlayerVersion(ver.toString().split("."))); } + this.installedVer = deconcept.SWFObjectUtil.getPlayerVersion(); + if (!window.opera && document.all && this.installedVer.major > 7) { + // only add the onunload cleanup if the Flash Player version supports External Interface and we are in IE + deconcept.SWFObject.doPrepUnload = true; + } + if(c) { this.addParam('bgcolor', c); } + var q = quality ? quality : 'high'; + this.addParam('quality', q); + this.setAttribute('useExpressInstall', false); + this.setAttribute('doExpressInstall', false); + var xir = (xiRedirectUrl) ? xiRedirectUrl : window.location; + this.setAttribute('xiRedirectUrl', xir); + this.setAttribute('redirectUrl', ''); + if(redirectUrl) { this.setAttribute('redirectUrl', redirectUrl); } +} +deconcept.SWFObject.prototype = { + useExpressInstall: function(path) { + this.xiSWFPath = !path ? "expressinstall.swf" : path; + this.setAttribute('useExpressInstall', true); + }, + setAttribute: function(name, value){ + this.attributes[name] = value; + }, + getAttribute: function(name){ + return this.attributes[name]; + }, + addParam: function(name, value){ + this.params[name] = value; + }, + getParams: function(){ + return this.params; + }, + addVariable: function(name, value){ + this.variables[name] = value; + }, + getVariable: function(name){ + return this.variables[name]; + }, + getVariables: function(){ + return this.variables; + }, + getVariablePairs: function(){ + var variablePairs = new Array(); + var key; + var variables = this.getVariables(); + for(key in variables){ + variablePairs.push(key +"="+ variables[key]); + } + return variablePairs; + }, + getSWFHTML: function() { + var swfNode = ""; + if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) { // netscape plugin architecture + if (this.getAttribute("doExpressInstall")) { + this.addVariable("MMplayerType", "PlugIn"); + this.setAttribute('swf', this.xiSWFPath); + } + swfNode = ' 0){ swfNode += 'flashvars="'+ pairs +'"'; } + swfNode += '/>'; + } else { // PC IE + if (this.getAttribute("doExpressInstall")) { + this.addVariable("MMplayerType", "ActiveX"); + this.setAttribute('swf', this.xiSWFPath); + } + swfNode = ''; + swfNode += ''; + var params = this.getParams(); + for(var key in params) { + swfNode += ''; + } + var pairs = this.getVariablePairs().join("&"); + if(pairs.length > 0) {swfNode += '';} + swfNode += ""; + } + return swfNode; + }, + write: function(elementId){ + if(this.getAttribute('useExpressInstall')) { + // check to see if we need to do an express install + var expressInstallReqVer = new deconcept.PlayerVersion([6,0,65]); + if (this.installedVer.versionIsValid(expressInstallReqVer) && !this.installedVer.versionIsValid(this.getAttribute('version'))) { + this.setAttribute('doExpressInstall', true); + this.addVariable("MMredirectURL", escape(this.getAttribute('xiRedirectUrl'))); + document.title = document.title.slice(0, 47) + " - Flash Player Installation"; + this.addVariable("MMdoctitle", document.title); + } + } + if(this.skipDetect || this.getAttribute('doExpressInstall') || this.installedVer.versionIsValid(this.getAttribute('version'))){ + var n = (typeof elementId == 'string') ? document.getElementById(elementId) : elementId; + n.innerHTML = this.getSWFHTML(); + return true; + }else{ + if(this.getAttribute('redirectUrl') != "") { + document.location.replace(this.getAttribute('redirectUrl')); + } + } + return false; + } +} + +/* ---- detection functions ---- */ +deconcept.SWFObjectUtil.getPlayerVersion = function(){ + var PlayerVersion = new deconcept.PlayerVersion([0,0,0]); + if(navigator.plugins && navigator.mimeTypes.length){ + var x = navigator.plugins["Shockwave Flash"]; + if(x && x.description) { + PlayerVersion = new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split(".")); + } + }else{ + // do minor version lookup in IE, but avoid fp6 crashing issues + // see http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/ + try{ + var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"); + }catch(e){ + try { + var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"); + PlayerVersion = new deconcept.PlayerVersion([6,0,21]); + axo.AllowScriptAccess = "always"; // throws if player version < 6.0.47 (thanks to Michael Williams @ Adobe for this code) + } catch(e) { + if (PlayerVersion.major == 6) { + return PlayerVersion; + } + } + try { + axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); + } catch(e) {} + } + if (axo != null) { + PlayerVersion = new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(",")); + } + } + return PlayerVersion; +} +deconcept.PlayerVersion = function(arrVersion){ + this.major = arrVersion[0] != null ? parseInt(arrVersion[0]) : 0; + this.minor = arrVersion[1] != null ? parseInt(arrVersion[1]) : 0; + this.rev = arrVersion[2] != null ? parseInt(arrVersion[2]) : 0; +} +deconcept.PlayerVersion.prototype.versionIsValid = function(fv){ + if(this.major < fv.major) return false; + if(this.major > fv.major) return true; + if(this.minor < fv.minor) return false; + if(this.minor > fv.minor) return true; + if(this.rev < fv.rev) return false; + return true; +} +/* ---- get value of query string param ---- */ +deconcept.util = { + getRequestParameter: function(param) { + var q = document.location.search || document.location.hash; + if(q) { + var pairs = q.substring(1).split("&"); + for (var i=0; i < pairs.length; i++) { + if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) { + return pairs[i].substring((pairs[i].indexOf("=")+1)); + } + } + } + return ""; + } +} +/* fix for video streaming bug */ +deconcept.SWFObjectUtil.cleanupSWFs = function() { + var objects = document.getElementsByTagName("OBJECT"); + for (var i=0; i < objects.length; i++) { + objects[i].style.display = 'none'; + for (var x in objects[i]) { + if (typeof objects[i][x] == 'function') { + objects[i][x] = function(){}; + } + } + } +} +// fixes bug in fp9 see http://blog.deconcept.com/2006/07/28/swfobject-143-released/ +if (deconcept.SWFObject.doPrepUnload) { + deconcept.SWFObjectUtil.prepUnload = function() { + __flash_unloadHandler = function(){}; + __flash_savedUnloadHandler = function(){}; + window.attachEvent("onunload", deconcept.SWFObjectUtil.cleanupSWFs); + } + window.attachEvent("onbeforeunload", deconcept.SWFObjectUtil.prepUnload); +} +/* add Array.push if needed (ie5) */ +if (Array.prototype.push == null) { Array.prototype.push = function(item) { this[this.length] = item; return this.length; }} + +/* add some aliases for ease of use/backwards compatibility */ +var getQueryParamValue = deconcept.util.getRequestParameter; +var FlashObject = deconcept.SWFObject; // for legacy support +var SWFObject = deconcept.SWFObject; diff --git a/browzos_options/drag/term.png b/browzos_options/drag/term.png new file mode 100644 index 0000000..f86c784 Binary files /dev/null and b/browzos_options/drag/term.png differ diff --git a/browzos_options/drag/wproc.png b/browzos_options/drag/wproc.png new file mode 100644 index 0000000..daf84b2 Binary files /dev/null and b/browzos_options/drag/wproc.png differ diff --git a/browzos_options/games/dk.html b/browzos_options/games/dk.html new file mode 100644 index 0000000..3a951ad --- /dev/null +++ b/browzos_options/games/dk.html @@ -0,0 +1,21 @@ + + + +Donkey Kong + + + + + +
+ +
+ + \ No newline at end of file diff --git a/browzos_options/games/dk.swf b/browzos_options/games/dk.swf new file mode 100644 index 0000000..70ba493 Binary files /dev/null and b/browzos_options/games/dk.swf differ diff --git a/browzos_options/games/doom.html b/browzos_options/games/doom.html new file mode 100644 index 0000000..a48a177 --- /dev/null +++ b/browzos_options/games/doom.html @@ -0,0 +1,21 @@ + + + +DOOM + + + + + +
+ +
+ + \ No newline at end of file diff --git a/browzos_options/games/doom.swf b/browzos_options/games/doom.swf new file mode 100644 index 0000000..cda747a Binary files /dev/null and b/browzos_options/games/doom.swf differ diff --git a/browzos_options/games/index.html b/browzos_options/games/index.html new file mode 100644 index 0000000..0e1864c --- /dev/null +++ b/browzos_options/games/index.html @@ -0,0 +1,31 @@ + + + +Games + + + + + +
Games Available:
+

Megaman Project X

+

Ultimate Flash Sonic

+

Donkey Kong

+

Metroid Elements

+

Doom

+

Madness Interactive: +America's Army Mod

+ + \ No newline at end of file diff --git a/browzos_options/games/madnessaa.html b/browzos_options/games/madnessaa.html new file mode 100644 index 0000000..70bf775 --- /dev/null +++ b/browzos_options/games/madnessaa.html @@ -0,0 +1,21 @@ + + + +Madness Interactive: America's Army Mod + + + + + +
+ +
+ + \ No newline at end of file diff --git a/browzos_options/games/madnessaa.swf b/browzos_options/games/madnessaa.swf new file mode 100644 index 0000000..02ed25b Binary files /dev/null and b/browzos_options/games/madnessaa.swf differ diff --git a/browzos_options/games/megaman.html b/browzos_options/games/megaman.html new file mode 100644 index 0000000..33d67cc --- /dev/null +++ b/browzos_options/games/megaman.html @@ -0,0 +1,21 @@ + + + +Megaman Project X + + + + + +
+ +
+ + \ No newline at end of file diff --git a/browzos_options/games/megaman.swf b/browzos_options/games/megaman.swf new file mode 100644 index 0000000..fd973d5 Binary files /dev/null and b/browzos_options/games/megaman.swf differ diff --git a/browzos_options/games/metroid-elements.swf b/browzos_options/games/metroid-elements.swf new file mode 100644 index 0000000..b5f2edd Binary files /dev/null and b/browzos_options/games/metroid-elements.swf differ diff --git a/browzos_options/games/metroid.html b/browzos_options/games/metroid.html new file mode 100644 index 0000000..4685a0a --- /dev/null +++ b/browzos_options/games/metroid.html @@ -0,0 +1,19 @@ + + + +Metroid Elements + + + + + +
+ +
+ + \ No newline at end of file diff --git a/browzos_options/games/mrsound.swf b/browzos_options/games/mrsound.swf new file mode 100644 index 0000000..dd0f905 Binary files /dev/null and b/browzos_options/games/mrsound.swf differ diff --git a/browzos_options/games/snailiad.html b/browzos_options/games/snailiad.html new file mode 100644 index 0000000..8622b2e --- /dev/null +++ b/browzos_options/games/snailiad.html @@ -0,0 +1,21 @@ + + + +Snailiad + + + + + +
+ +
+ + \ No newline at end of file diff --git a/browzos_options/games/snailiad.swf b/browzos_options/games/snailiad.swf new file mode 100644 index 0000000..64ad996 Binary files /dev/null and b/browzos_options/games/snailiad.swf differ diff --git a/browzos_options/games/ufsonic.html b/browzos_options/games/ufsonic.html new file mode 100644 index 0000000..fe50850 --- /dev/null +++ b/browzos_options/games/ufsonic.html @@ -0,0 +1,21 @@ + + + +Ultimate Flash Sonic + + + + + +
+ +
+ + \ No newline at end of file diff --git a/browzos_options/games/ufsonic.swf b/browzos_options/games/ufsonic.swf new file mode 100644 index 0000000..620e14d Binary files /dev/null and b/browzos_options/games/ufsonic.swf differ diff --git a/browzos_options/instmess/chat.jar b/browzos_options/instmess/chat.jar new file mode 100644 index 0000000..9af8e24 Binary files /dev/null and b/browzos_options/instmess/chat.jar differ diff --git a/browzos_options/instmess/chat.php b/browzos_options/instmess/chat.php new file mode 100644 index 0000000..fa5b109 --- /dev/null +++ b/browzos_options/instmess/chat.php @@ -0,0 +1,134 @@ + + + +Tech's Test Rig Chat + + + + + +

Morevil Web Chat Lite 2.4 www.morevil.com

+\n";exit();} +$d=explode("\n",str_replace("\\", "", $d)); +foreach($d as $w){$x=substr($w,0,1);$$x=substr($w,1);} +$p = ""; +$tp=30;$st=$p."chat.txt";$in=$p."chat.tmp";$sm=$p."chat.dat"; +$r=array($st,$in,$sm); +$q="\$-\$$l\n"; +if($a==1){if(f1()){ao();}} +else if($a==2 || $a==4){ +if($j!="n" && $j < 0x8000 && $I=of($in,"r+")){fseek($I, $j+22); fwrite($I, time());fclose($I);} +if($a==4){ +if($T= of($st, "a")){fwrite ($T, "d$b : $k\n");fclose($T);} +$q.= "k\n"; +clearstatcache(); +} +if($T=of($st,"r")){ +fseek($T, $i); $q.=fread($T, 10000)."\ng".filesize($st)."\n"; fclose($T); +} +} +else if($a==3){if(f1()){lo();}} +else if($a==9 && $j!="n" && $b!=""){ +$q.="uu\n"; +if($I=of($in,"r+")){ +fseek($I, $j); +if(trim(fread($I, 22))==$b){ +fseek($I, $j); +fwrite($I, str_repeat(" ",32));fclose($I); +if($T= of($st, "a")){fwrite ($T, "f$b\n");fclose($T);} +} +} +} +echo "\n$q\n\$-\$\n"; +function f1(){ +global $q, $r, $i, $p; +if($i==""){ +foreach($r as $f){ +if(!file_exists ($f)){ +if(!($F = fopen($f, "a"))){$q.="n$p\n";return 0;} +fclose($F);chmod($f,0777); +} +} +} +return 1; +} +function lo(){ +global $q, $tp, $in, $st, $sm, $b, $c; +if(strlen($b)>20 || strlen($b)==0){$q.="c3\n";return;} +if(($I = of($in,"r+")) && ($T = of($st, "a"))){ +$n=-1; $tp=time()-$tp; +fseek($I, 0); +$m="";if(filesize($in)>0){$m = fread($I, filesize($in));} +$l = strlen($m) - 3; +for($i=0;$i<$l;$i+=32){ +$s = rtrim(substr($m,$i,22)); $t = substr($m,$i+22,10); +if($t>0){ +if($t>$tp){ +if($s==$b){$q.="c1\n";$n=-2;break;} +} +else{ +fseek($I, $i); fwrite($I, str_repeat(" ",32)); +fwrite($T, "l$s\n"); +if($n==-1){$n=$i;} +} +}else if($n==-1){$n=$i;} +} +if($n!=-2){ +if($n==-1){$n=$i;} +if($M=of($sm,"r+")){ +$m = "";if(filesize($sm)>0){$m=fread($M, filesize($sm));}else{fwrite($M, " ");} +$uo = str_replace("=", " ", pack("V",crc32($b))); $po = str_replace("=", " ", pack("V",crc32($c))); +if($x=strpos($m,"=$uo")){ $u=2;if("=$uo$po" != substr($m,$x,9)){$q.="c2\n";$u=1;}} +if($u!=1){ +if($c!="" && $u!=2){fseek($M, 0, SEEK_END); fwrite($M, "=$uo$po");} +fseek($I, $n); fwrite($I, str_pad($b,22).time()); +fseek($T, 0, SEEK_END); fwrite($T, "h$b\n"); +$q.="a$b\ni$n\n"; +} +}fclose($M); +} +fclose($I);fclose($T); +}else{$q.="c4\n";} +} +function ao(){ +global $q, $r, $tp, $in, $st, $b; +if(($I = of($in,"r+")) && ($T = of($st, "a"))){ +$a=0; $tp=time()-$tp; +fseek($I, 0); +$m = fread($I, filesize($in)); +$l = strlen($m) - 3; +for($i=0;$i<$l;$i+=32){ +$s = rtrim(substr($m,$i,22)); $t = substr($m,$i+22,10); +if($t>0){ +$a=1; +if($t>$tp){$q.="e$s\n"; } +else{ +fseek($I, $i); fwrite($I, str_repeat(" ",32)); +fwrite($T, "l$s\n"); +} +} +} +if($a==0){if(filesize($st)>2048){ftruncate($T, 0);}ftruncate($I, 0);} +fclose($I);fclose($T); +$q.="y1\ng".filesize($st)."\n"; +} +} +function of($b,$c){$i=0;while($i++ < 2){if($a = fopen($b,$c)){return $a;}usleep(200);}return 0;} +$data_file_to_delete = "error_log"; +while(is_file($data_file_to_delete) == TRUE) +{ +chmod($data_file_to_delete, 0666); +unlink($data_file_to_delete); +} +?> + \ No newline at end of file diff --git a/browzos_options/instmess/index.html b/browzos_options/instmess/index.html new file mode 100644 index 0000000..1d354e1 --- /dev/null +++ b/browzos_options/instmess/index.html @@ -0,0 +1,24 @@ + + + +Tech's Test Rig Chat + + + + + +
+ + + +Your browser is NOT Java enabled. + +
+ + \ No newline at end of file diff --git a/browzos_options/instmess/readme.txt b/browzos_options/instmess/readme.txt new file mode 100644 index 0000000..cc343af --- /dev/null +++ b/browzos_options/instmess/readme.txt @@ -0,0 +1,10 @@ + + Morevil Web Chat Lite 2.4 for Web Site + + Please read the documentation. + + + + Copyright (c) 2000-2004 Morevil Software. All rights reserved + + http://www.morevil.com/ \ No newline at end of file diff --git a/bzcmd/exec.js b/bzcmd/exec.js new file mode 100644 index 0000000..d2a00f8 --- /dev/null +++ b/bzcmd/exec.js @@ -0,0 +1,2413 @@ +// ×òåíèå èç ôàéëà îïèñàíèé ïðîöåññîâ +function GetDescr (n) +{ + var s = fget ('/etc/daemon.cf'), i, S + if (!IOResult) + { + S = s.split (/\n/) + for (i in S) + { + s = S[i].split (/ +/) + if (s[0]==n) return '#'+s[1] + } + } + return '' +} + +// Ïðîèçâîäèò çàìåíó ïðèìèòèâîâ (-l name) +function PriRep(str) +{ + var re = !MZ && NC && Version<5 ? '[-(\\w) ([^]]+)\\]' : '\\[-(\\w) ([^\\]]+)\\]' + return str.replace(new RegExp(re, 'g'),'PriFunc("$1","$2")') +} + +function PriFunc(f,name) +{ + switch (f) + { + case 'e': f = (fexists(name),!IOResult);break + case 'z': f = !fsize(name); break + case 'f': + case 'd': + case 'w': + case 'x': + case 'r': var r = faccess(name) + if (IOResult) f = false; else + f = f=='f' && r.nm('d') || r.m(f) + break + default: f = true + } + IOResult = 0 + return f +} + +// Âû÷èñëÿåò ðàçíèöó ìåæäó äâóìÿ äèðàìè +function SubcDirs(p1,p2) +{ + p1 = FullPath(p1).snsplit("/") + p2 = FullPath(p2).snsplit("/") + + for (var i = 0; i 4) + expr = 'try {\n\texpr = ' + expr + '\n} catch(e) {\n\tfappend("/core.dump","' + + expr.replace(/"/g, '\\"') + + ' -> sheval ->\\n")\n\tIOResult = errEvalError\n'+ + "\tfappend('/core.dump',(expr = e.description)+'\\n\\n')\n}" + + return eval(expr) +} + +// Ïðèìåíåíèå ìàñêè ôàéëà è FullName íà ìàññèâ (áåç 1st ýë-òà) +function DoName(arr) +{ + var out = new Array(), k = 0 + + for (i = 1; i1 && arr[1]!="") return out; else + return new Array("") +} + +// Äëÿ êîìàíäû cd +function prepcd(str) +{ + if (faccess(str).nm('x')) return "cd: "+ErrorMsg(IOResult = errNoAccess) + var Path = SearchMask(FullPath(str)) + if (Path.length>0) SetVar('pwd',WD=Path[0]) + return '' +} + +// Ñðåçàåò ïðîáåëû ñëåâà +function ltrim(str) +{ + if (/^\s*(\S.{0,})$/.test(str)) return RegExp.$1 + return str +} + +// Ðàçáîð àäðåñà for sed +function CheckAddress(line,num,com,len) +{ + var flag; + if (/^(\d+),(\d+)(!?)(.{0,})/.test(com)) + { + flag=((num>=RegExp.$1) && (num<=RegExp.$2)); + if (RegExp.$3=='!') flag=!flag; + if (flag) return RegExp.$4; + return false; + } else + if (/^(\d+)(!?)(.{0,})/.test(com)) + { + flag=(num==RegExp.$1); + if (RegExp.$2=='!') flag=!flag; + if (flag) return RegExp.$3; + return false; + } else + if (/^\/([^\/]+)\/(!?)(.{0,})/.test(com)) + { + var res=RegExp.$3; + var reg=new RegExp(RegExp.$1); + var inv=RegExp.$2; + flag=(reg.test(line)); + if (inv=='!') flag=!flag; + if (flag) return res; + return false; + } else + if (/^\$(!?)(.{0,})/.test(com)) + { + flag=(num==len); + if (RegExp.$1=='!') flag=!flag; + if (flag) return RegExp.$2; + return false; + } else return com; +}; + +// Çàìåíà ñïåöñèìâîëîâ (äî ïðîáåëà) íà \HH + +function Spec2Hex(str) +{ + if (!/\W/.test(str)) return str; + var c; + for (var i=0,out='';i< \*\?\[\]]/.test(str.c(i))) out+="\\" + out+=str.c(i) + } + return out +} + +// Ýêðàíèðîâêà Unix spec chars +function QuoteMeta3(str) +{ + for (var out="",i=0;i< \$\*`\?\[\]]/.test(str.c(i))) out+="\\" + out+=str.c(i) + } + return out +} + +// Sed +function Sed(line,Script,lineout) +{ + var Lines=line.split("\n"),buffer='',res,out='',c,str,i,j; + var addon=''; // Äîáàâêà ïîñëå áóôåðà (äëÿ r) + var XpaH=''; // Õðàíèëèùå + var newfor=0,chg=0,brack=0,addr=''; + line=null; + var len=Lines.length, scln + for (i=0;i=0) + { + daemoncnt++ + chaincnt++ + + var max = last?Math.floor(6/last):12 + if (max<2) max = 2 + if (daemoncnt>max) + { + daemoncnt = 0 + if (last=0; daemoncur--) + if (/daemon/.test(com = Chain[daemoncur])) break + if (daemoncur>=0) PID = PIDs[daemoncur],Exec(com),PID = 0 + daemoncur-- + } + + if (chaincnt>2) + { + chaincnt = 0 + for (cur = last; cur>=0; cur--) + if (!/daemon/.test(com = Chain[cur])) break + if (cur>=0) + { + PID = PIDs[cur] + Chain.del(cur) + PIDs.del(cur) + Exec(com) + PID = 0 + } + } + } else + { + daemoncnt = chaincnt = 0 + if (pHnd!=null) clearInterval(pHnd) + pHnd = null + } +}; + +// Óñòàíîâèòü ïåðåìåííóþ +function SetVar(name,val) +{ + IOResult = errNoError + if (name.charAt(0)=='$') name=name.substring(1) + if (name.length>1 && /[- ]/.test(name)) return IOResult = errInvSyntax + + if (VarNames.length!=VarValues.length) {EL(255);return ''}; + for (var i=0;i= âòîðîé +function Compare(s1,s2) +{ + if (s1.lengthVals.length || i[0]<=0 || Vals=="") return IOResult = errOutOfBounds + return Vals[i[0]-1].substring(i[1]-1,i[2]-1) + }; + + if (name.m('[')) return IOResult = errInvIndex; + + switch(name) + { + case "*": return GetVar("argv[*]") + case "#": return GetVar("#argv") + case '-': return ''; + case '$': return PID + }; + return GetRealVar(name); +}; + + +// Âîçâðàùàåò çíà÷åíèå ïåðåìåííîé, åñëè $?ïåðåìåííàÿ, èíà÷å - '' +function GetRealVar2(str) +{ + if (str = GetRealVar(str),IOResult) return '' + return str +} + +// Hàéòè ïåðåìåííóþ, max. ñîîòâ. èìåíè+îñòàòîê +function SearchVar(name) +{ + var max=0,i,p,idx; + if (VarNames.length!=VarValues.length) {EL(255);return ''}; + + for (i=0;imax) max=p,idx=i; + }; + if (max<2) return ''; + return VarValues[idx]+name.substring(VarNames[idx].length+1); +}; + +// Ïûòàåòñÿ çàìåíèòü âñå ïåðåìåííûå íà èõ çíà÷åíèÿ +// (ðàññìàòðèâàåòñÿ ÷àñòü ñòðîêè ïîñëå '=' ) +function ReplaceAllVars() +{ + IOResult = errNoError + str = arguments[0] + if (/^(\$?\w+=)(.+)/.test(str)) + var out = RegExp.$1,str = RegExp.$2; else var out = "" + var idx,i, name + + while ((idx = str.indexOf("$"))>=0) + { + if (idx>0 && str.c(idx-1) == "\\") + out+= str.substring(0,idx-1)+'$', + str = str.substr(idx+1); else + { + out+= str.substring(0,idx) + str = str.substring(idx+1) + + if ( + /^([\w_][_\w\d]*\[[\d\*,]+\]?)(.{0,65535})$/.test(str) || + /^([_\w\d\{\}]+)(.{0,65535})$/.test(str) || + /^([#\?][_\w\d]+)(.{0,65535})$/.test(str) || + /^(%\*)(.{0,65535})$/.test(str) || + /^(%\d+)(.{0,65535})$/.test(str) || + /^(\d+)(.{0,65535})$/.test(str) || + /^([\*\$#-])(.{0,65535})$/.test(str)) + name = RegExp.$1, str = RegExp.$2; else + name = str, str = "" + + if (/^\{(.*)\}(.{0,65535})$/.test(name)) + name = RegExp.$1, str = RegExp.$2+str + + out+= GetVar(name) + } + } + return out+str +}; + +// Àíàëîã :N â TPascal'e +function Norm(str,n) +{ + str+=''; + var l=str.length; + for (var i=l;i<=n;i++) str=' '+str; + return str; +}; + +// Àíàëîã :N â TPascal'e, íî ñ äðóãîãî êðàÿ +function Norm2(str,n) +{ + str+=''; + var l=str.length; + for (var i=l;i<=n;i++) str=str+' '; + return str; +}; + +// Óðåçêà ñòðîêè +function CutStr(str,n) +{ + if (str.length<=n) return str; + return str.substr(0,n-3)+"..."; +}; + +// Ïîèñê [íå]ñîâïàäåíèé +function Grep(s, r, flag, case_ins) +{ + var str=s.split(/\n/), out='', s + if (case_ins) r = r.toLowerCase() + + /*@cc_on + @if (@_jscript_version>4) + try + {@end @*/ + var reg=new RegExp(r) + /*@cc_on + @if (@_jscript_version>4) + } + catch (e) + { + return "" + }; + @end + @cc_off @*/ + + for (var i=0;ito))) + for (;fr<=to;fr++) Nu[z++]=fr + }; + }; + var str=s.split(/\n/), Line, out = '' + for (i=0;i=0) + { + if (idx>0 && str.c(idx-1)=="\\") + out+= str.substring(0,idx-1)+"`", + str = str.substr(idx+1); else + { + if (flag) + out+= Exec(str.substring(0,idx)), + str = str.substr(idx+1),flag = 0; else + out+= str.substring(0,idx), + str = str.substr(idx+1),flag = 1 + } + + } + return out+str +}; + +// Ïûòàåòñÿ óãàäàòü êàêèå ïðàâà äîñòóïà äîëæíû áûòü ó ôàéëà +function GuessAccess(i) +{ + if (typeof(Files[i])=="undefined") return "-rw-" + var con = Files[i].substr(0,2) + if (con=="\n>") return "lrw-" + if (con=="#/") return "-rwx" + if (typeof(Names[i])!="undefined" && Names[i].m("/bin/") && Files[i].length<501) + return "-rwx" + + return "-rw-" +} + + +// Ïðîèçâîäèò èñïðàâëåíèå ôàéëîâîé ñèñòåìû +function RubScan() +{ + EL(0); + var err=0,i,j,item,out=''; + + if (typeof(Access)=="undefined" || typeof(Access.length)=="undefined" || typeof(Access.constructor)=="undefined" || (Access.constructor+"").nm("Array")) Write("\nAccess table corrupted. Droped."),err++,Access = new Array() + if (typeof(Names)=="undefined" || typeof(Names.length)=="undefined" || typeof(Names.constructor)=="undefined" || (Names.constructor+"").nm("Array")) Write("\nNames table corrupted. Droped."),err++,Names = new Array() + if (typeof(Files)=="undefined" || typeof(Files.length)=="undefined" || typeof(Files.constructor)=="undefined" || (Files.constructor+"").nm("Array")) Write("\nFiles table corrupted. Droped."),err++,Files = new Array() + + for (i=0;i')) + { + fexists(Files[i].substr(2)); + if (IOResult) + Write('\nLink to unexists file. Corrected.'),err++, + Names = DeleteItem(Names,i), + Files = DeleteItem(Files,i), + Access = DeleteItem(Access,i) + }; + + for (j=0;j1) + { + key+= args[i]+" " + args = DeleteItem(args,i) + i-- + } + var cnt=args.length-1; + + //  ïåðåìåííîé parms íàõîäÿòñÿ âñå àðãóìåíòû + var parms=args.slice(1,cnt+1).join(' ') + + // Îáðàáîòêà àëèàñîâ. Äîëæíà ñòîÿòü ïåðâîé, ÷òî áû ìîæíî + // áûëî íàçíà÷èòü àëèàñû âñòðîåííûì êîìàíäàì + if (com=='alias' || com=='unalias') + { + var name = "/etc/alias.cf" + if (fexists(name),!IOResult) + { + out = fget(name) + if (IOResult) return ErrorMsg(IOResult) + } else out = "" + out = out.split("\n") + + if (com == 'alias') + { + if (cnt>1) + { + if (out.length>1) + { + for (i = 0; i=0) + str = str.substr(j); else str = "" + str = Exec(out[i+1]+str) + alias = "" + return str + } + } + out = "" + } + IOResult = errNoError + if (/^[^=]+=.{0,}$/.test(com)) + { + + EL(IOResult = 0) + if (/^([^=]+)=(.{0,})$/.test(str)) + SetVar(RegExp.$1,RegExp.$2) + else return Exec(Vars[i]) + if (IOResult) return ErrorMsg(IOResult) + return '' + } else + if (com.c(0)=='#') + { + if (/^#(VB|JS):/i.test(com)) + { + parms=parms.replace(/\slt\s/g,"<").replace(/\sgt\s/g,">").replace(/\sand\s/g,"&&").replace(/\sor\s/g,"||") + /*@cc_on + @if (@_jscript_version>4) + try + {@end @*/ + if (/^#VB:/i.test(com)) + { + if (NC) return "" + out=execScript(parms,'VBScript') + } else + out=eval(parms) + /*@if (@_jscript_version>4) + } catch (e) {return e.description} + @end + @cc_off @*/ + return out + + } else return "" + } else + if (com=='clear' || com=='c') return Clear(),''; else + if (com=='restart') + { + if (IE) + document.execCommand('refresh'); else + location=location + return '' + } else + if (com=='id') + { + return "uid=0("+login+")" + } else + if (com=='uname') + { + if (key.m('a')) key = "snrvm" + if (key=="" || key.m('s')) out = "JUnix " + if (key.m('n')) out+="localhost " + if (key.m('r')) out+=Ver+" " + if (key.m('v')) out+="#"+document.lastModified+" " + if (key.m('m')) out+="Browser: "+(IE?"IE":(MZ?'MOZ':"NC"))+" "+Version+" " + return out + } else + if (com=='?') + { + EL(0) + var Man = fget ('/bin/manual'), line = "" + if (IOResult) + if (fexists ('/bin/manual.jz'),!IOResult) + var Man = Run ('jzip -d /bin/manual.jz'); else + return 'manual: '+ErrorMsg(IOResult) + Man = Man.split(/\n/).sort() + + for (i=0;i4) + try + {@end @*/ + var shell = new ActiveXObject("WScript.Shell") + shell.Run('"'+(Res[1]+"/"+args[1]).replace(/\//g,"\\")+'"'+args.slice(2).join(" ")) + /*@if (@_jscript_version>4) + } catch (e) {return e.description}; + @end + @cc_off @*/ + return "Application started." + } + return "Cannot start W-Shell" + } else + if (com=='break') + { + if (Break.length) + return Break.rep(1),""; else + return com+': Not in while/foreach.'; + } + else + if (com=='continue') + { + if (Break.length) + return Break.rep(2),""; else + return com+': Not in while/foreach.'; + } + else + if (com=='source') + { + if (!cnt) return EL(IOResult = errFewArguments), ErrorMsg(IOResult) + Mask = DoName(args) + EL(0) + for (i = 0; i=2000) year-=2000; else + { + fy+=1900 + if (year>=100) year-=100 + } + + var r = Math.ceil((d.getTime() - (new Date(fy,0,1,0,0,0)).getTime())/86400000) + + if (/^(\S+)\s+(\S+)/.test(d+"")) + var day = RegExp.$1, mon = RegExp.$2; else + var day = "", mon = "" + + out = out.replace(/%D/g,"%m/%d/%y").replace(/%T/g,"%H:%M:%S") + out = out.replace(/%%/g,"%").replace(/%n/g,"\n").replace(/%t/g,"\t") + out = out.replace(/%m/g,(1+d.getMonth()+"").lz(2)).replace(/%d/g,(""+d.getDate()).lz(2)) + out = out.replace(/%y/g,(year+"").lz(2)).replace(/%H/g,(""+d.getHours()).lz(2)) + out = out.replace(/%M/g,(d.getMinutes()+"").lz(2)).replace(/%S/g,(""+d.getSeconds()).lz(2)) + out = out.replace(/%r/g,(d.getHours()%13+"").lz(2)).replace(/%w/g,(d.getDay()+"")) + out = out.replace(/%h/g,mon).replace(/%a/g,day).replace(/%f/g,fy).replace(/%j/g,r) + return out + + } else + if (com=='dialog') + { + // Ñëåøè ïåðåä [ è ] äëÿ ñòàðîãî NC óáðàíû - îí èõ íå ïîíèìàåò + + if (!MZ && NC && Version < 5) + var ts = new RegExp("\\[([^])]*)\\]\s+\[([^]]+)\\]").test(parms); else + var ts = /\[([^\])]*)\]\s+\[([^\]]+)\]/.test(parms) + + if (ts) + { + var pipe + prompt = RegExp.$1, dlgcom = RegExp.$2 + + if (pipefile != "") + { + dialog = 0 + if (/([^\n]*)\n(.+)/.test(pipefile)) + pipe = RegExp.$1, pipefile = RegExp.$2; else + pipe = pipefile, pipefile = "" + + out = Exec(DialogRep(dlgcom,pipe)) + if (pipefile == "") dialog = 0, dlgcom + return out + } + + dialog = 1 + return '' + } + EL(3) + return 'Missing argument.' + } else + if (com=='grep') + { + EL(0) + var Pat=args[1] + Mask = DoName(args.slice(1)) + if (cnt<1) {EL(3);return 'Missing argument.'} + for (i = 0; i=0) + r = Grep(r, Pat, key.m('v'), case_ins) + if ((key.m('h')) && (r!='')) r='\n'+Mask[i]+'\n'+r + if ((key.m('l')) && (r!='')) r='\n'+Mask[i] + if (key.m('c')) + { + if (r=='') r=0; else + r=r.split(/\n/).length + } + out+=r + } + } + return out + } else + if (com=='compress' || com=='jzip') + { + var flag=key.m('d') + var pack=com=='jzip' + var jbou = key.m('l') ? 4 : 3; + + Mask=DoName(args); + for (i = 0; i=1) wd=FullPath(args[1]); else wd=WD + var Dirs=SearchMask(wd) + + var Fl1=((key.m('o')) || (key.m('g'))); + var Fl2=((Fl1) || (key.m('l'))); + var Fl3=key.m('a'); + var Fl4=key.m('1'); + var user = ' '+Norm(login,9); + for (k=0;k").replace(/\sand\s/g,"&&").replace(/\sor\s/g,"||") + if ((expr = ReplaceAllVars(expr),IOResult) || (expr = ReplaceW(expr),IOResult)) + return out+'\n'+ErrorMsg(IOResult) + expr = sheval(PriRep(expr)) + + if (IOResult) return expr + if (expr) return Exec(coms); else + if (els!='') return Exec(els) + return "" + } else + if (com=='foreach') + { + Break.push(0) + if (/^foreach (.+) \((.+)\);(.+)$/.test(bustr)) + { + var varname = StripSlashes(RegExp.$1), + comstr = RegExp.$3, + list = ReplaceAllVars(RegExp.$2) + if (IOResult) return ErrorMsg(IOResult) + list = AddMask(list).slice(1,-1) + list = list.snsplit() + + for (i = 0; i").replace(/\sand\s/g,"&&").replace(/\sor\s/g,"||") + do { + ex = expr + if ((ex = ReplaceAllVars(ex),IOResult) || (ex = ReplaceW(ex),IOResult)) + return out+'\n'+ErrorMsg(IOResult) + ex = sheval(PriRep(ex)) + if (IOResult) + { + out = "while: "+ex + break + } + if (ex) out+=Exec(comstr) + if (Break.top()==2) Break.rep(0) + } + while (ex && --MAX && !Break.top()) + } + if (!MAX) out+="\nwhile: Overload." + + Break.pop() + return out + } else + if (com=='rm') + { + EL(0) + var code + Mask=DoName(args) + for (i = 0; iVals.length) {EL(IOResult=errOutOfBounds);return ErrorMsg(IOResult)} + + if (idx=="*") + Vals = val.split(" ");else + Vals[idx-1] = val + if (Vals.length>1) val_t = "("+Vals.join(" ")+")" + else val_t = Vals[0] + val = val_t + } + + val = AddMask(val_t = val) + if (val.slice(1,-1)==StripSlashes(val_t)) val = val.slice(1,-1) + SetVar(name,StripSlashes(val)) + if (IOResult) return "set: "+ErrorMsg(IOResult) + } + } + return '' + + } else + if (com=='shift') + { + var idx,str; + if (!cnt) str = "argv"; else str = args[1]; + for (i in VarNames) + if (VarNames[i] == str && /\(([^)]*)\)/.test(VarValues[i])) + { + str = RegExp.$1; + idx = str.indexOf(' '); + if (idx<0) + VarValues[i] = '()'; else + VarValues[i] = '('+str.substr(idx+1)+')'; + }; + return ''; + } else + if (com=='save') + { + if (!cnt) {EL(3);return 'Missing argument.'} + if (MountExists(args[1] = FullName(args[1])),IOResult) + {EL(7);return 'Invalid argument.'} + if (Names.length != Access.length || Names.length != Files.length) + {EL(255);return 'File system error.'} + var len = 0, + quote = new Function("str","return str.replace(/~/g,'~1').replace(/\\x00/g,'~2')") + + for (i = 0;i=Icq.length) return "ICQ alias not found." + icq = Icq[i-1] + } else + { + Icq[i-1] = message + Icq[i]= icq + fput(file, Icq.join("\n")) + if (IOResult) + return ErrorMsg(IOResult) + return "ICQ alias successfully saved." + } + + } + + if (NC) message=escape(message) + SetNavi('http://msg.mirabilis.com/scripts/WWPMsg.dll?fromemail='+ + login+'&from='+login+'&subject=&to='+icq+'&body='+message); + + return 'Message was sent to ICQ server for '+icq+'.'; + } else + if (com=='passwd') + { + if (!cnt) return Run('dialog [Enter old password: ] [passwd PARM]') + if (cnt<2) return Run('dialog [Enter new password: ] [passwd '+args[1]+' PARM]') + password = args[2] + return "" + } else + if (com=='where') + { + if (!cnt) {EL(3);return 'Missing argument.'} + var Paths=GetVar('path[*]'),name; + if (IOResult) return IOResult + Paths = Paths.split(" ") + for (i in Paths) + { + fexists(name = FullPath(Paths[i])+args[1]) + if (!IOResult) out+=name+'\n' + } + if (!out) + { + var List = Run('?').split(' ') + for (i in List) + if (List[i]==args[1]) + { + out = 'Built-in command.' + break + } + } + + return out + + } else + if (com=='d') + { + if (key.m('l')) + { + EL(0) + if (cnt<1) {EL(3);return 'Missing argument.'}; + var type = sheval("typeof("+args[1]+")") + if (IOResult) return type + + if (type=="undefined") {EL(7);return 'Invalid argument.'}; + if (/object/.test(type)) + { + var Obj = args[1].split('.'); + + for (i = 1; i1) + { + sheval(args[1]+"=window.open('"+args[2]+"')") + return "Opened." + } + return EL(IOResult = errFewArguments), "d: "+ErrorMsg(IOResult) + } else + if (com=='mount') + { + if (!cnt) + { + var Dev,vfs; + out+=" mounted mounted over vfs options\n"+ + "----------- ---------------- ------- ---------\n"+ + "Junix / jsfs"; + + for (i=0;i2 && Dev[2].m("r")) out+="R/Only " + if (vfs=='cook') out+="JZipped" + }; + return out + }; + if (cnt<2) {EL(3);return 'Missing argument.'}; + + if (key.m("c")) cont = args[1]; else + { + fexists(args[1] = FullName(args[1])); + if (IOResult) {EL(7);return 'Invalid argument.'}; + var cont = fget(args[1]), acc = faccess(args[1]) + + if (!IOResult && acc.c(0)!="b") return "Block device required." + if (IOResult || cont.nm(",")) {EL(7);return 'Invalid argument.'}; + }; + + if (key.m("r") || ROMount) cont+=",r" + args[2]=FullPath(args[2]) + if (args[2]=="/") return "Device busy." + + for (i in Mount) if (Mount[i] == args[2]) + { + Device[i] = cont; + return "Device was remounted."; + }; + Mount = Mount.concat(args[2]); + Device = Device.concat(cont); + return "Device was mounted."; + } else + if (com=='unmount') + { + if (!cnt) {EL(3);return 'Missing argument.'}; + if (args[1]=="/") return "Device busy."; + args[1] = FullPath(args[1]); + for (i=0;i=0) + out+=con.substr(0,nums); else + if (con.length+nums<0) + out+=con; else + out+=con.substr(con.length+nums); + break; + case 'l': var Lines=con.split(/[\n]/); + if (nums>=0) + out+=Lines.slice(0,nums).join('\n'); else + if (Lines.length+nums<0) + out+=con; else + out+=Lines.slice(Lines.length+nums).join('\n'); + break; + }; + }; + return out; + } else + { + var scval,arval,ar,sc + arval = GetRealVar("argv"); if (IOResult) arval = "()" + scval = scriptname; if (IOResult) scval = "jush" + + if (/(\S+)\s+(.+)/.test(str)) + ar = '('+RegExp.$2+')',sc = RegExp.$1; else ar = '()',sc = str + + SetVar('argv',ar),scriptname = sc + var name + parms=key+parms + args=(com+(parms!=''?' ':'')+parms).split(' ') + cnt=args.length-1 + name = ffind(FullName(args[0])) + + if (!IOResult) var fa = faccess(name) + if (!IOResult && (fa.m('x') || /^j[us]sh$/.test(bustr))) + { + if (fa.m('d')) return com+': '+ErrorMsg(errNoAccess) + TTL-- + var c = fget(name),arg,reg + + // substr ââåäåí äëÿ èñïðàâëåíèÿ ãëþêà ñ \n + if (/^(#!\/bin\/)(j[us]sh)/.test(c) || /^(#!)(j[us]sh)/.test(c)) + { + var len = (RegExp.$1+RegExp.$2).length + if (c.charAt(len)==';' || c.charAt(len)=='\n') + bustr = RegExp.$2, c = c.substr(len+1) + } + + if (IOResult) return 'Unknown command.' + if (bustr == "jssh") out = sheval(c); else + out = Exec(c) + + if (typeof(out) == "undefined") out = "" + + TTL++,scriptname = scval,SetVar('argv',arval) + return out + } + } + EL(15),scriptname = scval,SetVar('argv',arval) + for (i = 0; i192) return com+': Command not found. Try to change keyboard layout.'; + out = com+': Command not found.' + return out +}; + +// Ïîèñê ïîñëåäíåé çàêðûâàþùåé ñêîáêè â ñòðîêå íà÷. ñ îòêð. ñêîáêè +function SearchLast(str,n) +{ + var sk=0; + for (i=n;i|;{}'.m(str.c(i))) break; + return i; +} + +function ShellComs(com) +{ + if (/^if\s*\(.*\)\s+then$/.test(com) || /^else\s+if\s*\([^)]*\)\s*then$/.test(com)) return "endif" + if (/^while\s*\(.*\)$/.test(com) || /^foreach/.test(com)) return "end" + return "" +} + +// ïðîâåðÿåò íåò ëè îêðóæàþùèõ ñèìâîë ïðîáåëîâ +function ChSpc(str,i) +{ + return str.c(i-1)==" " && str.c(i+1)==" " +} + +function Exec(str) +{ + label = "" + sline = 0 + var bound = new stack(), longcom = "" + str=str.replace(/\n/g,';').replace(/\r/g,"") + str=str.replace(/([^(\\)])\|\|/g,'$1;if (!errorlevel);'); + str=str.replace(/([^(\\)])\&\&/g,'$1;if (errorlevel);'); + var Out='',oldcom,endcom,c,i,com='',pos,s,multi=0,out,needtoout=0,outname,cont,pipe = 0,keep = 0, dh,brc=0 + str=str+';' + + var gotoTTL=2000 + + + while (/'([^']*)'/.test(str)) + str = str.replace(/'[^']*'/,QuoteMeta3(RegExp.$1)) + + for (i = 0;i': if (brc || bound.length || ChSpc(str,i)) {com+=c;break} + needtoout=1; + if (str.c(i+1)=='>') needtoout=2; + pos = SearchPipeOut(str,i); + outname = StripSlashes(ReplaceW(ReplaceAllVars(str.substring(i+needtoout,pos)))) + + if (outname == "") IOResult = errMissingName + + if (IOResult) + Out+= ErrorMsg(IOResult)+'\n', needtoout = 0 + i=pos-1;break + + case '<': if (brc || bound.length || ChSpc(str,i)) {com+=c;break} + if (str.c(i+1)=='<') + { + if ((pos = str.indexOf(';',i+2))<0) + pos = str.length + dh = str.substring(i+2,pos) + pipe = 1 + if ((s = str.indexOf(';'+dh,pos))<0) + s = str.length + fput ("",cont = str.substring(pos+1,s).replace(/;/g,'\n')+'\n') + i = s+dh.length + break + } + + + pos = SearchPipeIn(str,i); + s = str.substring(i+1,pos); + i = pos-1; + s = StripSlashes(ReplaceW(ReplaceAllVars(s))) + + if (!IOResult) + if (s == "") + IOResult = errMissingName; else + cont = fget(s) + + if (IOResult) Out+='\n'+ErrorMsg(IOResult),EL(IOResult) + else EL(0), fput("", cont+'\n'), pipe = 1 // + "êîíåö ôàéëà" + break + + case '|': if (ChSpc(str,i)) {com+=c;break} else pipe = 1 + case ';': if (Break.length && Break.top()) return Out + if (label!="") + { + if (/^:(.+)$/.test(com) && RegExp.$1 == label) + label = "" + + com = "" + break + } + + if (/^:(.+)$/.test(com)) {com = "";break} + + if (/^if \((!?errorlevel)\)$/.test(com)) + if (sheval(RegExp.$1) || IOResult) + return Out; else {com = "";break} + + if (com == bound.top()) com = bound.pop(); else + if ((endcom = ShellComs(com)) != "") bound.push(endcom) + + if (bound.length) {longcom+= com+c;com="";break} + if (longcom!= "") oldcom = com = longcom,longcom = ""; else + { + oldcom = com + if (!multi) + if ((com = ReplaceAllVars(com),IOResult) || (com = ReplaceW(com),IOResult)) + {Out+='\n'+ErrorMsg(IOResult);break} + } + + if (com == "") out=""; else + if (com.l()=="&") + com = com.replace(/"/g,'\\"'), + out = SetChain(com.substr(0,com.length-1))+" "; else + if (multi) out = Exec(com,"",++sline); else + { + if (/^goto +(.+)$/.test(com)) + { + if (--gotoTTL<0) return "goto: Loop found. Terminated."; + label = RegExp.$1 + if (label!="") i = -1 // -1, ò.ê. â êîíöå èòåðàöèè äîáàâèòñÿ 1 + com = "" + break + } + + if (/^(j[us]sh) +(.+)$/.test(com)) + com = RegExp.$2, oldcom = RegExp.$1 + out = Run(com,oldcom,++sline) + } + + if (pipe) fget(""), pipe = 0 + switch (needtoout) + { + case 0:if (c == "|") fput("",out); else Out+=out;break + case 2:var cont=fget(outname) + if (IOResult) cont = "", IOResult = errNoError + out=cont+out + case 1:var res = fput(FullName(outname),out) + if (IOResult) Out+='\n'+ErrorMsg(IOResult),EL(IOResult) + }; + multi=needtoout=0;com=''; + break; + + default : com+=c; + } + } + return Out +} \ No newline at end of file diff --git a/bzcmd/file.js b/bzcmd/file.js new file mode 100644 index 0000000..5a90d05 --- /dev/null +++ b/bzcmd/file.js @@ -0,0 +1,1390 @@ +// Îáðàáàòûâàåò ðåãóëÿðíûå âûðàæåíèÿ äëÿ SearchMask +function SMCheckReg(str) +{ + for (var c, out = "",bracket = "", i = 0; i1?"wd":"fd")+ch, + Files[last] = "Extended,"+ch+":"; + if (/^\/?(.)/.test(location.pathname)) + Dirs[Dirs.length] = "Extended,"+RegExp.$1.toLowerCase()+":", + Mount[Mount.length] = "local"; + }; + }; + + for (i=0;i Mid.length) Lo++; + while (List[Hi].length < Mid.length) Hi--; + if (Lo <= Hi) T = List[Lo], List[Lo] = List[Hi], List[Hi] = T; + Lo++; + Hi--; + } + while (Hi > Lo); + if (Hi > a) List = QuickSort(List, a, Hi); + if (Lo < b) List = QuickSort(List, Lo, b); + return List; +}; + +// Ïîèñê ïóòè èëè ôàéëà äëÿ êëàâèøè Tab +function SearchPath(str) +{ + var p=-1,n,s,len,name,List + var rest = str + var Div = str.split(/[ ><|;)("']/) + + if (Div.length<2) return str // Êîìàíäà áåç ïàðàìåòðà + for (var p = Div[Div.length-1].length, i = Div.length-2; i>=0; i--) + { + if (Div[i].l()=="\\") p+= Div[i].length+1 + else break + } + + delete Div + com = str.substring(0,str.length-p) + len = (name = StripSlashes(FullName(str = StripSlashes(str.substr(com.length))))).length + List = SearchMask(name+"*/") + if (name.l()!="/") + List = SearchMask(name+"*").concat(List).concat(SearchMask(FullPath(str)+"*")); + if (!List.length) + { + List = flist(ExtractPath(name)) + if (!IOResult) Type('\n'+List.join(', ')+'\n'+P()) + return com+QuoteMeta2(str) + }; + List = QuickSort(List,0,List.length-1) + + s = List[0]; + for (i=0; i') && (Files[i].substr(2)==s)) return i; + return IOResult = errNotFound; +}; + +function ErrorMsg(num) +{ + EL(num) + switch (num) + { + case errNoError: return '' + case errNotFound: return 'No such file or directory.' + case errNoAccess: return 'Access denied.' + case errFatalErr: return 'Fatal file system error.' + case errCookNoSpace: return 'Not enougth space or browser declined request.' + case errCantWriteDir: return 'Cannot write to directory.' + case errVarNotFound: return 'Undefined variable.' + case errInvVarName: return 'Invalid variable name.' + case errOutOfBounds: return 'Subscript out of range.' + case errInvIndex: return 'Newline in variable index.' + case errNoHome: return 'No home directory.' + case errFewArguments: return 'Too few arguments.' + case errNoNumber: return 'Badly format number.' + case errMissingName: return 'Missing name.' + case errEvalError: return 'Expression error.' + case errBinFile: return 'Cannot read this binary file.' + case errFileLocked: return 'The file is locked by Windows.' + case errInvSyntax: return 'Invalid syntax.' + case errCantChmod: return 'Cannot change mode this file.' + }; + return 'Unknown error.'; +}; + +function LocalExists(name) +{ + IOResult = errNoError; + + for (var i in Names) if (name == Names[i]) return i; + return IOResult = errNotFound; +}; + +function MountExists(name) +{ + var len,i,p=0,cur; + IOResult = errNoError; + name = FullPath(name); + for (i in Mount) + { + if (name.charAt(1) == Mount[i].charAt(1) && + name.length >= (len = Mount[i].length) && + name.substr(0,len) == Mount[i] && p2 && Res[2].m('r')) return IOResult = errNoAccess; + name = name.substr(Mount[res].length) + var put = new Function("name,content,r","FileWrite"+Res[0]+"(name,content,r)") + return put (name,content,Res[1]) + }; + + // Ôàéë íå ñîçäàí â ëîêàëå, ïóòü íå ñìîíòèðîâàí, ñîçäàåì ôàéë + + var len = Names.length; + if (len != Access.length || len != Names.length) + return IOResult = errFatalErr; + + Names[len] = name; + Access[len] = umask; + Files[len] = content; + + return IOResult = errNoError; +}; + + +function fget(name) +{ + IOResult=errNoError + if (name == "" || name == "-") + { + var pipe = pipefile + return pipefile = "",IOResult=errNoError,pipe + } + switch (name = FullName(name)) + { + case "/dev/bmem": if (IE) return GetBuffer() + case "/dev/null": return "" + case "/dev/random": return Math.floor(Math.random()*65535) + case "/JUNIX" : return "Written by Stepanischev Evgeny. 1999-2001 y." + case "/dev/stat": return window.status + } + var acc = faccess(name) + + if (acc.nm('r')) return IOResult = errNoAccess + + if ((name.l()=="/" || acc.c(0)=="d") && (!IOResult || LocalDirEi(name),!IOResult)) + return IOResult=errNoError,flist(name).join("...") + + var res = LocalExists(name) + + if (!IOResult) + { + IOResult = errNoError; + if (name == '/dev/nul') return "" + + var cont = Files[res], acc = Access[res] + + if (acc.m('l')) return fget(cont.substr(2)) + if (acc.m('p')) Files[res] = "" + return cont + }; + + var res = MountExists(name); + if (!IOResult) + { + var Res = Device[res].split(',') + name = name.substr(Mount[res].length) + return sheval("FileRead"+Res[0]+"('"+name+"','"+Res[1]+"')") + }; + return IOResult = errNotFound; +}; + +function flist(path) +{ + path = FullPath(path) + + if (faccess(path).nm('r')) return new Array() + + var List = new Array(),i,j = 0, name; + var Xnam = Names.concat(Mount); + + for (i in Xnam) + if (Xnam[i].length > (len = path.length) && + path == Xnam[i].substr(0,len)) + { + name = Xnam[i].substr(len); + if (/^([^\/]+\/)/.test(name)) + { + for (j = 0;j= (len = tname.length) + && Names[i].substr(0,len) == tname) return IOResult = errNoError + + var res = MountExists(name) + if (IOResult) return IOResult = errNotFound + + var Res = Device[res].split(',') + name = name.substr(Mount[res].length) + return sheval("FileExists"+Res[0]+"('"+name+"','"+Res[1]+"')") +} + +function fdelete(name) +{ + name = FullName(name) + var acc = faccess(name) + + if (IOResult) return IOResult; else + if (fexists(name),!IOResult && acc.nm('w') || "cb".m(acc.c(0)) || faccess(ExtractPath(name)).nm('w') && !IOResult) + return IOResult = errNoAccess + + var res + while (res = FindLink(name),!IOResult) + { + Names=DeleteItem(Names,res); + Files=DeleteItem(Files,res); + Access=DeleteItem(Access,res); + }; + + var res = LocalExists(name); + if (!IOResult) + { + Names=DeleteItem(Names,res); + Files=DeleteItem(Files,res); + Access=DeleteItem(Access,res); + return IOResult = errNoError; + }; + + var res = MountExists(name); + if (!IOResult) + { + var Res = Device[res].split(','); + if (Res.length>2 && Res[2].m('r')) return IOResult = errNoAccess; + name = name.substr(Mount[res].length); + return sheval("FileDelete"+Res[0]+"('"+name+"','"+Res[1]+"')"); + }; + + return IOResult = errNotFound; +}; + +function fsize(name) +{ + name = FullName(name); + + var res = LocalExists(name); + if (!IOResult) return Files[res].length; + + IOResult = errNoError; + var Xnam = Names.concat(Mount); + + if (/\/$/.test(name)) + for (var i in Xnam) if (Xnam[i].length >= (len = name.length) + && Xnam[i].substr(0,len) == name) return len; + + var res = MountExists(name); + if (!IOResult) + { + var Res = Device[res].split(','); + name = name.substr(Mount[res].length); + return sheval("FileSize"+Res[0]+"('"+name+"','"+Res[1]+"')"); + }; + + // Ïóòü âûøå ñìîíòèðîâàí + var len = name.length; + for (i in Mount) if (Mount[i].substr(0,len) == name) return len; + + return IOResult = errNotFound; +}; + +function faccess(name) +{ + name = FullName(name); + var res = LocalExists(name) + if (!IOResult) + { + if (name.l()=="/") return "d"+Access[res].substr(1); else + if (Access[res].m('l')) return 'l'+faccess(Files[res].substr(2)).substr(1); else + return Access[res] + } + + IOResult = errNoError + + var i, t_name = (name.l()=="/"?name:name+"/"), len = t_name.length + + for (i in Names) if (Names[i].length >= len + && Names[i].substr(0,len) == t_name) return "drwx"; + + var res = MountExists(name); + + if (!IOResult) + { + var Res = Device[res].split(','); + name = name.substr(Mount[res].length); + + Res[1] = Res[1].replace(/\\/g,"/") + name = name.replace(/\\$/,"") + return sheval("FileAccess"+Res[0]+"('"+name+"','"+Res[1]+"')") + } + + // Ïóòü âûøå ñìîíòèðîâàí + var len = name.length; + for (i in Mount) if (Mount[i].substr(0,len) == name) return "drwx" + + return IOResult = errNotFound,"drwx" +}; + +function fchmod(name, r) +{ + + IOResult = errNoError + if (name == "" || name == "-" || ExtractPath(FullName(name)) == '/dev') + return errCantChmod + + var acc = faccess(name) + if (IOResult) return IOResult + + var res = LocalExists(name) + if (!IOResult) + { + IOResult = errNoError + var cont = Files[res], acc = Access[res] + + if (acc.m('l')) return fchmod(cont.substr(2)) + if (acc.c(0)!='-') return errCantChmod + + Access[res] = acc.c(0)+(r.m('r')?'r':'-')+(r.m('w')?'w':'-')+(r.m('x')?'x':'-') + return IOResult = errNoError + } + + var res = MountExists(name) + if (!IOResult) + { + var Res = Device[res].split(',') + name = name.substr(Mount[res].length) + return sheval("FileChmod"+Res[0]+"('"+name+"','"+r+"','"+Res[1]+"')") + } + return IOResult = errCantChmod +}; + + +function ffind(str) +{ + if (fexists(str),!IOResult) return str + var Paths=GetVar('path[*]'),name + if (IOResult) return IOResult + Paths = Paths.split(' ') + + str = ExtractName(str) + for (i in Paths) + { + fexists(name = FullPath(Paths[i])+str) + if (!IOResult) return name + } + return IOResult = errNotFound,"" +}; + +//---------------------------------------------------// + +function FileWriteObject(name) +{ + return IOResult = errNoAccess; +}; + +function FileWriteCookie(name,content) //,f +{ + var len = content.length, r + + if (arguments[2]=='direct') r = '', len-=2; else + { + r = FileAccessCookie (name) + if (IOResult) r = '#;'; else + r = '#'+String.fromCharCode(48+(r.m('r')?1:0)+(r.m('w')?2:0)+ + (r.m('x')?8:0)) + } + + content = JZip(r+content,1,3) + var d=new Date(2999,2,2),nd + if (NC) nd='Wednesday, 30-Dec-37 23:59:59 GMT'; else nd=d.toGMTString() + document.cookie= name + '=' + content + ';expires=' + nd + + if (FileSizeCookie(name) == len) return IOResult = errNoError + return IOResult = errCookNoSpace +}; + +function FileWriteExtended() +{ + var name = arguments[0].replace(/\//g,"\\"), + content = arguments[1], + disknum = arguments[2].replace(/\\\\/g,"\\") + + if (/(.+\/)([^\/]+)$/.test(name)) + var path = '\\'+RegExp.$1,name = RegExp.$2; else var path = "\\"; + path = disknum+path; + + var fullname = path + name, idx = fullname.lastIndexOf("\\"); + if (idx >=0) fullname = fullname.substring(0,idx); else fullname = "\\"; + + if (DelConf && !confirm("File "+fullname+"\\"+name+" will changed!")) + return IOResult = errNoAccess; + + FileMkdirExtended(fullname); + if (IOResult) return IOResult; + + IOResult = errNoError + if (name.l()=="\\") return IOResult + + if (NC || OP) + { + netscape.security.PrivilegeManager.enablePrivilege('UniversalFileAccess'); + var fi=new java.io.File(path,name); + if (fi.exists() && !fi.canWrite()) return IOResult = errNoAccess + + var f=new java.io.FileOutputStream(fi) + + for (var i=0;i4) + try + {@end @*/ + var fso=new ActiveXObject("Scripting.FileSystemObject"); + var file=fso.CreateTextFile(path+name,1,false); + /*@if (@_jscript_version>4) + } catch (e) + { + return IOResult = errFileLocked + };@end @*/ + /*@cc_off @*/ + + file.Write(content) + file.Close() + return IOResult = errNoError + }; + +}; +function FileListObject() +{ + var path = arguments[0] + var obj = sheval(arguments[1]) + var List = new Array(), j = 0; + if (path == "") + for (i in obj) + List[j++] = ""+i + return List +}; + +function FileListCookie(path) +{ + var Xnam = document.cookie.split(";"), List = new Array(), name, i + for (i in Xnam) + if (/\s*([^=]+)/.test(Xnam[i])) + { + if (RegExp.$1.length >= (len = path.length) && + path == RegExp.$1.substr(0,len)) + { + name = RegExp.$1.substr(len); + if (/^([^\/]+\/)/.test(name)) + { + for (j = 0;j4) + try + {@end @*/ + var fso=new ActiveXObject("Scripting.FileSystemObject"); + /*@if (@_jscript_version>4) + } catch (e) + { + return IOResult = errNoAccess; + };@end + if (/.:\\?$/.test(path) && !fso.DriveExists(path) || + !fso.FolderExists(path)) return IOResult = errNotFound; + + var folder = fso.GetFolder(path); + var fc = new Enumerator(folder.SubFolders); + for (; !fc.atEnd(); fc.moveNext()) + if (/([^\\]+)$/.test(fc.item())) + List[j++] = RegExp.$1.toLowerCase()+"/"; + + fc = new Enumerator(folder.files); + + for (; !fc.atEnd(); fc.moveNext()) + if (/([^\\]+)$/.test(fc.item())) + List[j++] = RegExp.$1.toLowerCase(); + return List; + @cc_off + @*/ + } else + return new Array(); +}; + +function FileReadCookie(name) //, f +{ + IOResult = errNoError + var prefix = name+'=' + var start = document.cookie.indexOf(prefix) + if (start<0) + if (FileExistsCookie(name),!IOResult) return '' + else return IOResult = errNotFound + var end = document.cookie.indexOf(';',start+prefix.length) + if (end<0) + end = document.cookie.length + var out = document.cookie.substring(start+prefix.length,end) + + out = UnJZip(out,1) + if (arguments[1]=='direct') return out + + if (out.c(0)=='#') return out.substr(2) + return out +}; + +function FileReadObject(name,obj) +{ + IOResult = 0; + var path = arguments[0]; + var obj = sheval(arguments[1]); + for (var i in obj) if (name == i) + return (((type=typeof(obj[i]))=="string" || type=="number" || type=="boolean")?(""+obj[i]):("["+type+"]")) + return IOResult = errNotFound; +}; + +function FileReadExtended() +{ + var name = arguments[0] + var disknum = arguments[1] + + name = name.replace(/\//g,"\\") + + if (/(.+\/)([^\/]+)$/.test(name)) + var path = '\\'+RegExp.$1,name = RegExp.$2; else var path = "\\" + path = disknum+path + + IOResult = errNoError + if (NC || OP) + { + netscape.security.PrivilegeManager.enablePrivilege('UniversalFileAccess') + var fi=new java.io.File(path,name) + if (!fi.canRead()) return IOResult = errNoAccess + + var f=new java.io.FileInputStream(fi); + + for (var fc='',i=f.available();i;i--) + fc+=String.fromCharCode(f.read()) + f.close() + + var jsfc = new String(fc) + delete fc, f, fi + + return jsfc + + } else + if (IE) + { + /*@cc_on @*/ + /*@if (@_jscript_version>4) + try + {@end @*/ + var fso=new ActiveXObject("Scripting.FileSystemObject"); + var file=fso.OpenTextFile(path+name,1,false); + /*@if (@_jscript_version>4) + } catch (e) + { + if (e.description=="Permission denied") + return IOResult = errFileLocked + return IOResult = errNotFound + };@end @*/ + /*@cc_off @*/ + + var out = file.Read(fso.GetFile(path+name).size) + file.Close() + return out + }; + return IOResult = errFatalErr +}; + +// Exists section + +function FileExistsObject(name,obj) +{ + if (name == "") return IOResult = errNoError + + obj = sheval(obj) + for (var i in obj) if (name == ''+i) return IOResult = errNoError + return IOResult = errNotFound +}; + +function FileExistsCookie(name) +{ + IOResult = errNoError + if (name == "") return IOResult + name = QuoteMeta(name) + var reg1 = new RegExp("; *"+name+"[;=]"), reg2 = new RegExp("^"+name+"[;=]") + var reg3 = new RegExp("; *"+name+"$"), reg4 = new RegExp("^"+name+"$") + + if (reg1.test(document.cookie) || reg2.test(document.cookie) || + reg3.test(document.cookie) || reg4.test(document.cookie)) return IOResult + return IOResult = errNotFound +}; + +function FileExistsExtended() +{ + var name = arguments[0]; + var disknum = arguments[1]; + name = name.replace(/\//g,"\\"); + + if (/(.+\/)([^\/]+)$/.test(name)) + var path = '\\'+RegExp.$1,name = RegExp.$2; else var path = "\\"; + path = disknum+path + + IOResult = errNoError; + if (NC || OP) + { + netscape.security.PrivilegeManager.enablePrivilege('UniversalFileAccess');; + var fi=new java.io.File(path,name) + if (fi.exists()) return IOResult = errNoError + + delete fi + return IOResult = errNotFound; + + } else + if (IE) + { + /*@cc_on @*/ + /*@if (@_jscript_version>4) + try + {@end @*/ + var fso=new ActiveXObject("Scripting.FileSystemObject"); + /*@if (@_jscript_version>4) + } catch (e) + { + return IOResult = errNotFound; + };@end @*/ + /*@cc_off @*/ + if (fso.FileExists(path+name) || fso.FolderExists(path+name)) + return IOResult = errNoError; + return IOResult = errNotFound; + }; + return IOResult = errFatalErr; +}; + +function FileDeleteObject(name,obj) +{ + FileExistsObject(name,obj) + if (!IOResult) return IOResult = errNoAccess + return IOResult +} + +function FileDeleteCookie(name) +{ + FileExistsCookie(name) + if (IOResult) return IOResult + if (IE) + document.cookie=name+'=;expires=Wednesday, 01-Jan-1970 00:00:00 GMT'; else + document.cookie=name+'=#%3F;expires=Wednesday, 01-Jan-70 00:00:00 GMT' + return IOResult = errNoError +} + +function FileDeleteExtended() +{ + var name = arguments[0]; + var disknum = arguments[1]; + + name = name.replace(/\//g,"\\"); + + if (/(.+\/)([^\/]+)$/.test(name)) + var path = '\\'+RegExp.$1,name = RegExp.$2; else var path = "\\"; + path = disknum+path; + + if (DelConf && !confirm("File "+path+name+" will deleted!")) + return IOResult = errNoAccess + + FileExistsExtended(name,disknum) + if (IOResult) return IOResult + IOResult = errNoError + if (NC || OP) + { + return IOResult = errNoError + } else + if (IE) + { + /*@cc_on @*/ + /*@if (@_jscript_version>4) + try + {@end @*/ + var fso=new ActiveXObject("Scripting.FileSystemObject"); + /*@if (@_jscript_version>4) + } catch (e) + { + return IOResult = errNotFound + };@end @*/ + /*@cc_off @*/ + + if (/(.*)\\$/.test(name)) fso.DeleteFolder(path+RegExp.$1,true); else + fso.DeleteFile(path+name,true) + + return IOResult = errNoError + }; + return IOResult = errFatalErr; +}; + +function FileSizeObject() +{ + var name = arguments[0]; + var obj = arguments[1]; + if (name=="") return 0; + return FileReadObject(name,obj).length; +}; + +function FileSizeCookie(name) +{ + if (name=="") return 0; + var content = FileReadCookie(name) + if (!IOResult) return content.length; else return 0; +}; + +function FileSizeExtended(name,disknum) +{ + var name = arguments[0] + var disknum = arguments[1]; + if (name=="") return 0 + FileExistsExtended(name,disknum); + if (IOResult) return 0 + name = name.replace(/\//g,"\\"); + if (/(.+\/)([^\/]+)$/.test(name)) + var path = '\\'+RegExp.$1,name = RegExp.$2; else var path = "\\" + path = disknum+path; + + IOResult = errNoError + if (NC || OP) + { + netscape.security.PrivilegeManager.enablePrivilege('UniversalFileAccess') + var fi=new java.io.File(path,name) + var jsfl = new String(fi.length()) + delete fi + + return jsfl + + } else + if (IE) + { + /*@cc_on @*/ + /*@if (@_jscript_version>4) + try + {@end @*/ + var fso=new ActiveXObject("Scripting.FileSystemObject") + /*@if (@_jscript_version>4) + } catch (e) + { + return IOResult = errNotFound; + };@end @*/ + /*@cc_off @*/ + + if (/(.*)\\$/.test(name)) return (path+name).length; else + return fso.GetFile(path+name).size; + }; + return IOResult = errFatalErr; +}; + +function FileAccessObject(name) +{ + if (name=="") return "dr-x"; + return "-r--"; +}; + +function FileAccessCookie(name) +{ + var content, ch + if (name=="") return "drwx" + + if (/\/$/.test(name)) return "drwx"; else + { + content = FileReadCookie(name, 'direct') + if (IOResult || content.c(0)!="#") return "-rw-" + ch = content.charCodeAt(1) + + return (ch&4?'M':'-')+(ch&1?'r':'-')+(ch&2?'w':'-')+(ch&8?'x':'-') + } +}; + +function FileAccessExtended(name,disk) +{ + IOResult = errNoError + + if (name == "") return "drwx" + var fullname = (disk+"/"+name).replace(/\//g,"\\") + if (fullname.l() == "\\") fullname = fullname.slice(0,-1) + + if (IE) + { + /*@cc_on + @if (@_jscript_version>4) + try + {@end @*/ + var fso = new ActiveXObject("Scripting.FileSystemObject") + /*@if (@_jscript_version>4) + } catch (e) + { + return IOResult = errNoAccess + }@end + @cc_off @*/ + + if (fso.FolderExists(fullname)) var attr = fso.GetFolder(fullname); else + if (fso.FileExists(fullname)) var attr = fso.GetFile(fullname); else + return IOResult = errNotFound + + attr = attr.attributes + + return (attr & 16 ? 'd':'-')+(attr & 1 ? '-':'r')+(attr & 6 ? '-':'w')+ + (attr & 32 ? '-' : 'x') + } else + + if (NC || OP) + { + netscape.security.PrivilegeManager.enablePrivilege('UniversalFileAccess') + var fi=new java.io.File(fullname) + + if (fi.isDirectory()) return "drwx" + if (fi.isFile()) return "-rwx" + IOResult = errNotFound + + delete fi + return "-rwx" // NC 5.x & NC 6.x java bug + } else + return "" +}; + +function FileMkdirExtended(path) +{ + if (DelConf && !confirm("Folder "+path+" will created!")) + return IOResult = errNoAccess; + + if (IE) + { + /*@cc_on + @if (@_jscript_version>4) + try + {@end @*/ + var fso=new ActiveXObject("Scripting.FileSystemObject"); + /*@if (@_jscript_version>4) + } catch (e) + { + return IOResult = errNoAccess; + };@end + @cc_off @*/ + + Path = path.split("\\"), + path = Path[0]; + + for (i = 1;i4) + try + {@end @*/ + var fso = new ActiveXObject("Scripting.FileSystemObject"); + var disk = fso.GetDrive(disk.substr(0,1)) + /*@if (@_jscript_version>4) + } catch (e) + { + IOResult = errNotFound + return new Array("-","-") + };@end @*/ + /*@cc_off @*/ + + var size = disk.TotalSize + return new Array(size - disk.FreeSpace, size) + }; + return IOResult = errFatalErr + +} + +function Jenc (s) +{ + s = parseInt(s,10) + if (s>51) s = (s+"").lz(2); else + { + if ((s+= 65)>90) s+= 6 + s = CODES.charAt(s) + } + return s +} + +function JZip (str, f, b) +{ + str = str.replace(/\*/g,'*+') + var Dict = new Array () + var Words = str.split (/[^\wà-ÿÀ-ß0-9:]+/),l1,l2,i,j,r + var Word = new Array() + var Freq = new Array () + + Words = Words.concat (new Array('tion','ternal')).sort(new Function("a,b", "return a.length-b.length")) + + if (f) for (i = 0; ib) break + + Words = Words.slice (r).sort(new Function ("a,b", "return a==b? 0 : (a>b?1:-1)")) + + for (i = 0; i1) + { + r = new RegExp (Word[i],"g") + if (r.test(str)) + { + str = str.replace (r, '*'+Jenc(j)) + Dict[j++] = Word[i] + } + } + + delete Freq + + return Dict.join (",")+"!"+str +} + +function UnJZip (str, f) +{ + if (f) + str = unescape (str).replace(/\*_/g,' ').replace(/\*\-/g,"\n").replace(/\*\./g,' ') + + var t = str.indexOf("!") + var Dict = str.substr (0, t).split (",") + var txt = str.substr (t+1), i, r + + for (i in Dict) + { + if ((r = Jenc (i))!=i) r = "("+(""+i).lz(2)+"|"+r+")"; else + r = (""+i).lz(2) + r = new RegExp ("\\*"+r,"g") + txt = txt.replace (r, Dict[i]) + } + return txt.replace(/\*\+/g,'*') +} + +function FileChmodCookie(name,r) +{ + if (name=='') return errCantChmod + + r = String.fromCharCode(48+(r.m('r')?1:0)+(r.m('w')?2:0)+(r.m('x')?8:0)) + var content = FileReadCookie(name) + if (IOResult) return IOResult + + FileWriteCookie (name, '#'+r+content, 'direct') + if (IOResult) return IOResult + return errNoError +} + +function FileChmodObject() +{ + return errCantChmod +} + +function FileChmodExtended(name, r, disk) +{ + IOResult = errNoError + + if (name == "") return "drwx" + var fullname = (disk+"/"+name).replace(/\//g,"\\") + if (fullname.l() == "\\") fullname = fullname.slice(0,-1) + + if (IE) + { + /*@cc_on + @if (@_jscript_version>4) + try + {@end @*/ + var fso = new ActiveXObject("Scripting.FileSystemObject") + /*@if (@_jscript_version>4) + } catch (e) + { + return IOResult = errNoAccess + }@end + @cc_off @*/ + + if (fso.FolderExists(fullname)) var file = fso.GetFolder(fullname); else + if (fso.FileExists(fullname)) var file = fso.GetFile(fullname); else + return IOResult = errNotFound + + file.attributes = (r.m('r')?0:1)+(r.m('w')?0:6)+(r.m('x')?0:32) + return errNoError + } + + return errCantChmod +} \ No newline at end of file diff --git a/bzcmd/index.html b/bzcmd/index.html new file mode 100644 index 0000000..33ff9fb --- /dev/null +++ b/bzcmd/index.html @@ -0,0 +1,28 @@ + +Command Line + + + + + + + + + + +
+ +
+
+BrowzOS Command Line
 
# help
help: Command not found.
# ?
# ? @ alias bc break brow bye cat cd chmod clear compress continue cp cut d date df 
echo eval exit fg foreach fw fy goto grep icq id if jzip kill ln load ls mail man mk
dir mkfifo more mount mv ping ps pwd read rehash repeat rm rmdir save scan sed set s
hift source su tail tee touch unalias uname unhash unmount unset vi wc where while w
hoami wish
# _
+
+ \ No newline at end of file diff --git a/bzcmd/mail.js b/bzcmd/mail.js new file mode 100644 index 0000000..00b1f19 --- /dev/null +++ b/bzcmd/mail.js @@ -0,0 +1,67 @@ +// Çàãëóøêà +function SetLogin(user, pass) +{ + SetVar("user",user) +} + +// Ïåðåéòè ê... (â îáúåêòå mail) +function Navi() +{ + dtime = ping?(new Date()-0):0 // äëÿ êîìàíäû ping + + with(document) + if (NaviStack.length) + { + for (i = 10; i && images["navi"+curnavi].busy; i--) + if (++curnavi>9) curnavi = 0 + + if (!images["navi"+curnavi].busy) + { + images["navi"+curnavi].busy = true + images["navi"+curnavi].onerror = new Function("x","NaviFree("+curnavi+","+dtime+")") + images["navi"+curnavi].src = NaviStack.pop() + } + } +} + +// Ïîñòàâèòü â î÷åðåäü +function SetNavi(url) +{ + NaviStack.unshift(url+(ping?'/%00?'+Math.random():'')) + Navi() +} + +function NaviFree(num, dtime) +{ + if (document.images && document.images["navi"+num]) + { + document.images["navi"+num].busy = false + if (/^http:\/\/([^\/]+)/.test(host = document.images["navi"+num].src)) host = RegExp.$1 + if (dtime) + { + dtime = new Date()-dtime + if (dtime>pingto) Type('Request timed out.\n'); else + Type('64 bytes from '+host+': time='+dtime+' msec\n') + } + + if (--ping<0) + { + ping = dialog = 0 + Type(P()) + } else SetNavi('http://'+host) + } +} + +// Ïðîâåðêà íà ãëþ÷íûå áðàóçåðû + +function NaviCheckBrowser() // 1 <= bad +{ + if (NC && Version>=5) return 1 + if (MZ) + { + var Aver = Version.split (".") + if (Aver[1]<9 || Aver[1]==9 && Aver[2]<6) + return 1 + } + return 0 +} \ No newline at end of file diff --git a/bzcmd/unix.js b/bzcmd/unix.js new file mode 100644 index 0000000..7bb6fe7 --- /dev/null +++ b/bzcmd/unix.js @@ -0,0 +1,1014 @@ +OP=self.opera ? 1 : 0 +NC=!document.all +IE=!NC && !OP +MZ=!OP && navigator.product=='Gecko' && navigator.vendor!='Netscape6' // Mozilla + +errNoError = 0 // Must be zero +errNotFound = -1 +errNoAccess = -2 +errFatalErr = -3 +errCookNoSpace = -4 +errInvName = -5 +errCantWriteDir = -6 +errMissingName = -7 +errBinFile = -8 +errFileLocked = -9 + +errVarNotFound = -16 +errOutOfBounds = -17 +errInvIndex = -18 +errInvVarName = -19 +errNoHome = -20 +errFewArguments = -21 +errNoNumber = -22 +errEvalError = -23 +errInvSyntax = -24 +errCantChmod = -25 + +var Mount = new Array(); +var Device = new Array(); +var ROMount = 0; // Ôëàã ìîíòèðîâàíèÿ òîëüêî R/O +var DelConf = 0; // Ïîäòâåðæäåíèå èçìåíåíèé íà Extended +var ScrollDown = 1 // Cêðîëëèíã ýêðàíà âíèç ïðè íàáîðå òåêñòà. +var KeepHistory = 1 // Ñîõðàíÿòü èñòîðèþ êîìàíä +var KeyDebug = 0 // Ïðîñìàòðèâàòü êîäû êëàâèø + +var IOResult = errNoError; +var umask = '-rwx'; // Ìàñêà ïî-óìîë÷àíèþ + +var Files=new Array(); +var Names=new Array('/bin/bc','/bin/vi','/bin/man','/bin/manual.jz','/bin/pwd','/bin/brow','/.rcjush','/tmp/','/.history','/bin/whoami', +'/bin/exit','/bin/navidaemon','/bin/pidaemon','/bin/su','/bin/fw', +'/bin/touch','/bin/read', +'/bin/history','/dev/null','/bin/fy','/dev/bmem', +'/dev/stat','/bin/garbdaemon','/dev/random', '/bin/ping') + +var Access=new Array('-r-x','-r-x','-r-x','-r--','-r-x','-r-x','-rwx','-rwx','-rw-','-r-x', +'-r-x','-r-x','-r-x','-r-x','-r-x', +'-r-x','-r-x', +'-r-x','crw-','-r-x','crw-', +'crw-','-r-x','cr--', '-r-x') +var VarNames=new Array('path','home','pwd','prompt','prompt2','user','version','argv', +'cdpath','history') +var VarValues=new Array('(/bin /etc)','/','/','# ','> ','nobody',Ver,'()', +'/etc/',0) + + +var Chain = new stack() +var PIDs = new stack() +var Stack = new stack() +var NaviStack = new stack() + +Files[0]='if ($#) then\n#JS: $*\nelse\ndialog [] [if (LEN) {{bc PARM;echo -------}>stdout;bc}]\nendif'; +Files[1]='#JS: Vi($%*)'; +Files[2]='if ($#) then\nif ($%1=="?") then\necho \\? - shows all commands\nelse\njzip -d /bin/manual.jz|grep ^$1[^A-Za-z] -\nendif\nelse\necho Usage: man \\\nendif'; +Files[3]= +'command,keyword,alias,exit,message,standart,output,file,pattern,search,show,lines,only,name,disable,current,directory,content,expression,Example:,change,access,list,char,from,execute,link,moves,system,mode,delete,write,save,line,right,device,display,sets,mount,read,default,format,compress,user,number,variable,while,label,hash,jzip,tion,ternal,. Danger., ( for compatibility ).,,engine., via , string, start, or , and !man <*A> - locates *As by *B lookup.\n'+ +'? \n'+ +'@ - *C of set *A.\n'+ +'bye - logout*60*D.\n'+ +'echo <*E> - *fs *Xaster*57s to *F *G.\n'+ +'cat *54 [*54..] - concatenates*59*ks *Hs.\n'+ +'grep [-cilvhd] <*I> *54 [*54..] *J <*I> in *H(s)*60*K found *L. -c - *K quantity found *L. -i - not case sensitivity. -l - *K *H*Ns *M. -v - *K *L not contained *I. -h - *K *N of *H*60*L. -s - *O *K errors.\n'+ +'pwd - *K *P *Q.\n'+ +'ls [-l1a] [<*Q] - *K *Q *R. -l - *K full *R. -1 - *K *R in 1 column. -a - *K all *H(s) (*60.*+ too. )\n'+ +'bc <*S> - calculate simple *S. Func*y: +,-,/,*+,sin,cos,tan,log,exp,pow,sqrt,asin,acos,atan, Int - integral by Simphson method.*T bc sin(1)+cos(1)*+sqrt(3)+Int("sin(x)",0,1)*52\n'+ +'clear - clears the terminal screen.\n'+ +'chmod <0-7|<+|->> - *U *V *is. 0-7 - bits-coded *V *is. <+|-> - enable/*O. rwx - Read/Write/eXecute.\n'+ +'cd <*Q> - *U *Q.\n'+ +'cp <*H1> <*H2> - copy one to other.cp <*H1> <*H2>.. - copy *Hs to *Q.\n'+ +'ln - *as *Hs.\n'+ +'cut <*W> [-<*X>] <*H(s)> - cuts *Y <*H(s)> row(s) with *ss <*W>.-<*X> - symbol for separate result *W.*T cut 1-3,5 /bin/manual.\n'+ +'brow - opens web-.\n'+ +'mkdir - make *Q*53\n'+ +'if (<*S>) <*A1>if (<*S>) then<*A1>;[<*A2>;...][else<*A3>;[<*A4..>;]]endif*Z <*A1>,.. if <*S> is true*60<*A3>,.. if it is false*52\n'+ +'rm *54 [*54..] - erase *H(s)*60*a(s)\n'+ +'mv - *b to .mv <*H1> <*H2>... - *b *Hs into directiory.\n'+ +'wc [-clw] [Files...] - count quantity of *L (-l), words (-w)*60*Xasters (-c) in *H(s).\n'+ +'scan - scan*60correct *H *c error (jsfs *M).\n'+ +'vi <*H*N> - VIsual editor. Editor for small *Hs.keys: a - append ( switch to text *d ),Backspace - *e *X,Esc - switch to *A *d,w - *f to *H ( *g ),q - quit.i - insert new *h.x - set "x" *V *i to *H.\n'+ +'load *54 - load JSFS *H *c *Y local *med *j.\n'+ +'*g *54 - *g JSFS *H *c to local *med *j.\n'+ +'rmdir - remove *Q*53\n'+ +'tee *54 - get input stream, *g it to *54*60*G it.\n'+ +'# ; - comment.\n'+ +'set [-rwqxsfBb] - *k all *ts*59*l some op*ys. -r - *m *n *d *M. -w - *m *n/*f *d (*o). -q - query for *U data on extended *js. -x - not query... (*o) -f - don\'t scroll down when *G. -s - scroll... (*o). -b - enable b*a cursor. -B - *O... (*o).\n'+ +'*D - logout*60*D.\n'+ +'date [+<*p>]- *k *P date.\n'+ +'eval - replace all *ts*60*Z *A.\n'+ +'fg - runs jobs in foreground.\n'+ +'ps - *Ks *P status of processes.\n'+ +'more [<*H*N>] - *Ks *H *R one screen at a time.keys: - continue.:q - *D.other - *Z shell *A.\n'+ +'sed [-n] [-e + + + + + + + \ No newline at end of file diff --git a/clock/cal.html b/clock/cal.html new file mode 100644 index 0000000..2c73b15 --- /dev/null +++ b/clock/cal.html @@ -0,0 +1,34 @@ + +Calendar Component + + + + + + + +
+ + \ No newline at end of file diff --git a/clock/clock.html b/clock/clock.html new file mode 100644 index 0000000..2ab0da3 --- /dev/null +++ b/clock/clock.html @@ -0,0 +1,10 @@ + +Time Component + + + + +
+ +
+ \ No newline at end of file diff --git a/clock/clock.js b/clock/clock.js new file mode 100644 index 0000000..70df82b --- /dev/null +++ b/clock/clock.js @@ -0,0 +1,86 @@ + \ No newline at end of file diff --git a/clock/clock_dial.js b/clock/clock_dial.js new file mode 100644 index 0000000..2ed12b9 --- /dev/null +++ b/clock/clock_dial.js @@ -0,0 +1,86 @@ + \ No newline at end of file diff --git a/clock/dclock.js b/clock/dclock.js new file mode 100644 index 0000000..0fcc4cb --- /dev/null +++ b/clock/dclock.js @@ -0,0 +1,22 @@ +tday =new Array("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"); +tmonth=new Array("January","February","March","April","May","June","July","August","September","October","November","December"); +function GetClock(){ +d = new Date(); +nday = d.getDay(); +nmonth = d.getMonth(); +ndate = d.getDate(); +nyear = d.getYear(); +nhour = d.getHours(); +nmin = d.getMinutes(); +nsec = d.getSeconds(); +if(nyear<1000) nyear=nyear+1900; +if(nhour == 0) {ap = " AM";nhour = 12;} +else if(nhour <= 11) {ap = " AM";} +else if(nhour == 12) {ap = " PM";} +else if(nhour >= 13) {ap = " PM";nhour -= 12;} +if(nmin <= 9) {nmin = "0" +nmin;} +if(nsec <= 9) {nsec = "0" +nsec;} +document.getElementById('clockbox').innerHTML=""+tday[nday]+", "+tmonth[nmonth]+" "+ndate+", "+nyear+"
"+nhour+":"+nmin+":"+nsec+ap+""; +setTimeout("GetClock()", 1000); +} +window.onload=GetClock; \ No newline at end of file diff --git a/clock/face.gif b/clock/face.gif new file mode 100644 index 0000000..01a75bb Binary files /dev/null and b/clock/face.gif differ diff --git a/clock/face.png b/clock/face.png new file mode 100644 index 0000000..0baff84 Binary files /dev/null and b/clock/face.png differ diff --git a/clock/img/aqua_dayDown.gif b/clock/img/aqua_dayDown.gif new file mode 100644 index 0000000..5bffaf3 Binary files /dev/null and b/clock/img/aqua_dayDown.gif differ diff --git a/clock/img/aqua_dayNormal.gif b/clock/img/aqua_dayNormal.gif new file mode 100644 index 0000000..62bee8f Binary files /dev/null and b/clock/img/aqua_dayNormal.gif differ diff --git a/clock/img/aqua_dayOver.gif b/clock/img/aqua_dayOver.gif new file mode 100644 index 0000000..264e2aa Binary files /dev/null and b/clock/img/aqua_dayOver.gif differ diff --git a/clock/img/armygreen_dayDown.gif b/clock/img/armygreen_dayDown.gif new file mode 100644 index 0000000..13707c3 Binary files /dev/null and b/clock/img/armygreen_dayDown.gif differ diff --git a/clock/img/armygreen_dayNormal.gif b/clock/img/armygreen_dayNormal.gif new file mode 100644 index 0000000..4631609 Binary files /dev/null and b/clock/img/armygreen_dayNormal.gif differ diff --git a/clock/img/armygreen_dayOver.gif b/clock/img/armygreen_dayOver.gif new file mode 100644 index 0000000..07baed7 Binary files /dev/null and b/clock/img/armygreen_dayOver.gif differ diff --git a/clock/img/bananasplit_dayDown.gif b/clock/img/bananasplit_dayDown.gif new file mode 100644 index 0000000..adb10f8 Binary files /dev/null and b/clock/img/bananasplit_dayDown.gif differ diff --git a/clock/img/bananasplit_dayNormal.gif b/clock/img/bananasplit_dayNormal.gif new file mode 100644 index 0000000..b916b0c Binary files /dev/null and b/clock/img/bananasplit_dayNormal.gif differ diff --git a/clock/img/bananasplit_dayOver.gif b/clock/img/bananasplit_dayOver.gif new file mode 100644 index 0000000..3789f30 Binary files /dev/null and b/clock/img/bananasplit_dayOver.gif differ diff --git a/clock/img/beige_dayDown.gif b/clock/img/beige_dayDown.gif new file mode 100644 index 0000000..510b08d Binary files /dev/null and b/clock/img/beige_dayDown.gif differ diff --git a/clock/img/beige_dayNormal.gif b/clock/img/beige_dayNormal.gif new file mode 100644 index 0000000..635dff6 Binary files /dev/null and b/clock/img/beige_dayNormal.gif differ diff --git a/clock/img/beige_dayOver.gif b/clock/img/beige_dayOver.gif new file mode 100644 index 0000000..a35b656 Binary files /dev/null and b/clock/img/beige_dayOver.gif differ diff --git a/clock/img/boxBottomLeftCorner.png b/clock/img/boxBottomLeftCorner.png new file mode 100644 index 0000000..5c39233 Binary files /dev/null and b/clock/img/boxBottomLeftCorner.png differ diff --git a/clock/img/boxBottomRightCorner.png b/clock/img/boxBottomRightCorner.png new file mode 100644 index 0000000..51a0367 Binary files /dev/null and b/clock/img/boxBottomRightCorner.png differ diff --git a/clock/img/boxSideWallPx.gif b/clock/img/boxSideWallPx.gif new file mode 100644 index 0000000..9016d31 Binary files /dev/null and b/clock/img/boxSideWallPx.gif differ diff --git a/clock/img/boxSideWallPx.png b/clock/img/boxSideWallPx.png new file mode 100644 index 0000000..fda209e Binary files /dev/null and b/clock/img/boxSideWallPx.png differ diff --git a/clock/img/boxTopLeftCorner.png b/clock/img/boxTopLeftCorner.png new file mode 100644 index 0000000..e146cf8 Binary files /dev/null and b/clock/img/boxTopLeftCorner.png differ diff --git a/clock/img/boxTopPx.gif b/clock/img/boxTopPx.gif new file mode 100644 index 0000000..62c24ac Binary files /dev/null and b/clock/img/boxTopPx.gif differ diff --git a/clock/img/boxTopPx.png b/clock/img/boxTopPx.png new file mode 100644 index 0000000..326ba05 Binary files /dev/null and b/clock/img/boxTopPx.png differ diff --git a/clock/img/boxTopRightCorner.png b/clock/img/boxTopRightCorner.png new file mode 100644 index 0000000..2a67dba Binary files /dev/null and b/clock/img/boxTopRightCorner.png differ diff --git a/clock/img/closeButton_down.gif b/clock/img/closeButton_down.gif new file mode 100644 index 0000000..cd218e2 Binary files /dev/null and b/clock/img/closeButton_down.gif differ diff --git a/clock/img/closeButton_normal.gif b/clock/img/closeButton_normal.gif new file mode 100644 index 0000000..737dc5b Binary files /dev/null and b/clock/img/closeButton_normal.gif differ diff --git a/clock/img/closeButton_over.gif b/clock/img/closeButton_over.gif new file mode 100644 index 0000000..8200058 Binary files /dev/null and b/clock/img/closeButton_over.gif differ diff --git a/clock/img/deepblue_dayDown.gif b/clock/img/deepblue_dayDown.gif new file mode 100644 index 0000000..d22916a Binary files /dev/null and b/clock/img/deepblue_dayDown.gif differ diff --git a/clock/img/deepblue_dayNormal.gif b/clock/img/deepblue_dayNormal.gif new file mode 100644 index 0000000..240ccbb Binary files /dev/null and b/clock/img/deepblue_dayNormal.gif differ diff --git a/clock/img/deepblue_dayOver.gif b/clock/img/deepblue_dayOver.gif new file mode 100644 index 0000000..66c6e78 Binary files /dev/null and b/clock/img/deepblue_dayOver.gif differ diff --git a/clock/img/greenish_dayDown.gif b/clock/img/greenish_dayDown.gif new file mode 100644 index 0000000..2a90f3c Binary files /dev/null and b/clock/img/greenish_dayDown.gif differ diff --git a/clock/img/greenish_dayNormal.gif b/clock/img/greenish_dayNormal.gif new file mode 100644 index 0000000..a43be3f Binary files /dev/null and b/clock/img/greenish_dayNormal.gif differ diff --git a/clock/img/greenish_dayOver.gif b/clock/img/greenish_dayOver.gif new file mode 100644 index 0000000..13fe177 Binary files /dev/null and b/clock/img/greenish_dayOver.gif differ diff --git a/clock/img/lightgreen_dayDown.gif b/clock/img/lightgreen_dayDown.gif new file mode 100644 index 0000000..9719329 Binary files /dev/null and b/clock/img/lightgreen_dayDown.gif differ diff --git a/clock/img/lightgreen_dayNormal.gif b/clock/img/lightgreen_dayNormal.gif new file mode 100644 index 0000000..bfa880b Binary files /dev/null and b/clock/img/lightgreen_dayNormal.gif differ diff --git a/clock/img/lightgreen_dayOver.gif b/clock/img/lightgreen_dayOver.gif new file mode 100644 index 0000000..eccc7b3 Binary files /dev/null and b/clock/img/lightgreen_dayOver.gif differ diff --git a/clock/img/monthBackward_down.gif b/clock/img/monthBackward_down.gif new file mode 100644 index 0000000..eb5398d Binary files /dev/null and b/clock/img/monthBackward_down.gif differ diff --git a/clock/img/monthBackward_normal.gif b/clock/img/monthBackward_normal.gif new file mode 100644 index 0000000..8b0172a Binary files /dev/null and b/clock/img/monthBackward_normal.gif differ diff --git a/clock/img/monthBackward_over.gif b/clock/img/monthBackward_over.gif new file mode 100644 index 0000000..3e032ba Binary files /dev/null and b/clock/img/monthBackward_over.gif differ diff --git a/clock/img/monthForward_down.gif b/clock/img/monthForward_down.gif new file mode 100644 index 0000000..39f755e Binary files /dev/null and b/clock/img/monthForward_down.gif differ diff --git a/clock/img/monthForward_normal.gif b/clock/img/monthForward_normal.gif new file mode 100644 index 0000000..332419a Binary files /dev/null and b/clock/img/monthForward_normal.gif differ diff --git a/clock/img/monthForward_over.gif b/clock/img/monthForward_over.gif new file mode 100644 index 0000000..410a5d7 Binary files /dev/null and b/clock/img/monthForward_over.gif differ diff --git a/clock/img/ocean_blue_dayDown.gif b/clock/img/ocean_blue_dayDown.gif new file mode 100644 index 0000000..be7c856 Binary files /dev/null and b/clock/img/ocean_blue_dayDown.gif differ diff --git a/clock/img/ocean_blue_dayNormal.gif b/clock/img/ocean_blue_dayNormal.gif new file mode 100644 index 0000000..da4957f Binary files /dev/null and b/clock/img/ocean_blue_dayNormal.gif differ diff --git a/clock/img/ocean_blue_dayOver.gif b/clock/img/ocean_blue_dayOver.gif new file mode 100644 index 0000000..427fab4 Binary files /dev/null and b/clock/img/ocean_blue_dayOver.gif differ diff --git a/clock/img/orange_dayDown.gif b/clock/img/orange_dayDown.gif new file mode 100644 index 0000000..c76b5ac Binary files /dev/null and b/clock/img/orange_dayDown.gif differ diff --git a/clock/img/orange_dayNormal.gif b/clock/img/orange_dayNormal.gif new file mode 100644 index 0000000..59d670e Binary files /dev/null and b/clock/img/orange_dayNormal.gif differ diff --git a/clock/img/orange_dayOver.gif b/clock/img/orange_dayOver.gif new file mode 100644 index 0000000..689538c Binary files /dev/null and b/clock/img/orange_dayOver.gif differ diff --git a/clock/img/peppermint_dayDown.gif b/clock/img/peppermint_dayDown.gif new file mode 100644 index 0000000..f3f88be Binary files /dev/null and b/clock/img/peppermint_dayDown.gif differ diff --git a/clock/img/peppermint_dayNormal.gif b/clock/img/peppermint_dayNormal.gif new file mode 100644 index 0000000..62411f3 Binary files /dev/null and b/clock/img/peppermint_dayNormal.gif differ diff --git a/clock/img/peppermint_dayOver.gif b/clock/img/peppermint_dayOver.gif new file mode 100644 index 0000000..030355e Binary files /dev/null and b/clock/img/peppermint_dayOver.gif differ diff --git a/clock/img/pink_dayDown.gif b/clock/img/pink_dayDown.gif new file mode 100644 index 0000000..8ec4ff6 Binary files /dev/null and b/clock/img/pink_dayDown.gif differ diff --git a/clock/img/pink_dayNormal.gif b/clock/img/pink_dayNormal.gif new file mode 100644 index 0000000..4ee641d Binary files /dev/null and b/clock/img/pink_dayNormal.gif differ diff --git a/clock/img/pink_dayOver.gif b/clock/img/pink_dayOver.gif new file mode 100644 index 0000000..530d32b Binary files /dev/null and b/clock/img/pink_dayOver.gif differ diff --git a/clock/img/purple_dayDown.gif b/clock/img/purple_dayDown.gif new file mode 100644 index 0000000..ec13fed Binary files /dev/null and b/clock/img/purple_dayDown.gif differ diff --git a/clock/img/purple_dayNormal.gif b/clock/img/purple_dayNormal.gif new file mode 100644 index 0000000..6483005 Binary files /dev/null and b/clock/img/purple_dayNormal.gif differ diff --git a/clock/img/purple_dayOver.gif b/clock/img/purple_dayOver.gif new file mode 100644 index 0000000..7d7109a Binary files /dev/null and b/clock/img/purple_dayOver.gif differ diff --git a/clock/img/torqoise_dayDown.gif b/clock/img/torqoise_dayDown.gif new file mode 100644 index 0000000..52f26ed Binary files /dev/null and b/clock/img/torqoise_dayDown.gif differ diff --git a/clock/img/torqoise_dayNormal.gif b/clock/img/torqoise_dayNormal.gif new file mode 100644 index 0000000..be204d9 Binary files /dev/null and b/clock/img/torqoise_dayNormal.gif differ diff --git a/clock/img/torqoise_dayOver.gif b/clock/img/torqoise_dayOver.gif new file mode 100644 index 0000000..88c4852 Binary files /dev/null and b/clock/img/torqoise_dayOver.gif differ diff --git a/clock/img/yearBackward_down.gif b/clock/img/yearBackward_down.gif new file mode 100644 index 0000000..ca1ba36 Binary files /dev/null and b/clock/img/yearBackward_down.gif differ diff --git a/clock/img/yearBackward_normal.gif b/clock/img/yearBackward_normal.gif new file mode 100644 index 0000000..d94b5e4 Binary files /dev/null and b/clock/img/yearBackward_normal.gif differ diff --git a/clock/img/yearBackward_over.gif b/clock/img/yearBackward_over.gif new file mode 100644 index 0000000..7e803c7 Binary files /dev/null and b/clock/img/yearBackward_over.gif differ diff --git a/clock/img/yearForward_down.gif b/clock/img/yearForward_down.gif new file mode 100644 index 0000000..3d2d56f Binary files /dev/null and b/clock/img/yearForward_down.gif differ diff --git a/clock/img/yearForward_normal.gif b/clock/img/yearForward_normal.gif new file mode 100644 index 0000000..475254e Binary files /dev/null and b/clock/img/yearForward_normal.gif differ diff --git a/clock/img/yearForward_over.gif b/clock/img/yearForward_over.gif new file mode 100644 index 0000000..bbbe8db Binary files /dev/null and b/clock/img/yearForward_over.gif differ diff --git a/clock/index.html b/clock/index.html new file mode 100644 index 0000000..6e2ff38 --- /dev/null +++ b/clock/index.html @@ -0,0 +1,12 @@ + +Clock + + + + +
+
+
+
+
+ \ No newline at end of file diff --git a/clock/jsDatePick.min.1.3.js b/clock/jsDatePick.min.1.3.js new file mode 100644 index 0000000..ead6de6 --- /dev/null +++ b/clock/jsDatePick.min.1.3.js @@ -0,0 +1 @@ +g_l=[];g_l.MONTHS=["Janaury","February","March","April","May","June","July","August","September","October","November","December"];g_l.DAYS_3=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];g_l.MONTH_FWD="Move a month forward";g_l.MONTH_BCK="Move a month backward";g_l.YEAR_FWD="Move a year forward";g_l.YEAR_BCK="Move a year backward";g_l.CLOSE="Close the calendar";g_l.ERROR_2=g_l.ERROR_1="Date object invalid!";g_l.ERROR_4=g_l.ERROR_3="Target invalid";g_jsDatePickImagePath="img/";g_jsDatePickDirectionality="ltr";g_arrayOfUsedJsDatePickCalsGlobalNumbers=[];g_arrayOfUsedJsDatePickCals=[];g_currentDateObject={};g_currentDateObject.dateObject=new Date();g_currentDateObject.day=g_currentDateObject.dateObject.getDate();g_currentDateObject.month=g_currentDateObject.dateObject.getMonth()+1;g_currentDateObject.year=g_currentDateObject.dateObject.getFullYear();JsgetElem=function(a){return document.getElementById(a)};String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")};String.prototype.ltrim=function(){return this.replace(/^\s+/,"")};String.prototype.rtrim=function(){return this.replace(/\s+$/,"")};String.prototype.strpad=function(){return(!isNaN(this)&&this.toString().length==1)?"0"+this:this};JsDatePick=function(a){if(document.all){this.isie=true;this.iever=JsDatePick.getInternetExplorerVersion()}else{this.isie=false}this.oConfiguration={};this.oCurrentDay=g_currentDateObject;this.monthsTextualRepresentation=g_l.MONTHS;this.lastPostedDay=null;this.initialZIndex=2;this.globalNumber=this.getUnUsedGlobalNumber();g_arrayOfUsedJsDatePickCals[this.globalNumber]=this;this.setConfiguration(a);this.makeCalendar()};JsDatePick.getCalInstanceById=function(a){return g_arrayOfUsedJsDatePickCals[parseInt(a,10)]};JsDatePick.getInternetExplorerVersion=function(){var c=-1,a,b;if(navigator.appName=="Microsoft Internet Explorer"){a=navigator.userAgent;b=new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");if(b.exec(a)!=null){c=parseFloat(RegExp.$1)}return c}};JsDatePick.prototype.setC=function(a,b){if(this.isie&&this.iever>7){a.setAttribute("class",b)}else{a.className=b}};JsDatePick.prototype.getUnUsedGlobalNumber=function(){var a=Math.floor(Math.random()*1000);while(!this.isUnique_GlobalNumber(a)){a=Math.floor(Math.random()*1000)}return a};JsDatePick.prototype.isUnique_GlobalNumber=function(b){var a;for(a=0;a5){c=this.senseDivider(this.oConfiguration.dateFormat);e=this.oConfiguration.dateFormat;g=b.value.trim().split(c);l=e.trim().split(c);d=a=h=k=0;for(d=0;dthis.oCurrentDay.year){return false}if(a>this.oCurrentDay.month&&c==this.oCurrentDay.year){return false}if(b>this.oCurrentDay.day&&a==this.oCurrentDay.month&&c==this.oCurrentDay.year){return false}return true};JsDatePick.prototype.getDOMCalendarStripped=function(){var h=document,e,i,b,a,f,c,g;e=h.createElement("div");if(this.oConfiguration.isStripped){this.setC(e,"boxMainStripped")}else{this.setC(e,"boxMain")}this.boxMain=e;i=h.createElement("div");b=h.createElement("div");a=h.createElement("div");f=h.createElement("div");c=h.createElement("div");g=h.createElement("div");this.setC(b,"clearfix");this.setC(g,"clearfix");this.setC(i,"boxMainInner");this.setC(a,"boxMainCellsContainer");this.setC(f,"tooltip");this.setC(c,"weekDaysRow");this.tooltip=f;e.appendChild(i);this.controlsBar=this.getDOMControlBar();this.makeDOMWeekDays(c);i.appendChild(this.controlsBar);i.appendChild(b);i.appendChild(f);i.appendChild(c);i.appendChild(a);i.appendChild(g);this.boxMainCellsContainer=a;this.populateMainBox(a);return e};JsDatePick.prototype.makeDOMWeekDays=function(a){var c=0,g=document,f=g_l.DAYS_3,e,b;for(c=this.oConfiguration.weekStartDay;c<7;c++){b=g.createElement("div");e=g.createTextNode(f[c]);this.setC(b,"weekDay");b.appendChild(e);a.appendChild(b)}if(this.oConfiguration.weekStartDay>0){for(c=0;cparseInt(this.oConfiguration.yearsRange[0])){this.currentYear--;this.repopulateMainBox();return true}else{return false}};JsDatePick.prototype.moveForwardOneMonth=function(){if(this.currentMonth<12){this.currentMonth++}else{if(this.moveForwardOneYear()){this.currentMonth=1}else{this.currentMonth=12}}this.repopulateMainBox()};JsDatePick.prototype.moveBackOneMonth=function(){if(this.currentMonth>1){this.currentMonth--}else{if(this.moveBackOneYear()){this.currentMonth=12}else{this.currentMonth=1}}this.repopulateMainBox()};JsDatePick.prototype.getCurrentColorScheme=function(){return this.oConfiguration.cellColorScheme};JsDatePick.prototype.getDOMControlBar=function(){var h=document,c,f,g,b,a,e;c=h.createElement("div");f=h.createElement("div");g=h.createElement("div");b=h.createElement("div");a=h.createElement("div");e=h.createElement("div");this.setC(c,"controlsBar");this.setC(f,"monthForwardButton");this.setC(g,"monthBackwardButton");this.setC(b,"yearForwardButton");this.setC(a,"yearBackwardButton");this.setC(e,"controlsBarText");c.setAttribute("globalNumber",this.globalNumber);f.setAttribute("globalNumber",this.globalNumber);g.setAttribute("globalNumber",this.globalNumber);a.setAttribute("globalNumber",this.globalNumber);b.setAttribute("globalNumber",this.globalNumber);this.controlsBarTextCell=e;c.appendChild(f);c.appendChild(g);c.appendChild(b);c.appendChild(a);c.appendChild(e);f.onmouseover=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}d=this.parentNode;while(d.className!="controlsBar"){d=d.parentNode}i=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));i.setTooltipText(g_l.MONTH_FWD);i.setC(this,"monthForwardButtonOver")};f.onmouseout=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText("");d.setC(this,"monthForwardButton")};f.onmousedown=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}d=this.parentNode;while(d.className!="controlsBar"){d=d.parentNode}i=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));i.setTooltipText(g_l.MONTH_FWD);i.setC(this,"monthForwardButtonDown")};f.onmouseup=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText(g_l.MONTH_FWD);d.setC(this,"monthForwardButton");d.moveForwardOneMonth()};g.onmouseover=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText(g_l.MONTH_BCK);d.setC(this,"monthBackwardButtonOver")};g.onmouseout=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText("");d.setC(this,"monthBackwardButton")};g.onmousedown=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText(g_l.MONTH_BCK);d.setC(this,"monthBackwardButtonDown")};g.onmouseup=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText(g_l.MONTH_BCK);d.setC(this,"monthBackwardButton");d.moveBackOneMonth()};b.onmouseover=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText(g_l.YEAR_FWD);d.setC(this,"yearForwardButtonOver")};b.onmouseout=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText("");d.setC(this,"yearForwardButton")};b.onmousedown=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText(g_l.YEAR_FWD);d.setC(this,"yearForwardButtonDown")};b.onmouseup=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText(g_l.YEAR_FWD);d.setC(this,"yearForwardButton");d.moveForwardOneYear()};a.onmouseover=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText(g_l.YEAR_BCK);d.setC(this,"yearBackwardButtonOver")};a.onmouseout=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText("");d.setC(this,"yearBackwardButton")};a.onmousedown=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText(g_l.YEAR_BCK);d.setC(this,"yearBackwardButtonDown")};a.onmouseup=function(){var i,d;if(parseInt(this.getAttribute("isJsDatePickDisabled"))==1){return}i=this.parentNode;while(i.className!="controlsBar"){i=i.parentNode}d=JsDatePick.getCalInstanceById(this.getAttribute("globalNumber"));d.setTooltipText(g_l.YEAR_BCK);d.setC(this,"yearBackwardButton");d.moveBackOneYear()};return c}; \ No newline at end of file diff --git a/clock/jsDatePick_ltr.min.css b/clock/jsDatePick_ltr.min.css new file mode 100644 index 0000000..ce48b0e --- /dev/null +++ b/clock/jsDatePick_ltr.min.css @@ -0,0 +1 @@ +.JsDatePickBox{position:relative;width:212px;font-family:Arial;}.JsDatePickBox .jsDatePickCloseButton{cursor:pointer;position:absolute;z-index:1;top:1px;right:10px;width:33px;height:13px;background:url(img/closeButton_normal.gif) left top no-repeat;}.JsDatePickBox .jsDatePickCloseButtonOver{cursor:pointer;position:absolute;z-index:1;top:1px;right:10px;width:33px;height:13px;background:url(img/closeButton_over.gif) left top no-repeat;}.JsDatePickBox .jsDatePickCloseButtonDown{cursor:pointer;position:absolute;z-index:1;top:1px;right:10px;width:33px;height:13px;background:url(img/closeButton_down.gif) left top no-repeat;}.JsDatePickBox .boxLeftWall{float:left;width:7px;margin:0;padding:0;}.JsDatePickBox .boxLeftWall .leftTopCorner{margin:0;padding:0;width:7px;height:8px;background:url(img/boxTopLeftCorner.png) left top no-repeat;overflow:hidden;}.JsDatePickBox .boxLeftWall .leftBottomCorner{margin:0;padding:0;width:7px;height:8px;background:url(img/boxBottomLeftCorner.png) left top no-repeat;overflow:hidden;}.JsDatePickBox .boxLeftWall .leftWall{margin:0;padding:0;width:7px;background:url(img/boxSideWallPx.gif) #fff left top repeat-y;overflow:hidden;}.JsDatePickBox .boxRightWall{float:left;width:7px;margin:0;padding:0;}.JsDatePickBox .boxRightWall .rightTopCorner{margin:0;padding:0;width:7px;height:8px;background:url(img/boxTopRightCorner.png) left top no-repeat;overflow:hidden;}.JsDatePickBox .boxRightWall .rightBottomCorner{margin:0;padding:0;width:7px;height:8px;background:url(img/boxBottomRightCorner.png) left top no-repeat;overflow:hidden;}.JsDatePickBox .boxRightWall .rightWall{margin:0;padding:0;width:7px;background:url(img/boxSideWallPx.gif) #fff right top repeat-y;overflow:hidden;}.JsDatePickBox .topWall{position:absolute;overflow:hidden;top:0;left:7px;width:198px;height:4px;background:url(img/boxTopPx.gif) #fff left top repeat-x;}.JsDatePickBox .bottomWall{position:absolute;overflow:hidden;bottom:-1px;left:7px;width:198px;height:4px;background:url(img/boxTopPx.gif) #fff left top repeat-x;}.JsDatePickBox .hiddenBoxLeftWall{float:left;width:0;overflow:hidden;overflow:hidden;}.JsDatePickBox .hiddenBoxRightWall{float:left;width:0;overflow:hidden;overflow:hidden;}.JsDatePickBox .boxMain{float:left;background-color:#fff;margin:0;padding:15px 0 5px 0;}.JsDatePickBox .boxMainStripped{background:#fff;border:none;}.JsDatePickBox .tooltip{height:12px;line-height:11px;overflow:hidden;font-size:10px;color:#666;text-align:left;padding:0;margin:2px 0 2px 0;}.JsDatePickBox .weekDaysRow{height:12px;overflow:hidden;line-height:11px;font-size:10px;color:#666;text-align:center;padding:0;margin:2px 0 0 0;}.JsDatePickBox .weekDaysRow .weekDay{float:left;height:14px;overflow:hidden;width:24px;margin:0 5px 0 0;padding:0;}.JsDatePickBox .boxMainInner{background:#fff;width:198px;float:left;margin:5px 0 0 0;padding:0;}.JsDatePickBox .boxMainCellsContainer{background-color:#fff;margin:0;padding:0;}.JsDatePickBox .boxMainInner .controlsBar{overflow:hidden;height:20px;position:relative;}.JsDatePickBox .boxMainInner .controlsBarText{overflow:hidden;height:20px;line-height:20px;color:#000;font-size:12px;text-align:center;}.JsDatePickBox .boxMainInner .monthForwardButton{overflow:hidden;cursor:pointer;width:20px;height:20px;position:absolute;top:0;right:22px;background:url(img/monthForward_normal.gif) left top no-repeat;}.JsDatePickBox .boxMainInner .monthForwardButtonOver{overflow:hidden;cursor:pointer;width:20px;height:20px;position:absolute;top:0;right:22px;background:url(img/monthForward_over.gif) left top no-repeat;}.JsDatePickBox .boxMainInner .monthForwardButtonDown{overflow:hidden;cursor:pointer;width:20px;height:20px;position:absolute;top:0;right:22px;background:url(img/monthForward_down.gif) left top no-repeat;}.JsDatePickBox .boxMainInner .monthBackwardButton{overflow:hidden;cursor:pointer;width:20px;height:20px;position:absolute;top:0;left:22px;background:url(img/monthBackward_normal.gif) left top no-repeat;}.JsDatePickBox .boxMainInner .monthBackwardButtonOver{overflow:hidden;cursor:pointer;width:20px;height:20px;position:absolute;top:0;left:22px;background:url(img/monthBackward_over.gif) left top no-repeat;}.JsDatePickBox .boxMainInner .monthBackwardButtonDown{overflow:hidden;cursor:pointer;width:20px;height:20px;position:absolute;top:0;left:22px;background:url(img/monthBackward_down.gif) left top no-repeat;}.JsDatePickBox .boxMainInner .yearForwardButton{overflow:hidden;cursor:pointer;width:20px;height:20px;position:absolute;top:0;right:0;background:url(img/yearForward_normal.gif) left top no-repeat;}.JsDatePickBox .boxMainInner .yearForwardButtonOver{overflow:hidden;cursor:pointer;width:20px;height:20px;position:absolute;top:0;right:0;background:url(img/yearForward_over.gif) left top no-repeat;}.JsDatePickBox .boxMainInner .yearForwardButtonDown{overflow:hidden;cursor:pointer;width:20px;height:20px;position:absolute;top:0;right:0;background:url(img/yearForward_down.gif) left top no-repeat;}.JsDatePickBox .boxMainInner .yearBackwardButton{overflow:hidden;cursor:pointer;width:20px;height:20px;position:absolute;top:0;left:0;background:url(img/yearBackward_normal.gif) left top no-repeat;}.JsDatePickBox .boxMainInner .yearBackwardButtonOver{overflow:hidden;cursor:pointer;width:20px;height:20px;position:absolute;top:0;left:0;background:url(img/yearBackward_over.gif) left top no-repeat;}.JsDatePickBox .boxMainInner .yearBackwardButtonDown{overflow:hidden;cursor:pointer;width:20px;height:20px;position:absolute;top:0;left:0;background:url(img/yearBackward_down.gif) left top no-repeat;}.JsDatePickBox .boxMainInner .skipDay{cursor:default;overflow:hidden;width:24px;height:25px;float:left;margin:4px 5px 0 0;padding:0;}.JsDatePickBox .boxMainInner .dayNormal{-khtml-user-select:none;font-size:12px;cursor:pointer;overflow:hidden;color:#4c4c4c;width:24px;height:25px;float:left;margin:4px 5px 0 0;padding:0;text-align:center;line-height:25px;}.JsDatePickBox .boxMainInner .dayNormalToday{-khtml-user-select:none;font-size:12px;cursor:pointer;overflow:hidden;color:#f40f0f;font-weight:bold;width:24px;height:25px;float:left;margin:4px 5px 0 0;padding:0;text-align:center;line-height:25px;}.JsDatePickBox .boxMainInner .dayDisabled{-khtml-user-select:none;cursor:default;font-size:12px;overflow:hidden;color:#999;width:24px;height:25px;float:left;margin:4px 5px 0 0;padding:0;text-align:center;line-height:25px;}.JsDatePickBox .boxMainInner .dayOver{-khtml-user-select:none;cursor:pointer;font-size:12px;overflow:hidden;color:#4c4c4c;width:24px;height:25px;float:left;margin:4px 5px 0 0;padding:0;text-align:center;line-height:25px;}.JsDatePickBox .boxMainInner .dayOverToday{-khtml-user-select:none;cursor:pointer;font-size:12px;overflow:hidden;font-weight:bold;color:#f40f0f;width:24px;height:25px;float:left;margin:4px 5px 0 0;padding:0;text-align:center;line-height:25px;}.JsDatePickBox .boxMainInner .dayDown{-khtml-user-select:none;cursor:pointer;font-size:12px;overflow:hidden;color:#F9F9F9;width:24px;height:25px;float:left;margin:4px 5px 0 0;padding:0;text-align:center;line-height:25px;}.JsDatePickBox .boxMainInner .dayDownToday{-khtml-user-select:none;cursor:pointer;font-size:12px;overflow:hidden;color:#f40f0f;font-weight:bold;width:24px;height:25px;float:left;margin:4px 5px 0 0;padding:0;text-align:center;line-height:25px;} \ No newline at end of file diff --git a/desktop/app_png.htc b/desktop/app_png.htc new file mode 100644 index 0000000..03a16bb --- /dev/null +++ b/desktop/app_png.htc @@ -0,0 +1,103 @@ + + + + + diff --git a/desktop/bg/gradient.png b/desktop/bg/gradient.png new file mode 100644 index 0000000..71f02c6 Binary files /dev/null and b/desktop/bg/gradient.png differ diff --git a/desktop/bg/gradient2.png b/desktop/bg/gradient2.png new file mode 100644 index 0000000..a7de125 Binary files /dev/null and b/desktop/bg/gradient2.png differ diff --git a/desktop/desk_png.htc b/desktop/desk_png.htc new file mode 100644 index 0000000..c2dba36 --- /dev/null +++ b/desktop/desk_png.htc @@ -0,0 +1,103 @@ + + + + + diff --git a/desktop/dock/calc.gif b/desktop/dock/calc.gif new file mode 100644 index 0000000..3903cae Binary files /dev/null and b/desktop/dock/calc.gif differ diff --git a/desktop/dock/chat.gif b/desktop/dock/chat.gif new file mode 100644 index 0000000..f795bcb Binary files /dev/null and b/desktop/dock/chat.gif differ diff --git a/desktop/dock/dock-bg.gif b/desktop/dock/dock-bg.gif new file mode 100644 index 0000000..2c5451c Binary files /dev/null and b/desktop/dock/dock-bg.gif differ diff --git a/desktop/dock/dock-bg2.gif b/desktop/dock/dock-bg2.gif new file mode 100644 index 0000000..1398985 Binary files /dev/null and b/desktop/dock/dock-bg2.gif differ diff --git a/desktop/dock/forum.gif b/desktop/dock/forum.gif new file mode 100644 index 0000000..10bec5e Binary files /dev/null and b/desktop/dock/forum.gif differ diff --git a/desktop/dock/games.gif b/desktop/dock/games.gif new file mode 100644 index 0000000..ead22d4 Binary files /dev/null and b/desktop/dock/games.gif differ diff --git a/desktop/dock/help.gif b/desktop/dock/help.gif new file mode 100644 index 0000000..c4d18b8 Binary files /dev/null and b/desktop/dock/help.gif differ diff --git a/desktop/dock/inet.gif b/desktop/dock/inet.gif new file mode 100644 index 0000000..469ea05 Binary files /dev/null and b/desktop/dock/inet.gif differ diff --git a/desktop/dock/info.gif b/desktop/dock/info.gif new file mode 100644 index 0000000..597e0c7 Binary files /dev/null and b/desktop/dock/info.gif differ diff --git a/desktop/dock/logout.gif b/desktop/dock/logout.gif new file mode 100644 index 0000000..9759ef5 Binary files /dev/null and b/desktop/dock/logout.gif differ diff --git a/desktop/dock/media.gif b/desktop/dock/media.gif new file mode 100644 index 0000000..668022b Binary files /dev/null and b/desktop/dock/media.gif differ diff --git a/desktop/dock/sample.gif b/desktop/dock/sample.gif new file mode 100644 index 0000000..235d5e2 Binary files /dev/null and b/desktop/dock/sample.gif differ diff --git a/desktop/dock/sprdsht.gif b/desktop/dock/sprdsht.gif new file mode 100644 index 0000000..1f13efa Binary files /dev/null and b/desktop/dock/sprdsht.gif differ diff --git a/desktop/dock/term.gif b/desktop/dock/term.gif new file mode 100644 index 0000000..c988744 Binary files /dev/null and b/desktop/dock/term.gif differ diff --git a/desktop/dock/time.gif b/desktop/dock/time.gif new file mode 100644 index 0000000..dc281f0 Binary files /dev/null and b/desktop/dock/time.gif differ diff --git a/desktop/dock/wproc.gif b/desktop/dock/wproc.gif new file mode 100644 index 0000000..89f9486 Binary files /dev/null and b/desktop/dock/wproc.gif differ diff --git a/desktop/drag/calc.gif b/desktop/drag/calc.gif new file mode 100644 index 0000000..bd5e214 Binary files /dev/null and b/desktop/drag/calc.gif differ diff --git a/desktop/drag/chat.gif b/desktop/drag/chat.gif new file mode 100644 index 0000000..a50dbf2 Binary files /dev/null and b/desktop/drag/chat.gif differ diff --git a/desktop/drag/forum.gif b/desktop/drag/forum.gif new file mode 100644 index 0000000..e92d190 Binary files /dev/null and b/desktop/drag/forum.gif differ diff --git a/desktop/drag/games.gif b/desktop/drag/games.gif new file mode 100644 index 0000000..4947921 Binary files /dev/null and b/desktop/drag/games.gif differ diff --git a/desktop/drag/help.gif b/desktop/drag/help.gif new file mode 100644 index 0000000..88bdea3 Binary files /dev/null and b/desktop/drag/help.gif differ diff --git a/desktop/drag/inet.gif b/desktop/drag/inet.gif new file mode 100644 index 0000000..1c4a99f Binary files /dev/null and b/desktop/drag/inet.gif differ diff --git a/desktop/drag/logout.gif b/desktop/drag/logout.gif new file mode 100644 index 0000000..3dfb272 Binary files /dev/null and b/desktop/drag/logout.gif differ diff --git a/desktop/drag/media.gif b/desktop/drag/media.gif new file mode 100644 index 0000000..ed4e3a3 Binary files /dev/null and b/desktop/drag/media.gif differ diff --git a/desktop/drag/sprdsht.gif b/desktop/drag/sprdsht.gif new file mode 100644 index 0000000..e387319 Binary files /dev/null and b/desktop/drag/sprdsht.gif differ diff --git a/desktop/drag/term.gif b/desktop/drag/term.gif new file mode 100644 index 0000000..2536d6f Binary files /dev/null and b/desktop/drag/term.gif differ diff --git a/desktop/drag/wproc.gif b/desktop/drag/wproc.gif new file mode 100644 index 0000000..26b6ae0 Binary files /dev/null and b/desktop/drag/wproc.gif differ diff --git a/desktop/js/clock.js b/desktop/js/clock.js new file mode 100644 index 0000000..0fcc4cb --- /dev/null +++ b/desktop/js/clock.js @@ -0,0 +1,22 @@ +tday =new Array("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"); +tmonth=new Array("January","February","March","April","May","June","July","August","September","October","November","December"); +function GetClock(){ +d = new Date(); +nday = d.getDay(); +nmonth = d.getMonth(); +ndate = d.getDate(); +nyear = d.getYear(); +nhour = d.getHours(); +nmin = d.getMinutes(); +nsec = d.getSeconds(); +if(nyear<1000) nyear=nyear+1900; +if(nhour == 0) {ap = " AM";nhour = 12;} +else if(nhour <= 11) {ap = " AM";} +else if(nhour == 12) {ap = " PM";} +else if(nhour >= 13) {ap = " PM";nhour -= 12;} +if(nmin <= 9) {nmin = "0" +nmin;} +if(nsec <= 9) {nsec = "0" +nsec;} +document.getElementById('clockbox').innerHTML=""+tday[nday]+", "+tmonth[nmonth]+" "+ndate+", "+nyear+"
"+nhour+":"+nmin+":"+nsec+ap+""; +setTimeout("GetClock()", 1000); +} +window.onload=GetClock; \ No newline at end of file diff --git a/desktop/js/drag.js b/desktop/js/drag.js new file mode 100644 index 0000000..7d7074e --- /dev/null +++ b/desktop/js/drag.js @@ -0,0 +1,79 @@ +var dO=new Object(); +dO.snapthresh=20; // THIS VALUE IS THE SNAPTO INCREMENT. +dO.snapto=false; // SET TO true TO ENABLE SNAPTO, false TO DISABLE IT. + +dO.currID=null; +dO.z=0; +dO.xo=0; +dO.yo=0; +dO.ns4=(document.layers)?true:false; +dO.ns6=(document.getElementById&&!document.all)?true:false; +dO.ie4=(document.all&&!document.getElementById)?true:false; +dO.ie5=(document.all&&document.getElementById)?true:false; +dO.w3c=(document.getElementById)?true:false; + +function invsnap(){ +dO.snapto=!dO.snapto; +} + +//NEAT FUNCTION BY MIKE HALL (OF BRAINJAR.COM) THAT FINDS NESTED LAYERS FOR NS4.x +function findnestedlayer(name,doc){ +var i,layer; +for(i=0;i0) +if((layer=findlayer(name,layer.document))!=null) +return layer; +} +return null; +} + +function trckM(e){ +if(dO.currID!=null){ +var x=(dO.ie4||dO.ie5)?event.clientX+document.body.scrollLeft:e.pageX; +var y=(dO.ie4||dO.ie5)?event.clientY+document.body.scrollTop:e.pageY; +if(dO.snapto){ +x=Math.ceil(x/dO.snapthresh)*dO.snapthresh; +y=Math.ceil(y/dO.snapthresh)*dO.snapthresh; +} +if(dO.ns4)dO.currID.moveTo(x-dO.xo, y-dO.yo); +else{ +dO.currID.style.top=y-dO.yo+'px'; +dO.currID.style.left=x-dO.xo+'px'; +}} +return false; +} + +function drgI(e){ +if(dO.currID==null){ +var tx=(dO.ns4)? this.left : parseInt(this.style.left); +var ty=(dO.ns4)? this.top : parseInt(this.style.top); +dO.currID=this; +if(dO.ns4)this.zIndex=document.images.length+(dO.z++); +else this.style.zIndex=document.images.length+(dO.z++); +dO.xo=((dO.ie4||dO.ie5)?event.clientX+document.body.scrollLeft:e.pageX)-tx; +dO.yo=((dO.ie4||dO.ie5)?event.clientY+document.body.scrollTop:e.pageY)-ty; +if(dO.snapto){ +dO.xo=Math.ceil(dO.xo/dO.snapthresh)*dO.snapthresh; +dO.yo=Math.ceil(dO.yo/dO.snapthresh)*dO.snapthresh; +} +return false; +}} + +function dragElement(id){ +this.idRef=(dO.ns4)? findnestedlayer(id,document) : (dO.ie4)? document.all[id] : document.getElementById(id); +if(dO.ns4)this.idRef.captureEvents(Event.MOUSEDOWN | Event.MOUSEUP); +this.idRef.onmousedown=drgI; +this.idRef.onmouseup=function(){dO.currID=null} +} + +if(dO.ns4)document.captureEvents(Event.MOUSEMOVE); +document.onmousemove=trckM; + + +window.onresize=function(){ +if(dO.ns4)setTimeout('history.go(0)',300); +} + + diff --git a/desktop/js/interface.js b/desktop/js/interface.js new file mode 100644 index 0000000..ac1e810 --- /dev/null +++ b/desktop/js/interface.js @@ -0,0 +1,8 @@ +/* + * Interface elements for jQuery - http://interface.eyecon.ro + * + * Copyright (c) 2006 Stefan Petre + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + */ + 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}('6.15={3o:d(e){7 x=0;7 y=0;7 1Q=1A;7 A=e.L;8(6(e).H(\'Q\')==\'U\'){1E=A.1a;2y=A.O;A.1a=\'1F\';A.Q=\'1Y\';A.O=\'2e\';1Q=26}7 4=e;2P(4){x+=4.3B+(4.1I&&!6.3p.41?F(4.1I.2X)||0:0);y+=4.3x+(4.1I&&!6.3p.41?F(4.1I.2Z)||0:0);4=4.4e}4=e;2P(4&&4.4a&&4.4a.39()!=\'V\'){x-=4.1D||0;y-=4.1s||0;4=4.2x}8(1Q){A.Q=\'U\';A.O=2y;A.1a=1E}q{x:x,y:y}},4E:d(4){7 x=0,y=0;2P(4){x+=4.3B||0;y+=4.3x||0;4=4.4e}q{x:x,y:y}},35:d(e){7 w=6.H(e,\'1T\');7 h=6.H(e,\'36\');7 1e=0;7 1o=0;7 A=e.L;8(6(e).H(\'Q\')!=\'U\'){1e=e.1z;1o=e.2s}u{1E=A.1a;2y=A.O;A.1a=\'1F\';A.Q=\'1Y\';A.O=\'2e\';1e=e.1z;1o=e.2s;A.Q=\'U\';A.O=2y;A.1a=1E}q{w:w,h:h,1e:1e,1o:1o}},4P:d(4){q{1e:4.1z||0,1o:4.2s||0}},58:d(e){7 h,w,22;8(e){w=e.2a;h=e.29}u{22=D.Y;w=2z.3c||2N.3c||(22&&22.2a)||D.V.2a;h=2z.31||2N.31||(22&&22.29)||D.V.29}q{w:w,h:h}},3P:d(e){7 t,l,w,h,1J,1R;8(e&&e.2E.39()!=\'V\'){t=e.1s;l=e.1D;w=e.3j;h=e.3e;1J=0;1R=0}u{8(D.Y&&D.Y.1s){t=D.Y.1s;l=D.Y.1D;w=D.Y.3j;h=D.Y.3e}u 8(D.V){t=D.V.1s;l=D.V.1D;w=D.V.3j;h=D.V.3e}1J=2N.3c||D.Y.2a||D.V.2a||0;1R=2N.31||D.Y.29||D.V.29||0}q{t:t,l:l,w:w,h:h,1J:1J,1R:1R}},3L:d(e,1U){7 4=6(e);7 t=4.H(\'2j\')||\'\';7 r=4.H(\'2k\')||\'\';7 b=4.H(\'2n\')||\'\';7 l=4.H(\'2l\')||\'\';8(1U)q{t:F(t)||0,r:F(r)||0,b:F(b)||0,l:F(l)};u q{t:t,r:r,b:b,l:l}},56:d(e,1U){7 4=6(e);7 t=4.H(\'3w\')||\'\';7 r=4.H(\'3u\')||\'\';7 b=4.H(\'3s\')||\'\';7 l=4.H(\'3t\')||\'\';8(1U)q{t:F(t)||0,r:F(r)||0,b:F(b)||0,l:F(l)};u q{t:t,r:r,b:b,l:l}},4Z:d(e,1U){7 4=6(e);7 t=4.H(\'2Z\')||\'\';7 r=4.H(\'3G\')||\'\';7 b=4.H(\'3y\')||\'\';7 l=4.H(\'2X\')||\'\';8(1U)q{t:F(t)||0,r:F(r)||0,b:F(b)||0,l:F(l)||0};u q{t:t,r:r,b:b,l:l}},3T:d(2i){7 x=2i.53||(2i.52+(D.Y.1D||D.V.1D))||0;7 y=2i.51||(2i.54+(D.Y.1s||D.V.1s))||0;q{x:x,y:y}},3h:d(12,3g){3g(12);12=12.3F;2P(12){6.15.3h(12,3g);12=12.5a}},59:d(12){6.15.3h(12,d(4){S(7 1j 1q 4){8(2R 4[1j]===\'d\'){4[1j]=20}}})},57:d(4,27){7 1b=$.15.3P();7 3l=$.15.35(4);8(!27||27==\'4Y\')$(4).H({X:1b.t+((1i.3S(1b.h,1b.1R)-1b.t-3l.1o)/2)+\'K\'});8(!27||27==\'4Q\')$(4).H({N:1b.l+((1i.3S(1b.w,1b.1J)-1b.l-3l.1e)/2)+\'K\'})},4O:d(4,3U){7 3V=$(\'3q[@2u*="2w"]\',4||D),2w;3V.1V(d(){2w=k.2u;k.2u=3U;k.L.4M="4N:4R.4S.4W(2u=\'"+2w+"\')"})}};[].4i||(4V.4U.4i=d(v,n){n=(n==20)?0:n;7 m=k.1m;S(7 i=n;i-4p?r:(Z(6.H(C,I))||0);W=W==\'3N\'?(1O==\'U\'?\'2H\':\'2G\'):W;o[W]=26;G[I]=W==\'2H\'?[0,C.1r[I]]:[C.1r[I],0];8(I!=\'1f\')y[I]=G[I][0]+(I!=\'2O\'&&I!=\'34\'?\'K\':\'\');u 6.1j(y,"1f",G[I][0])}u{G[I]=[Z(6.1y(C,I)),Z(W)||0]}}u 8(6.E.49[I])G[I]=[6.E.1w(6.1y(C,I)),6.E.1w(W)];u 8(/^2D$|2C$|24$|2B$|3d$/i.2q(I)){7 m=W.1t(/\\s+/g,\' \').1t(/1N\\s*\\(\\s*/g,\'1N(\').1t(/\\s*,\\s*/g,\',\').1t(/\\s*\\)/g,\')\').64(/([^\\s]+)/g);6n(I){2b\'2D\':2b\'2C\':2b\'3d\':2b\'2B\':m[3]=m[3]||m[1]||m[0];m[2]=m[2]||m[0];m[1]=m[1]||m[0];S(7 i=0;i<6.E.25.1m;i++){7 1l=6.E.3k[I][0]+6.E.25[i]+6.E.3k[I][1];G[1l]=I==\'2B\'?[6.E.1w(6.1y(C,1l)),6.E.1w(m[i])]:[Z(6.1y(C,1l)),Z(m[i])]}3R;2b\'24\':S(7 i=0;io.1k+z.2L){4h(z.2h);z.2h=20;S(p 1q G){8(p=="1f")6.1j(y,"1f",G[p][1]);u 8(2R G[p][1]==\'2V\')y[p]=\'1N(\'+G[p][1].r+\',\'+G[p][1].g+\',\'+G[p][1].b+\')\';u y[p]=G[p][1]+(p!=\'2O\'&&p!=\'34\'?\'K\':\'\')}8(o.2G||o.2H)S(7 p 1q C.1r)8(p=="1f")6.1j(y,p,C.1r[p]);u y[p]="";y.Q=o.2G?\'U\':(1O!=\'U\'?1O:\'1Y\');y.2K=44;C.1n=20;8(6.43(o.2M))o.2M.3Z(C)}u{7 n=t-k.2L;7 2c=n/o.1k;S(p 1q G){8(2R G[p][1]==\'2V\'){y[p]=\'1N(\'+F(6.P[o.P](2c,n,G[p][0].r,(G[p][1].r-G[p][0].r),o.1k))+\',\'+F(6.P[o.P](2c,n,G[p][0].g,(G[p][1].g-G[p][0].g),o.1k))+\',\'+F(6.P[o.P](2c,n,G[p][0].b,(G[p][1].b-G[p][0].b),o.1k))+\')\'}u{7 2W=6.P[o.P](2c,n,G[p][0],(G[p][1]-G[p][0]),o.1k);8(p=="1f")6.1j(y,"1f",2W);u y[p]=2W+(p!=\'2O\'&&p!=\'34\'?\'K\':\'\')}}}};z.2h=3O(d(){z.14()},13);C.1n=z},3m:d(C,14){8(14)C.1n.2L-=67;u{2z.4h(C.1n.2h);C.1n=20;6.65(C,"E")}}});6.30=d(1g){7 1c={};8(2R 1g==\'66\'){1g=1g.39().40(\';\');S(7 i=0;i<1g.1m;i++){1X=1g[i].40(\':\');8(1X.1m==2){1c[6.45(1X[0].1t(/\\-(\\w)/g,d(m,c){q c.6a()}))]=6.45(1X[1])}}}q 1c};6.1u={3K:d(o){q k.1V(d(){7 4=k;4.f={10:6(o.10,k),23:6(o.23,k),21:6.15.3o(k),T:o.T,2p:o.2p,1Z:o.1Z,3Y:o.3Y,17:o.17,2T:o.2T};6.1u.2J(4,0);6(2z).2F(\'6b\',d(){4.f.21=6.15.3o(4);6.1u.2J(4,0);6.1u.3i(4)});6.1u.3i(4);4.f.10.2F(\'6g\',d(){6(4.f.2p,k).1S(0).L.Q=\'1Y\'}).2F(\'6f\',d(){6(4.f.2p,k).1S(0).L.Q=\'U\'});6(D).2F(\'6e\',d(e){7 2g=6.15.3T(e);7 19=0;8(4.f.17&&4.f.17==\'3M\')7 2o=2g.x-4.f.21.x-(4.1z-4.f.T*4.f.10.1W())/2-4.f.T/2;u 8(4.f.17&&4.f.17==\'38\')7 2o=2g.x-4.f.21.x-4.1z+4.f.T*4.f.10.1W();u 7 2o=2g.x-4.f.21.x;7 3D=1i.3C(2g.y-4.f.21.y-4.2s/2,2);4.f.10.1V(d(2m){11=1i.6c(1i.3C(2o-2m*4.f.T,2)+3D);11-=4.f.T/2;11=11<0?0:11;11=11>4.f.1Z?4.f.1Z:11;11=4.f.1Z-11;3a=4.f.2T*11/4.f.1Z;k.L.1T=4.f.T+3a+\'K\';k.L.N=4.f.T*2m+19+\'K\';19+=3a});6.1u.2J(4,19)})})},2J:d(4,19){8(4.f.17)8(4.f.17==\'3M\')4.f.23.1S(0).L.N=(4.1z-4.f.T*4.f.10.1W())/2-19/2+\'K\';u 8(4.f.17==\'N\')4.f.23.1S(0).L.N=-19/4.f.10.1W()+\'K\';u 8(4.f.17==\'38\')4.f.23.1S(0).L.N=(4.1z-4.f.T*4.f.10.1W())-19/2+\'K\';4.f.23.1S(0).L.1T=4.f.T*4.f.10.1W()+19+\'K\'},3i:d(4){4.f.10.1V(d(2m){k.L.1T=4.f.T+\'K\';k.L.N=4.f.T*2m+\'K\'})}};6.3Q.6d=6.1u.3K;',62,456,'||||el||jQuery|var|if|||||function||fisheyeCfg|||||this||||options||return||||else||||||es|255|elem|document|fx|parseInt|props|css|tp|oldStyle|px|style|result|left|position|easing|display|wrs|for|itemWidth|none|body|vp|top|documentElement|parseFloat|items|distance|nodeEl||step|iUtil|color|halign|margins|toAdd|visibility|clientScroll|newStyles|cs|wb|opacity|styles|prop|Math|attr|duration|nmp|length|animationHandler|hb|old|in|orig|scrollTop|replace|iFisheye|128|parseColor|wr|curCSS|offsetWidth|false|F0|0x|scrollLeft|oldVisibility|hidden|speed|fA|currentStyle|iw|cssRules|139|0px|rgb|oldDisplay|np|restoreStyle|ih|get|width|toInteger|each|size|rule|block|proximity|null|pos|de|container|border|cssSides|true|axis|new|clientHeight|clientWidth|case|pr|queue|absolute|namedColors|pointer|timer|event|marginTop|marginRight|marginLeft|nr|marginBottom|posx|itemsText|test|sideEnd|offsetHeight|opt|src|callback|png|parentNode|oldPosition|window|exec|borderColor|padding|margin|nodeName|bind|hide|show|192|positionContainer|overflow|startTime|complete|self|zIndex|while|getValues|typeof|styleSheets|maxWidth|211|object|pValue|borderLeftWidth|oldFloat|borderTopWidth|parseStyle|innerHeight|sizes|relative|fontWeight|getSize|height|pause|right|toLowerCase|extraWidth|169|innerWidth|borderWidth|scrollHeight|Color|func|traverseDOM|positionItems|scrollWidth|cssSidesEnd|windowSize|stopAnim|floatVal|getPosition|browser|img|224|paddingBottom|paddingLeft|paddingRight|230|paddingTop|offsetTop|borderBottomWidth|notColor|fxCheckTag|offsetLeft|pow|posy|240|firstChild|borderRightWidth|165|140|wid|build|getMargins|center|toggle|setInterval|getScroll|fn|break|max|getPointer|emptyGIF|images|Width|className|valign|apply|split|opera||isFunction|oldOverflow|trim|Date||getTime|colorCssProps|tagName|extend|144|delta|offsetParent|insertBefore|styleFloat|clearInterval|indexOf|firstNum|107|245|fxe|cssProps|values|10000|linear|azure|dl|220|iframe|ul|beige|table|button|form|appendChild|fxWrapper|listStyle|cssFloat|getPositionLite|id|msie|ol|wrapper|div|aqua|td|filter|progid|fixPNG|getSizeLite|horizontally|DXImageTransform|Microsoft|tr|prototype|Array|AlphaImageLoader|black|vertically|getBorder||pageY|clientX|pageX|clientY||getPadding|centerEl|getClient|purgeEvents|nextSibling|tbody|caption|float|w_|buildWrapper|removeChild|destroyWrapper|random|createElement|select|hr|input|br|meta|optgroup|colgroup|col|tfoot|thead|th|header|option|frameset|frame|script|textarea|fontSize|outlineColor|Top|borderTopColor|borderRightColor|borderBottomColor|borderLeftColor|Right|Bottom|stopAll|cos|stop|animate|Left|backgroundColor|textIndent|yellow|bottom|white|silver|red|letterSpacing|lineHeight|outlineOffset|outlineWidth|blue|minHeight|maxHeight|PI|match|dequeue|string|100000000|cssText|RegExp|toUpperCase|resize|sqrt|Fisheye|mousemove|mouseout|mouseover|selectorText|rules|dotted|dashed|transparent|isNaN|switch|solid|double|outset|borderStyle|inset|ridge|groove|purple|minWidth|darkred|203|204|153|darkorchid|233|150|fuchsia|gold|148|darkviolet|122|darkorange|85|darkcyan|darkgrey|darkblue|cyan|brown|darkgreen|100|darkmagenta|darkolivegreen|183|189|darkkhaki|215|darksalmon|olive|182|lightpink|lightgrey|orange|193||lightyellow|maroon|navy|magenta|green|lime|lightgreen|238|lightblue|khaki|lightcyan|173|130|indigo|pink|216'.split('|'),0,{})) diff --git a/desktop/js/jquery.js b/desktop/js/jquery.js new file mode 100644 index 0000000..19b21e7 --- /dev/null +++ b/desktop/js/jquery.js @@ -0,0 +1,12 @@ +/* + * jQuery 1.1.2 - New Wave Javascript + * + * Copyright (c) 2007 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2007-02-27 17:18:47 -0500 (Tue, 27 Feb 2007) $ + * $Rev: 1460 $ + */ + +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}('7(1D 1u.6=="R"){1u.R=1u.R;v 6=q(a,c){7(1u==l)u 1q 6(a,c);a=a||12;7(6.1p(a))u 1q 6(12)[6.D.27?"27":"2P"](a);7(1D a=="20"){v m=/^[^<]*(<(.|\\s)+>)[^>]*$/.2M(a);7(m)a=6.41([m[1]]);I u 1q 6(c).2p(a)}u l.6p(a.1l==2A&&a||(a.3W||a.H&&a!=1u&&!a.1W&&a[0]!=R&&a[0].1W)&&6.3N(a)||[a])};7(1D $!="R")6.30$=$;v $=6;6.D=6.8n={3W:"1.1.2",8K:q(){u l.H},H:0,2b:q(1P){u 1P==R?6.3N(l):l[1P]},2j:q(a){v K=6(a);K.6n=l;u K},6p:q(a){l.H=0;[].1m.15(l,a);u l},J:q(D,1A){u 6.J(l,D,1A)},2g:q(1b){v 4W=-1;l.J(q(i){7(l==1b)4W=i});u 4W},1G:q(23,O,B){v 1b=23;7(23.1l==3t)7(O==R)u l.H&&6[B||"1G"](l[0],23)||R;I{1b={};1b[23]=O}u l.J(q(2g){P(v G 1B 1b)6.1G(B?l.1o:l,G,6.G(l,1b[G],B,2g,G))})},1n:q(23,O){u l.1G(23,O,"3g")},2I:q(e){7(1D e=="20")u l.3u().3s(12.8q(e));v t="";6.J(e||l,q(){6.J(l.3b,q(){7(l.1W!=8)t+=l.1W!=1?l.6u:6.D.2I([l])})});u t},2H:q(){v a=6.41(1v);u l.J(q(){v b=a[0].3j(U);l.Y.2T(b,l);1Y(b.1a)b=b.1a;b.4A(l)})},3s:q(){u l.3d(1v,U,1,q(a){l.4A(a)})},5j:q(){u l.3d(1v,U,-1,q(a){l.2T(a,l.1a)})},5k:q(){u l.3d(1v,11,1,q(a){l.Y.2T(a,l)})},5u:q(){u l.3d(1v,11,-1,q(a){l.Y.2T(a,l.2c)})},4f:q(){u l.6n||6([])},2p:q(t){u l.2j(6.2Y(l,q(a){u 6.2p(t,a)}),t)},4Y:q(4M){u l.2j(6.2Y(l,q(a){v a=a.3j(4M!=R?4M:U);a.$1E=14;u a}))},1C:q(t){u l.2j(6.1p(t)&&6.2n(l,q(2u,2g){u t.15(2u,[2g])})||6.3z(t,l))},2e:q(t){u l.2j(t.1l==3t&&6.3z(t,l,U)||6.2n(l,q(a){u(t.1l==2A||t.3W)?6.3y(a,t)<0:a!=t}))},1K:q(t){u l.2j(6.2m(l.2b(),t.1l==3t?6(t).2b():t.H!=R&&(!t.1e||t.1e=="8s")?t:[t]))},4k:q(1s){u 1s?6.1C(1s,l).r.H>0:11},19:q(19){u 19==R?(l.H?l[0].O:14):l.1G("O",19)},4T:q(19){u 19==R?(l.H?l[0].2G:14):l.3u().3s(19)},3d:q(1A,1N,3Y,D){v 4Y=l.H>1;v a=6.41(1A);7(3Y<0)a.8t();u l.J(q(){v 1b=l;7(1N&&6.1e(l,"1N")&&6.1e(a[0],"3m"))1b=l.5K("1X")[0]||l.4A(12.56("1X"));6.J(a,q(){D.15(1b,[4Y?l.3j(U):l])})})}};6.1w=6.D.1w=q(){v 1T=1v[0],a=1;7(1v.H==1){1T=l;a=0}v G;1Y(G=1v[a++])P(v i 1B G)1T[i]=G[i];u 1T};6.1w({8u:q(){7(6.30$)$=6.30$;u 6},1p:q(D){u!!D&&1D D!="20"&&!D.1e&&1D D[0]=="R"&&/q/i.1j(D+"")},4C:q(C){u C.60&&C.5J&&!C.5J.63},1e:q(C,W){u C.1e&&C.1e.3K()==W.3K()},J:q(1b,D,1A){7(1b.H==R)P(v i 1B 1b)D.15(1b[i],1A||[i,1b[i]]);I P(v i=0,6q=1b.H;i<6q;i++)7(D.15(1b[i],1A||[i,1b[i]])===11)3M;u 1b},G:q(C,O,B,2g,G){7(6.1p(O))O=O.3n(C,[2g]);v 5G=/z-?2g|8x-?8y|1c|58|8z-?26/i;u O&&O.1l==3J&&B=="3g"&&!5G.1j(G)?O+"4R":O},18:{1K:q(C,c){6.J(c.3o(/\\s+/),q(i,N){7(!6.18.2Q(C.18,N))C.18+=(C.18?" ":"")+N})},2d:q(C,c){C.18=c?6.2n(C.18.3o(/\\s+/),q(N){u!6.18.2Q(c,N)}).6r(" "):""},2Q:q(t,c){t=t.18||t;c=c.1S(/([\\.\\\\\\+\\*\\?\\[\\^\\]\\$\\(\\)\\{\\}\\=\\!\\<\\>\\|\\:])/g,"\\\\$1");u t&&1q 4u("(^|\\\\s)"+c+"(\\\\s|$)").1j(t)}},4c:q(e,o,f){P(v i 1B o){e.1o["1L"+i]=e.1o[i];e.1o[i]=o[i]}f.15(e,[]);P(v i 1B o)e.1o[i]=e.1o["1L"+i]},1n:q(e,p){7(p=="26"||p=="3Q"){v 1L={},44,3I,d=["8A","8B","8C","8D"];6.J(d,q(){1L["8E"+l]=0;1L["8F"+l+"8G"]=0});6.4c(e,1L,q(){7(6.1n(e,"1h")!="1V"){44=e.8H;3I=e.8I}I{e=6(e.3j(U)).2p(":4i").5m("2W").4f().1n({4m:"1F",3k:"7H",1h:"2z",8L:"0",6w:"0"}).5A(e.Y)[0];v 3i=6.1n(e.Y,"3k");7(3i==""||3i=="4a")e.Y.1o.3k="6x";44=e.6y;3I=e.6z;7(3i==""||3i=="4a")e.Y.1o.3k="4a";e.Y.36(e)}});u p=="26"?44:3I}u 6.3g(e,p)},3g:q(C,G,53){v K;7(G=="1c"&&6.V.1g)u 6.1G(C.1o,"1c");7(G=="4g"||G=="2s")G=6.V.1g?"3X":"2s";7(!53&&C.1o[G])K=C.1o[G];I 7(12.42&&12.42.4V){7(G=="2s"||G=="3X")G="4g";G=G.1S(/([A-Z])/g,"-$1").4l();v N=12.42.4V(C,14);7(N)K=N.54(G);I 7(G=="1h")K="1V";I 6.4c(C,{1h:"2z"},q(){v c=12.42.4V(l,"");K=c&&c.54(G)||""})}I 7(C.4e){v 55=G.1S(/\\-(\\w)/g,q(m,c){u c.3K()});K=C.4e[G]||C.4e[55]}u K},41:q(a){v r=[];6.J(a,q(i,1z){7(!1z)u;7(1z.1l==3J)1z=1z.6C();7(1D 1z=="20"){v s=6.32(1z),22=12.56("22"),2h=[];v 2H=!s.17("<1t")&&[1,"<40>",""]||(!s.17("<6D")||!s.17("<1X")||!s.17("<6E"))&&[1,"<1N>",""]||!s.17("<3m")&&[2,"<1N><1X>",""]||(!s.17("<6F")||!s.17("<6G"))&&[3,"<1N><1X><3m>",""]||[0,"",""];22.2G=2H[1]+s+2H[2];1Y(2H[0]--)22=22.1a;7(6.V.1g){7(!s.17("<1N")&&s.17("<1X")<0)2h=22.1a&&22.1a.3b;I 7(2H[1]=="<1N>"&&s.17("<1X")<0)2h=22.3b;P(v n=2h.H-1;n>=0;--n)7(6.1e(2h[n],"1X")&&!2h[n].3b.H)2h[n].Y.36(2h[n])}1z=22.3b}7(1z.H===0&&!6.1e(1z,"3w"))u;7(1z[0]==R||6.1e(1z,"3w"))r.1m(1z);I r=6.2m(r,1z)});u r},1G:q(C,W,O){v 2l=6.4C(C)?{}:{"P":"6J","6L":"18","4g":6.V.1g?"3X":"2s",2s:6.V.1g?"3X":"2s",2G:"2G",18:"18",O:"O",2U:"2U",2W:"2W",88:"6N",2R:"2R"};7(W=="1c"&&6.V.1g&&O!=R){C.58=1;u C.1C=C.1C.1S(/4h\\([^\\)]*\\)/6O,"")+(O==1?"":"4h(1c="+O*6c+")")}I 7(W=="1c"&&6.V.1g)u C.1C?4S(C.1C.6P(/4h\\(1c=(.*)\\)/)[1])/6c:1;7(W=="1c"&&6.V.3h&&O==1)O=0.6R;7(2l[W]){7(O!=R)C[2l[W]]=O;u C[2l[W]]}I 7(O==R&&6.V.1g&&6.1e(C,"3w")&&(W=="80"||W=="7Z"))u C.6T(W).6u;I 7(C.60){7(O!=R)C.6V(W,O);7(6.V.1g&&/5E|3f/.1j(W)&&!6.4C(C))u C.33(W,2);u C.33(W)}I{W=W.1S(/-([a-z])/6W,q(z,b){u b.3K()});7(O!=R)C[W]=O;u C[W]}},32:q(t){u t.1S(/^\\s+|\\s+$/g,"")},3N:q(a){v r=[];7(a.1l!=2A)P(v i=0,2L=a.H;i<2L;i++)r.1m(a[i]);I r=a.3O(0);u r},3y:q(b,a){P(v i=0,2L=a.H;i<2L;i++)7(a[i]==b)u i;u-1},2m:q(2r,3P){v r=[].3O.3n(2r,0);P(v i=0,5c=3P.H;i<5c;i++)7(6.3y(3P[i],r)==-1)2r.1m(3P[i]);u 2r},2n:q(1O,D,4j){7(1D D=="20")D=1q 4v("a","i","u "+D);v 1f=[];P(v i=0,2u=1O.H;i<2u;i++)7(!4j&&D(1O[i],i)||4j&&!D(1O[i],i))1f.1m(1O[i]);u 1f},2Y:q(1O,D){7(1D D=="20")D=1q 4v("a","u "+D);v 1f=[],r=[];P(v i=0,2u=1O.H;i<2u;i++){v 19=D(1O[i],i);7(19!==14&&19!=R){7(19.1l!=2A)19=[19];1f=1f.6Z(19)}}v r=1f.H?[1f[0]]:[];5g:P(v i=1,5f=1f.H;i<5f;i++){P(v j=0;jm[3]-0",29:"m[3]-0==i",5r:"m[3]-0==i",2r:"i==0",2X:"i==r.H-1",5S:"i%2==0",5T:"i%2","29-3r":"6.29(a.Y.1a,m[3],\'2c\',a)==a","2r-3r":"6.29(a.Y.1a,1,\'2c\')==a","2X-3r":"6.29(a.Y.7m,1,\'5t\')==a","7o-3r":"6.2w(a.Y.1a).H==1",5v:"a.1a",3u:"!a.1a",5w:"6.D.2I.15([a]).17(m[3])>=0",38:\'a.B!="1F"&&6.1n(a,"1h")!="1V"&&6.1n(a,"4m")!="1F"\',1F:\'a.B=="1F"||6.1n(a,"1h")=="1V"||6.1n(a,"4m")=="1F"\',7u:"!a.2U",2U:"a.2U",2W:"a.2W",2R:"a.2R||6.1G(a,\'2R\')",2I:"a.B==\'2I\'",4i:"a.B==\'4i\'",5y:"a.B==\'5y\'",4F:"a.B==\'4F\'",5z:"a.B==\'5z\'",4Q:"a.B==\'4Q\'",5a:"a.B==\'5a\'",5B:"a.B==\'5B\'",3x:\'a.B=="3x"||6.1e(a,"3x")\',5C:"/5C|40|7z|3x/i.1j(a.1e)"},".":"6.18.2Q(a,m[2])","@":{"=":"z==m[4]","!=":"z!=m[4]","^=":"z&&!z.17(m[4])","$=":"z&&z.2S(z.H - m[4].H,m[4].H)==m[4]","*=":"z&&z.17(m[4])>=0","":"z",4t:q(m){u["",m[1],m[3],m[2],m[5]]},5Q:"z=a[m[3]];7(!z||/5E|3f/.1j(m[3]))z=6.1G(a,m[3]);"},"[":"6.2p(m[2],a).H"},5N:[/^\\[ *(@)([a-2o-3C-]*) *([!*$^=]*) *(\'?"?)(.*?)\\4 *\\]/i,/^(\\[)\\s*(.*?(\\[.*?\\])?[^[]*?)\\s*\\]/,/^(:)([a-2o-3C-]*)\\("?\'?(.*?(\\(.*?\\))?[^(]*?)"?\'?\\)/i,/^([:.#]*)([a-2o-3C*-]*)/i],1R:[/^(\\/?\\.\\.)/,"a.Y",/^(>|\\/)/,"6.2w(a.1a)",/^(\\+)/,"6.29(a,2,\'2c\')",/^(~)/,q(a){v s=6.2w(a.Y.1a);u s.3O(6.3y(a,s)+1)}],3z:q(1s,1O,2e){v 1L,N=[];1Y(1s&&1s!=1L){1L=1s;v f=6.1C(1s,1O,2e);1s=f.t.1S(/^\\s*,\\s*/,"");N=2e?1O=f.r:6.2m(N,f.r)}u N},2p:q(t,1y){7(1D t!="20")u[t];7(1y&&!1y.1W)1y=14;1y=1y||12;7(!t.17("//")){1y=1y.4G;t=t.2S(2,t.H)}I 7(!t.17("/")){1y=1y.4G;t=t.2S(1,t.H);7(t.17("/")>=1)t=t.2S(t.17("/"),t.H)}v K=[1y],2a=[],2X=14;1Y(t&&2X!=t){v r=[];2X=t;t=6.32(t).1S(/^\\/\\//i,"");v 3B=11;v 1H=/^[\\/>]\\s*([a-2o-9*-]+)/i;v m=1H.2M(t);7(m){6.J(K,q(){P(v c=l.1a;c;c=c.2c)7(c.1W==1&&(6.1e(c,m[1])||m[1]=="*"))r.1m(c)});K=r;t=t.1S(1H,"");7(t.17(" ")==0)5F;3B=U}I{P(v i=0;i<6.1R.H;i+=2){v 1H=6.1R[i];v m=1H.2M(t);7(m){r=K=6.2Y(K,6.1p(6.1R[i+1])?6.1R[i+1]:q(a){u 3l(6.1R[i+1])});t=6.32(t.1S(1H,""));3B=U;3M}}}7(t&&!3B){7(!t.17(",")){7(K[0]==1y)K.4K();6.2m(2a,K);r=K=[1y];t=" "+t.2S(1,t.H)}I{v 31=/^([a-2o-3C-]+)(#)([a-2o-9\\\\*30-]*)/i;v m=31.2M(t);7(m){m=[0,m[2],m[3],m[1]]}I{31=/^([#.]?)([a-2o-9\\\\*30-]*)/i;m=31.2M(t)}7(m[1]=="#"&&K[K.H-1].4X){v 2i=K[K.H-1].4X(m[2]);7(6.V.1g&&2i&&2i.2D!=m[2])2i=6(\'[@2D="\'+m[2]+\'"]\',K[K.H-1])[0];K=r=2i&&(!m[3]||6.1e(2i,m[3]))?[2i]:[]}I{7(m[1]==".")v 4q=1q 4u("(^|\\\\s)"+m[2]+"(\\\\s|$)");6.J(K,q(){v 3E=m[1]!=""||m[0]==""?"*":m[2];7(6.1e(l,"7I")&&3E=="*")3E="3c";6.2m(r,m[1]!=""&&K.H!=1?6.4w(l,[],m[1],m[2],4q):l.5K(3E))});7(m[1]=="."&&K.H==1)r=6.2n(r,q(e){u 4q.1j(e.18)});7(m[1]=="#"&&K.H==1){v 5L=r;r=[];6.J(5L,q(){7(l.33("2D")==m[2]){r=[l];u 11}})}K=r}t=t.1S(31,"")}}7(t){v 19=6.1C(t,r);K=r=19.r;t=6.32(19.t)}}7(K&&K[0]==1y)K.4K();6.2m(2a,K);u 2a},1C:q(t,r,2e){1Y(t&&/^[a-z[({<*:.#]/i.1j(t)){v p=6.5N,m;6.J(p,q(i,1H){m=1H.2M(t);7(m){t=t.7L(m[0].H);7(6.1s[m[1]].4t)m=6.1s[m[1]].4t(m);u 11}});7(m[1]==":"&&m[2]=="2e")r=6.1C(m[3],r,U).r;I 7(m[1]=="."){v 1H=1q 4u("(^|\\\\s)"+m[2]+"(\\\\s|$)");r=6.2n(r,q(e){u 1H.1j(e.18||"")},2e)}I{v f=6.1s[m[1]];7(1D f!="20")f=6.1s[m[1]][m[2]];3l("f = q(a,i){"+(6.1s[m[1]].5Q||"")+"u "+f+"}");r=6.2n(r,f,2e)}}u{r:r,t:t}},4w:q(o,r,1R,W,1H){P(v s=o.1a;s;s=s.2c)7(s.1W==1){v 1K=U;7(1R==".")1K=s.18&&1H.1j(s.18);I 7(1R=="#")1K=s.33("2D")==W;7(1K)r.1m(s);7(1R=="#"&&r.H)3M;7(s.1a)6.4w(s,r,1R,W,1H)}u r},4y:q(C){v 4z=[];v N=C.Y;1Y(N&&N!=12){4z.1m(N);N=N.Y}u 4z},29:q(N,1f,3Y,C){1f=1f||1;v 1P=0;P(;N;N=N[3Y]){7(N.1W==1)1P++;7(1P==1f||1f=="5S"&&1P%2==0&&1P>1&&N==C||1f=="5T"&&1P%2==1&&N==C)u N}},2w:q(n,C){v r=[];P(;n;n=n.2c){7(n.1W==1&&(!C||n!=C))r.1m(n)}u r}});6.F={1K:q(S,B,1k,E){7(6.V.1g&&S.45!=R)S=1u;7(E)1k.E=E;7(!1k.2y)1k.2y=l.2y++;7(!S.$1E)S.$1E={};v 34=S.$1E[B];7(!34){34=S.$1E[B]={};7(S["35"+B])34[0]=S["35"+B]}34[1k.2y]=1k;S["35"+B]=l.5Y;7(!l.1i[B])l.1i[B]=[];l.1i[B].1m(S)},2y:1,1i:{},2d:q(S,B,1k){7(S.$1E){v i,j,k;7(B&&B.B){1k=B.1k;B=B.B}7(B&&S.$1E[B])7(1k)5V S.$1E[B][1k.2y];I P(i 1B S.$1E[B])5V S.$1E[B][i];I P(j 1B S.$1E)l.2d(S,j);P(k 1B S.$1E[B])7(k){k=U;3M}7(!k)S["35"+B]=14}},1Q:q(B,E,S){E=6.3N(E||[]);7(!S)6.J(l.1i[B]||[],q(){6.F.1Q(B,E,l)});I{v 1k=S["35"+B],19,D=6.1p(S[B]);7(1k){E.61(l.2l({B:B,1T:S}));7((19=1k.15(S,E))!==11)l.4E=U}7(D&&19!==11)S[B]();l.4E=11}},5Y:q(F){7(1D 6=="R"||6.F.4E)u;F=6.F.2l(F||1u.F||{});v 3R;v c=l.$1E[F.B];v 1A=[].3O.3n(1v,1);1A.61(F);P(v j 1B c){1A[0].1k=c[j];1A[0].E=c[j].E;7(c[j].15(l,1A)===11){F.2q();F.2E();3R=11}}7(6.V.1g)F.1T=F.2q=F.2E=F.1k=F.E=14;u 3R},2l:q(F){7(!F.1T&&F.62)F.1T=F.62;7(F.64==R&&F.66!=R){v e=12.4G,b=12.63;F.64=F.66+(e.67||b.67);F.7W=F.7X+(e.6b||b.6b)}7(6.V.2F&&F.1T.1W==3){v 2V=F;F=6.1w({},2V);F.1T=2V.1T.Y;F.2q=q(){u 2V.2q()};F.2E=q(){u 2V.2E()}}7(!F.2q)F.2q=q(){l.3R=11};7(!F.2E)F.2E=q(){l.7Y=U};u F}};6.D.1w({3T:q(B,E,D){u l.J(q(){6.F.1K(l,B,D||E,E)})},6s:q(B,E,D){u l.J(q(){6.F.1K(l,B,q(F){6(l).6f(F);u(D||E).15(l,1v)},E)})},6f:q(B,D){u l.J(q(){6.F.2d(l,B,D)})},1Q:q(B,E){u l.J(q(){6.F.1Q(B,E,l)})},3V:q(){v a=1v;u l.6i(q(e){l.4L=l.4L==0?1:0;e.2q();u a[l.4L].15(l,[e])||11})},81:q(f,g){q 4N(e){v p=(e.B=="3Z"?e.82:e.83)||e.84;1Y(p&&p!=l)2B{p=p.Y}2J(e){p=l};7(p==l)u 11;u(e.B=="3Z"?f:g).15(l,[e])}u l.3Z(4N).6j(4N)},27:q(f){7(6.3U)f.15(12,[6]);I{6.3a.1m(q(){u f.15(l,[6])})}u l}});6.1w({3U:11,3a:[],27:q(){7(!6.3U){6.3U=U;7(6.3a){6.J(6.3a,q(){l.15(12)});6.3a=14}7(6.V.3h||6.V.3e)12.85("6m",6.27,11)}}});1q q(){6.J(("86,87,2P,89,8b,51,6i,8c,"+"8d,8e,8f,3Z,6j,8h,40,"+"4Q,8i,8j,8k,2x").3o(","),q(i,o){6.D[o]=q(f){u f?l.3T(o,f):l.1Q(o)}});7(6.V.3h||6.V.3e)12.8l("6m",6.27,11);I 7(6.V.1g){12.8m("<8o"+"8p 2D=6o 8r=U "+"3f=//:><\\/2f>");v 2f=12.4X("6o");7(2f)2f.37=q(){7(l.3D!="1Z")u;l.Y.36(l);6.27()};2f=14}I 7(6.V.2F)6.4Z=45(q(){7(12.3D=="8v"||12.3D=="1Z"){4o(6.4Z);6.4Z=14;6.27()}},10);6.F.1K(1u,"2P",6.27)};7(6.V.1g)6(1u).6s("51",q(){v 1i=6.F.1i;P(v B 1B 1i){v 49=1i[B],i=49.H;7(i&&B!=\'51\')6v 6.F.2d(49[i-1],B);1Y(--i)}});6.D.1w({6A:q(T,21,L){l.2P(T,21,L,1)},2P:q(T,21,L,1U){7(6.1p(T))u l.3T("2P",T);L=L||q(){};v B="5e";7(21)7(6.1p(21)){L=21;21=14}I{21=6.3c(21);B="65"}v 4d=l;6.3v({T:T,B:B,E:21,1U:1U,1Z:q(2K,16){7(16=="2O"||!1U&&16=="5M")4d.1G("2G",2K.3G).4U().J(L,[2K.3G,16,2K]);I L.15(4d,[2K.3G,16,2K])}});u l},6B:q(){u 6.3c(l)},4U:q(){u l.2p("2f").J(q(){7(l.3f)6.59(l.3f);I 6.50(l.2I||l.6H||l.2G||"")}).4f()}});7(!1u.3p)3p=q(){u 1q 6I("6K.6M")};6.J("5n,5R,5P,5W,5O,5I".3o(","),q(i,o){6.D[o]=q(f){u l.3T(o,f)}});6.1w({2b:q(T,E,L,B,1U){7(6.1p(E)){L=E;E=14}u 6.3v({T:T,E:E,2O:L,4s:B,1U:1U})},6Q:q(T,E,L,B){u 6.2b(T,E,L,B,1)},59:q(T,L){u 6.2b(T,14,L,"2f")},6S:q(T,E,L){u 6.2b(T,E,L,"6l")},6U:q(T,E,L,B){7(6.1p(E)){L=E;E={}}u 6.3v({B:"65",T:T,E:E,2O:L,4s:B})},6X:q(28){6.3q.28=28},6Y:q(5d){6.1w(6.3q,5d)},3q:{1i:U,B:"5e",28:0,5s:"70/x-73-3w-77",5i:U,48:U,E:14},3S:{},3v:q(s){s=6.1w({},6.3q,s);7(s.E){7(s.5i&&1D s.E!="20")s.E=6.3c(s.E);7(s.B.4l()=="2b"){s.T+=((s.T.17("?")>-1)?"&":"?")+s.E;s.E=14}}7(s.1i&&!6.4D++)6.F.1Q("5n");v 4x=11;v M=1q 3p();M.7i(s.B,s.T,s.48);7(s.E)M.3A("7k-7l",s.5s);7(s.1U)M.3A("7n-4J-7p",6.3S[s.T]||"7r, 7s 7v 7w 4n:4n:4n 7y");M.3A("X-7A-7B","3p");7(M.7D)M.3A("7E","7F");7(s.5H)s.5H(M);7(s.1i)6.F.1Q("5I",[M,s]);v 37=q(4r){7(M&&(M.3D==4||4r=="28")){4x=U;7(3H){4o(3H);3H=14}v 16;2B{16=6.5Z(M)&&4r!="28"?s.1U&&6.68(M,s.T)?"5M":"2O":"2x";7(16!="2x"){v 3F;2B{3F=M.4O("6a-4J")}2J(e){}7(s.1U&&3F)6.3S[s.T]=3F;v E=6.6h(M,s.4s);7(s.2O)s.2O(E,16);7(s.1i)6.F.1Q("5O",[M,s])}I 6.3L(s,M,16)}2J(e){16="2x";6.3L(s,M,16,e)}7(s.1i)6.F.1Q("5P",[M,s]);7(s.1i&&!--6.4D)6.F.1Q("5R");7(s.1Z)s.1Z(M,16);7(s.48)M=14}};v 3H=45(37,13);7(s.28>0)57(q(){7(M){M.7M();7(!4x)37("28")}},s.28);2B{M.7O(s.E)}2J(e){6.3L(s,M,14,e)}7(!s.48)37();u M},3L:q(s,M,16,e){7(s.2x)s.2x(M,16,e);7(s.1i)6.F.1Q("5W",[M,s,e])},4D:0,5Z:q(r){2B{u!r.16&&7T.7U=="4F:"||(r.16>=5X&&r.16<7V)||r.16==6d||6.V.2F&&r.16==R}2J(e){}u 11},68:q(M,T){2B{v 6e=M.4O("6a-4J");u M.16==6d||6e==6.3S[T]||6.V.2F&&M.16==R}2J(e){}u 11},6h:q(r,B){v 4P=r.4O("8a-B");v E=!B&&4P&&4P.17("M")>=0;E=B=="M"||E?r.8g:r.3G;7(B=="2f")6.50(E);7(B=="6l")3l("E = "+E);7(B=="4T")6("<22>").4T(E).4U();u E},3c:q(a){v s=[];7(a.1l==2A||a.3W)6.J(a,q(){s.1m(2N(l.W)+"="+2N(l.O))});I P(v j 1B a)7(a[j]&&a[j].1l==2A)6.J(a[j],q(){s.1m(2N(j)+"="+2N(l))});I s.1m(2N(j)+"="+2N(a[j]));u s.6r("&")},50:q(E){7(1u.52)1u.52(E);I 7(6.V.2F)1u.57(E,0);I 3l.3n(1u,E)}});6.D.1w({1M:q(Q,L){v 1F=l.1C(":1F");Q?1F.25({26:"1M",3Q:"1M",1c:"1M"},Q,L):1F.J(q(){l.1o.1h=l.2C?l.2C:"";7(6.1n(l,"1h")=="1V")l.1o.1h="2z"});u l},1I:q(Q,L){v 38=l.1C(":38");Q?38.25({26:"1I",3Q:"1I",1c:"1I"},Q,L):38.J(q(){l.2C=l.2C||6.1n(l,"1h");7(l.2C=="1V")l.2C="2z";l.1o.1h="1V"});u l},5h:6.D.3V,3V:q(D,4H){v 1A=1v;u 6.1p(D)&&6.1p(4H)?l.5h(D,4H):l.J(q(){6(l)[6(l).4k(":1F")?"1M":"1I"].15(6(l),1A)})},7a:q(Q,L){u l.25({26:"1M"},Q,L)},7c:q(Q,L){u l.25({26:"1I"},Q,L)},7e:q(Q,L){u l.J(q(){v 5l=6(l).4k(":1F")?"1M":"1I";6(l).25({26:5l},Q,L)})},7q:q(Q,L){u l.25({1c:"1M"},Q,L)},7t:q(Q,L){u l.25({1c:"1I"},Q,L)},7x:q(Q,43,L){u l.25({1c:43},Q,L)},25:q(G,Q,1r,L){u l.1J(q(){l.2t=6.1w({},G);v 1t=6.Q(Q,1r,L);P(v p 1B G){v e=1q 6.39(l,1t,p);7(G[p].1l==3J)e.2v(e.N(),G[p]);I e[G[p]](G)}})},1J:q(B,D){7(!D){D=B;B="39"}u l.J(q(){7(!l.1J)l.1J={};7(!l.1J[B])l.1J[B]=[];l.1J[B].1m(D);7(l.1J[B].H==1)D.15(l)})}});6.1w({Q:q(Q,1r,D){v 1t=Q&&Q.1l==7J?Q:{1Z:D||!D&&1r||6.1p(Q)&&Q,24:Q,1r:D&&1r||1r&&1r.1l!=4v&&1r};1t.24=(1t.24&&1t.24.1l==3J?1t.24:{7P:7Q,7R:5X}[1t.24])||7S;1t.1L=1t.1Z;1t.1Z=q(){6.69(l,"39");7(6.1p(1t.1L))1t.1L.15(l)};u 1t},1r:{},1J:{},69:q(C,B){B=B||"39";7(C.1J&&C.1J[B]){C.1J[B].4K();v f=C.1J[B][0];7(f)f.15(C)}},39:q(C,1d,G){v z=l;v y=C.1o;v 4B=6.1n(C,"1h");y.5U="1F";z.a=q(){7(1d.47)1d.47.15(C,[z.2k]);7(G=="1c")6.1G(y,"1c",z.2k);I 7(6k(z.2k))y[G]=6k(z.2k)+"4R";y.1h="2z"};z.6t=q(){u 4S(6.1n(C,G))};z.N=q(){v r=4S(6.3g(C,G));u r&&r>-8w?r:z.6t()};z.2v=q(4b,43){z.4I=(1q 5p()).5x();z.2k=4b;z.a();z.4p=45(q(){z.47(4b,43)},13)};z.1M=q(){7(!C.1x)C.1x={};C.1x[G]=l.N();1d.1M=U;z.2v(0,C.1x[G]);7(G!="1c")y[G]="5b"};z.1I=q(){7(!C.1x)C.1x={};C.1x[G]=l.N();1d.1I=U;z.2v(C.1x[G],0)};z.3V=q(){7(!C.1x)C.1x={};C.1x[G]=l.N();7(4B=="1V"){1d.1M=U;7(G!="1c")y[G]="5b";z.2v(0,C.1x[G])}I{1d.1I=U;z.2v(C.1x[G],0)}};z.47=q(2Z,46){v t=(1q 5p()).5x();7(t>1d.24+z.4I){4o(z.4p);z.4p=14;z.2k=46;z.a();7(C.2t)C.2t[G]=U;v 2a=U;P(v i 1B C.2t)7(C.2t[i]!==U)2a=11;7(2a){y.5U="";y.1h=4B;7(6.1n(C,"1h")=="1V")y.1h="2z";7(1d.1I)y.1h="1V";7(1d.1I||1d.1M)P(v p 1B C.2t)7(p=="1c")6.1G(y,p,C.1x[p]);I y[p]=""}7(2a&&6.1p(1d.1Z))1d.1Z.15(C)}I{v n=t-l.4I;v p=n/1d.24;z.2k=1d.1r&&6.1r[1d.1r]?6.1r[1d.1r](p,n,2Z,(46-2Z),1d.24):((-6g.7N(p*6g.8J)/2)+0.5)*(46-2Z)+2Z;z.a()}}}})}',62,544,'||||||jQuery|if||||||||||||||this|||||function||||return|var||||||type|elem|fn|data|event|prop|length|else|each|ret|callback|xml|cur|value|for|speed|undefined|element|url|true|browser|name||parentNode|||false|document||null|apply|status|indexOf|className|val|firstChild|obj|opacity|options|nodeName|result|msie|display|global|test|handler|constructor|push|css|style|isFunction|new|easing|expr|opt|window|arguments|extend|orig|context|arg|args|in|filter|typeof|events|hidden|attr|re|hide|queue|add|old|show|table|elems|num|trigger|token|replace|target|ifModified|none|nodeType|tbody|while|complete|string|params|div|key|duration|animate|height|ready|timeout|nth|done|get|nextSibling|remove|not|script|index|tb|oid|pushStack|now|fix|merge|grep|z0|find|preventDefault|first|cssFloat|curAnim|el|custom|sibling|error|guid|block|Array|try|oldblock|id|stopPropagation|safari|innerHTML|wrap|text|catch|res|al|exec|encodeURIComponent|success|load|has|selected|substr|insertBefore|disabled|originalEvent|checked|last|map|firstNum|_|re2|trim|getAttribute|handlers|on|removeChild|onreadystatechange|visible|fx|readyList|childNodes|param|domManip|opera|src|curCSS|mozilla|parPos|cloneNode|position|eval|tr|call|split|XMLHttpRequest|ajaxSettings|child|append|String|empty|ajax|form|button|inArray|multiFilter|setRequestHeader|foundToken|9_|readyState|tag|modRes|responseText|ival|oWidth|Number|toUpperCase|handleError|break|makeArray|slice|second|width|returnValue|lastModified|bind|isReady|toggle|jquery|styleFloat|dir|mouseover|select|clean|defaultView|to|oHeight|setInterval|lastNum|step|async|els|static|from|swap|self|currentStyle|end|float|alpha|radio|inv|is|toLowerCase|visibility|00|clearInterval|timer|rec|isTimeout|dataType|_resort|RegExp|Function|getAll|requestDone|parents|matched|appendChild|oldDisplay|isXMLDoc|active|triggered|file|documentElement|fn2|startTime|Modified|shift|lastToggle|deep|handleHover|getResponseHeader|ct|submit|px|parseFloat|html|evalScripts|getComputedStyle|pos|getElementById|clone|safariTimer|globalEval|unload|execScript|force|getPropertyValue|newProp|createElement|setTimeout|zoom|getScript|image|1px|sl|settings|GET|rl|check|_toggle|processData|prepend|before|state|removeAttr|ajaxStart|lt|Date|gt|eq|contentType|previousSibling|after|parent|contains|getTime|checkbox|password|appendTo|reset|input|webkit|href|continue|exclude|beforeSend|ajaxSend|ownerDocument|getElementsByTagName|tmp|notmodified|parse|ajaxSuccess|ajaxComplete|_prefix|ajaxStop|even|odd|overflow|delete|ajaxError|200|handle|httpSuccess|tagName|unshift|srcElement|body|pageX|POST|clientX|scrollLeft|httpNotModified|dequeue|Last|scrollTop|100|304|xmlRes|unbind|Math|httpData|click|mouseout|parseInt|json|DOMContentLoaded|prevObject|__ie_init|setArray|ol|join|one|max|nodeValue|do|left|relative|clientHeight|clientWidth|loadIfModified|serialize|toString|thead|tfoot|td|th|textContent|ActiveXObject|htmlFor|Microsoft|class|XMLHTTP|readOnly|gi|match|getIfModified|9999|getJSON|getAttributeNode|post|setAttribute|ig|ajaxTimeout|ajaxSetup|concat|application|userAgent|compatible|www|compatMode|CSS1Compat|next|urlencoded|siblings|children|slideDown|prependTo|slideUp|insertAfter|slideToggle|removeAttribute|addClass|removeClass|open|toggleClass|Content|Type|lastChild|If|only|Since|fadeIn|Thu|01|fadeOut|enabled|Jan|1970|fadeTo|GMT|textarea|Requested|With|prev|overrideMimeType|Connection|close|boxModel|absolute|object|Object|navigator|substring|abort|cos|send|slow|600|fast|400|location|protocol|300|pageY|clientY|cancelBubble|method|action|hover|fromElement|toElement|relatedTarget|removeEventListener|blur|focus|readonly|resize|content|scroll|dblclick|mousedown|mouseup|mousemove|responseXML|change|keydown|keypress|keyup|addEventListener|write|prototype|scr|ipt|createTextNode|defer|FORM|reverse|noConflict|loaded|10000|font|weight|line|Top|Bottom|Right|Left|padding|border|Width|offsetHeight|offsetWidth|PI|size|right'.split('|'),0,{})) diff --git a/desktop/login.php b/desktop/login.php new file mode 100644 index 0000000..56ab384 --- /dev/null +++ b/desktop/login.php @@ -0,0 +1,108 @@ + 'PassWord', + 'UserName' => 'LogIn' +); +// request login? true - show login and password boxes, false - password box only +define('USE_USERNAME', true); +// User will be redirected to this page after logout +define('LOGOUT_URL', 'desktop.php'); +// time out after NN minutes of inactivity. Set to 0 to not timeout +define('TIMEOUT_MINUTES', 0); +// This parameter is only useful when TIMEOUT_MINUTES is not zero +// true - timeout time from last activity, false - timeout time from login +define('TIMEOUT_CHECK_ACTIVITY', true); +// show usage example +//if(isset($_GET['help'])) { +// die('Include following code into every page you would like to protect, at the very beginning (first line):
<?php include("' . str_replace('\\','\\\\',__FILE__) . '"); ?>'); +//} +// timeout in seconds +$timeout = (TIMEOUT_MINUTES == 0 ? 0 : time() + TIMEOUT_MINUTES * 60); +// logout? +if(isset($_GET['logout'])) { + setcookie("verify", '', $timeout, '/'); // clear password; + header('Location: ' . LOGOUT_URL); + exit(); +} +if(!function_exists('showLoginPasswordProtect')) { +// show login form +function showLoginPasswordProtect($error_msg) { +?> + + + +BrowzOS + + + + + + +
+
+

Please log into your user account

+
Password:
'; ?> +

+

+
+
+
+ + +$val) { + $lp = (USE_USERNAME ? $key : '') .'%'.$val; + if ($_COOKIE['verify'] == md5($lp)) { + $found = true; + // prolong timeout + if (TIMEOUT_CHECK_ACTIVITY) { + setcookie("verify", md5($lp), $timeout, '/'); + } + break; + } + } + if (!$found) { + showLoginPasswordProtect(""); + } +} +?> \ No newline at end of file diff --git a/desktop/spacer.png b/desktop/spacer.png new file mode 100644 index 0000000..bf867a3 Binary files /dev/null and b/desktop/spacer.png differ diff --git a/desktop/style.css b/desktop/style.css new file mode 100644 index 0000000..41e2db8 --- /dev/null +++ b/desktop/style.css @@ -0,0 +1,85 @@ +html { + overflow: auto; + overflow: hidden; +} + +body { + font: 11px Arial, Helvetica, sans-serif; + color: #000000; + background: #D3D3D3 url(bg/gradient.png); + background-repeat:repeat-x; + padding: 0; + margin: 0; +} + a:link {color:#000000;} + a:visited {color:#000000;} + a:hover {color:#000000;} + a:active {color:#000000;} +img { + border: none; +} + +/* dock - top */ +.dock { + position: relative; + height: 50px; + text-align: center; +} +.dock-container { + position: absolute; + height: 50px; + background: url(dock/dock-bg2.gif); + padding-left: 20px; +} +a.dock-item { + display: block; + width: 40px; + color: #000; + position: absolute; + top: 0px; + text-align: center; + text-decoration: none; + font: bold 12px Arial, Helvetica, sans-serif; +} +.dock-item img { + border: none; + margin: 5px 10px 0px; + width: 100%; +} +.dock-item span { + display: none; + padding-left: 20px; +} + +/* dock2 - bottom */ +#dock2 { + width: 100%; + bottom: 0px; + position: absolute; + left: 0px; +} +.dock-container2 { + position: absolute; + height: 50px; + background: url(dock/dock-bg.gif); + padding-left: 20px; +} +a.dock-item2 { + display: block; + font: bold 12px Arial, Helvetica, sans-serif; + width: 40px; + color: #000; + bottom: 0px; + position: absolute; + text-align: center; + text-decoration: none; +} +.dock-item2 span { + display: none; + padding-left: 20px; +} +.dock-item2 img { + border: none; + margin: 5px 10px 0px; + width: 100%; +} \ No newline at end of file diff --git a/forum/index.html b/forum/index.html new file mode 100644 index 0000000..6889a44 --- /dev/null +++ b/forum/index.html @@ -0,0 +1,19 @@ + + + +Forum Software + + + + + +

BrowzOS comes packaged with PunBB 1.3.4 as forum software. Please note that PunBB has to be installed via a specialised installation procedure.

+

In order to install PunBB properly on a website, extract the zip file's contents into this folder and log into http://www.example.com/forum/admin/install.php (where http://www.example.com is a place holder for the website's domain/subdomain name). From there, follow the proper installation procedure. Once that is done, edit the script "linkdock.js" (found in the "browzos_source/dock" directory of the download package) to omit this page (instructions for doing so can be found inside the script file).

+

PunBB is not as secure as other forum software packages unless CAPTCHA has been implemented to prevent spamming on the forum; fortunately, CAPTCHA is available through an updates repository from within the forum's administration utilities. Please note that the used CAPTCHA method is also open-source.

+

Even though PunBB has been included in this download package, this doesn't mean PunBB has to be used on a website; any forum software can be used.

\ No newline at end of file diff --git a/forum/punbb-1.3.4.zip b/forum/punbb-1.3.4.zip new file mode 100644 index 0000000..4cbbf86 Binary files /dev/null and b/forum/punbb-1.3.4.zip differ diff --git a/games/index.html b/games/index.html new file mode 100644 index 0000000..a667d13 --- /dev/null +++ b/games/index.html @@ -0,0 +1,28 @@ + + + +Games + + + + + +
Games Available:
+

Chess

+

Madness Interactive

+

Please note that only the above games (as presented in browzos.i-console.com) are open-source. As all the other games presented are available only as closed-source freeware, they have not been included in this package.

+

However, this does not mean only open-source software has to be used; feel free to use any type of applet, regardless of license.

+ + \ No newline at end of file diff --git a/games/jchess.html b/games/jchess.html new file mode 100644 index 0000000..bdb030a --- /dev/null +++ b/games/jchess.html @@ -0,0 +1,19 @@ + + + +Chess + + + + + +
+ +
+ + \ No newline at end of file diff --git a/games/jchess.js b/games/jchess.js new file mode 100644 index 0000000..c0930f2 --- /dev/null +++ b/games/jchess.js @@ -0,0 +1,62 @@ + \ No newline at end of file diff --git a/games/jchess.zip b/games/jchess.zip new file mode 100644 index 0000000..13cef22 Binary files /dev/null and b/games/jchess.zip differ diff --git a/games/madness.html b/games/madness.html new file mode 100644 index 0000000..a1efdf1 --- /dev/null +++ b/games/madness.html @@ -0,0 +1,21 @@ + + + +Madness Interactive + + + + + +
+ +
+ + \ No newline at end of file diff --git a/games/madness.swf b/games/madness.swf new file mode 100644 index 0000000..e4b42c1 Binary files /dev/null and b/games/madness.swf differ diff --git a/games/madness.zip b/games/madness.zip new file mode 100644 index 0000000..2432edc Binary files /dev/null and b/games/madness.zip differ diff --git a/games/mi_srcv2.zip b/games/mi_srcv2.zip new file mode 100644 index 0000000..994acac Binary files /dev/null and b/games/mi_srcv2.zip differ diff --git a/games/mrsound.swf b/games/mrsound.swf new file mode 100644 index 0000000..dd0f905 Binary files /dev/null and b/games/mrsound.swf differ diff --git a/help/credits.html b/help/credits.html new file mode 100644 index 0000000..a405b8c --- /dev/null +++ b/help/credits.html @@ -0,0 +1,44 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Credits, The Licensing Of BrowzOS And Included Applets

+

THIS WEBSITE IS IN THE PUBLIC DOMAIN!!!!!

+

Special thanks to Jamie Sanders and Team vNES of +Virtual NES for their +support of this project. Thanks to Dan Davis for initially hosting this project and special thanks to Ruben Baca of KryptonWare Solutions LLC for being its current host.

+

All applets and scripts in this website are either freely +distributed or open-source, following various different licenses that may or may +not be compatible with the GNU General Public License +or each other. The scripts responsible for the BrowzOS +Desktop and Dock are copyrighted by Brian Gosselin and Nick La, respectively. The user authenticaction system is copyrighted by Zubrag.com. The project's PNG image file rendering script is copyrighted by TwinHelix. The Clock's time and calendar components are copyrighted by both Brian Gosselin and Itamar Arjuan. Madness Interactive is copyrighted by Linger +Media Company. The scientific calculator is copyrighted by Eni +Generalić of The Faculty Of Chemistry And Technology, Split, Croatia. The command line is copyrighted by Evgeny Stephanischev. The word processor is copyrighted by Frederico Caldeira Knabben. The spreadsheet is copyrighted by +Simple Groupware. The media player is copyrighted by +Jeroen +Wijering. The instant messenger is copyrighted by +phpFreeChat.

+

The page sources for this website are freely available and accessible under the terms of the GNU Affero General Public License. The documentation for this project is available under the GNU Free Documentation License.

+


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help/index.html b/help/index.html new file mode 100644 index 0000000..e20d36e --- /dev/null +++ b/help/index.html @@ -0,0 +1,43 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Introducing BrowzOS, an open-source project to create a full-fledged, online +operating system (well, a sort of simulated operating system); the goal of this +project is to give BrowzOS the ability to manipulate files & folders on a client +and/or server computer's hard drive from within a web browser instead of using the +client's currently installed programs, as well as to encourage programmers to create +online applets (be they in any scripting language, Flash, Shockwave, Java, etc.) +with the ability to function as such. In spite of this, anyone is free to use +BrowzOS for any purpose, including the creation of applets that do NOT have +client (or server) computer file access.

+


+

Contents

+ + + + + + + + + \ No newline at end of file diff --git a/help/inst.html b/help/inst.html new file mode 100644 index 0000000..b919bcb --- /dev/null +++ b/help/inst.html @@ -0,0 +1,44 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Downloading And Installing BrowzOS

+

As mentioned previously, BrowzOS is an open-source project; this +means all the HTML pages and applets contained in this package can be +downloaded and modified to the designer's content (so long as all credits and +ownership of copyrights are acknowledged and retained as per the GNU Affero GPL).

+

There are only 2 simple steps for the average computer user:

+
  1. Download the source package (available in many different archive file formats)
  2. +
  3. Extract the package to any folder on the hard drive and make any necessary changes to the + scripts, web pages and applets in the package (this would also include adding + and removing various applets and scripts as needed)
+

There are 3 steps for web designers to get started:

+
  1. Download the source package (available in many different archive file formats)
  2. +
  3. Extract the package and make any necessary changes to the + scripts, web pages and applets in the package (this would also include adding + & removing various applets and scripts as needed)
  4. +
  5. Upload the files to any directory on the server in use
+

Once the package has been downloaded and extracted, edit the web pages as needed. This includes adding/removing scripts and applets as needed, editing scripts as needed, changing the file names of the HTML pages as needed and editing the contents of the HTML pages as needed (i.e.: adding/removing links, icons, etc.) If BrowzOS is to be uploaded to a web server, do so once all the desired changes have been completed.

+


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help/intro.html b/help/intro.html new file mode 100644 index 0000000..b300e17 --- /dev/null +++ b/help/intro.html @@ -0,0 +1,77 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Introduction And Overview

+

Welcome to BrowzOS!

+

BrowzOS is an online project whose chief goal is to address an +encourage the creativity of aspiring web designers and applet +programmers. Namely, the goal of BrowzOS is to give web designers and applet +programmers a solid base for their web-related projects; a simulated operating +system in which programmers and designers can create, display and run any web +applet of any kind, using any programming language and any scripting language. +Due to the fact that this project is a simulated operating system, BrowzOS +especially encourages Java programmers (from novices to professionals) to create +applets whose functionality extends across both the web server's and the client +computer's file system.

+

With the ever-evolving functionality and +popularity of the internet, this project presents a great opportunity that +anyone can take advantage of; this project is OPEN-SOURCE AND IN THE PUBLIC +DOMAIN. This means a number of things:

+
  • For hobbyist web designers, BrowzOS can be +freely downloaded, modified and used for such things as presenting +résumés, pictures, videos and music through the free/open-source +applets included in the main package. Setting up the site is simple and is +merely a matter of time to fully implement.
  • +
  • For novice/professional web designers and +programmers, this project can not only be used as a testing facility for other +open-source applet projects, but can also present the resulting creations in a +manner that familiarly resembles the look and feel of a mainstream operating +system (such as GNU/Linux, Mac OS X and Windows). Since the simulated environment +is completely user-customisable, multiple applets can be created, displayed and +executed from within the web browser in a matter of minutes. BrowzOS can even be +customised for use as a free/charitable online service to ease various +education/business-related undertakings in which collaboration is a necessity.
  • +
  • For educational institutions, non-governmental +organisations, governments of developing nations and charitable organisations, +this project can be used as a collaboration tool, easing any undertaking in +which project planning, research, documentation and constant communication +between team members is a must.
  • +
  • For the average computer user, this project +(and the included/resulting applets) can be used in hopes of providing a better +computing environment. With such applets whose functions fully replicate word +processors, music players and various other items that would otherwise be found +on an ordinary desktop computer's operating system, BrowzOS will reduce (or +hopefully, eliminate) the user's need to search for and install trial software, +bloatware, pirated programs and programs which are potentially malicious to both +the computer and its user.
  • +
+

Currently, this project comes packaged with a +word processor, scientific calculator, instant messenger, media player, clock and a tidy user interface, reminiscent of both Mac OS X +and GNU/Linux. Hopefully, this project will inspire others to contribute, be it +for fun or for providing a much needed public service, as intended by the +concept of truly free and open-source software.

+

Happy computing!

+


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help/jarsigner.html b/help/jarsigner.html new file mode 100644 index 0000000..9e953a6 --- /dev/null +++ b/help/jarsigner.html @@ -0,0 +1,777 @@ + + + + +jarsigner - JAR Signing and Verification Tool + + + +

jarsigner - JAR Signing and Verification Tool

+
+

Generates signatures for Java ARchive (JAR) files, and verifies the + signatures of signed JAR files.

+
+

SYNOPSIS

+
+
jarsigner [ options ] jar-file alias
+jarsigner -verify [ options ] jar-file 
+
+
+

DESCRIPTION

+
+

The jarsigner tool is used for two purposes:

+
    +
  1. to sign Java ARchive (JAR) files, and

     

  2. +
  3. to verify the signatures and integrity of signed JAR files.
  4. +
+

The JAR feature enables the packaging of class files, images, sounds, and + other digital data in a single file for faster and easier distribution. A tool + named + + jar enables developers to produce JAR files. (Technically, any zip + file can also be considered a JAR file, although when created by jar or + processed by jarsigner, JAR files also contain a META-INF/MANIFEST.MF + file.)

+

A digital signature is a string of bits that is computed from some + data (the data being "signed") and the private key of an entity (a person, + company, etc.). Like a handwritten signature, a digital signature has many + useful characteristics:

+

 

+
    +
  • Its authenticity can be verified, via a computation that uses the public + key corresponding to the private key used to generate the signature.

     

  • +
  • It cannot be forged, assuming the private key is kept secret.

     

  • +
  • It is a function of the data signed and thus can't be claimed to be the + signature for other data as well.

     

  • +
  • The signed data cannot be changed; if it is, the signature will no + longer verify as being authentic.

     

  • +
+

In order for an entity's signature to be generated for a file, the entity + must first have a public/private key pair associated with it, and also one or + more certificates authenticating its public key. A certificate is a + digitally signed statement from one entity, saying that the public key of some + other entity has a particular value.

+

jarsigner uses key and certificate information from a keystore + to generate digital signatures for JAR files. A keystore is a database of + private keys and their associated X.509 certificate chains authenticating the + corresponding public keys. The + + keytool utility is used to create and administer keystores.

+

jarsigner uses an entity's private key to generate a signature. The + signed JAR file contains, among other things, a copy of the certificate from + the keystore for the public key corresponding to the private key used to sign + the file. jarsigner can verify the digital signature of the signed JAR + file using the certificate inside it (in its signature block file).

+

At this time, jarsigner can only sign JAR files created by the JDK + + jar tool or zip files. (JAR files are the same as zip files, except + they also have a META-INF/MANIFEST.MF file. Such a file will automatically be + created when jarsigner signs a zip file.)

+

The default jarsigner behavior is to sign a JAR (or zip) + file. Use the -verify option to instead have it verify a + signed JAR file.

+

Compatibility with JDK 1.1

+
+

The keytool and jarsigner tools completely replace the + javakey tool provided in JDK 1.1. These new tools provide more features + than javakey, including the ability to protect the keystore and + private keys with passwords, and the ability to verify signatures in + addition to generating them.

+

The new keystore architecture replaces the identity database that + javakey created and managed. There is no backwards compatibility between + the keystore format and the database format used by javakey in 1.1. + However,

+
    +
  • It is possible to import the information from an identity database + into a keystore, via the keytool -identitydb command.

     

  • +
  • jarsigner can sign JAR files also previously signed using + javakey.

     

  • +
  • jarsigner can verify JAR files signed using javakey. + Thus, it recognizes and can work with signer aliases that are from a JDK + 1.1 identity database rather than a Java 2 SDK keystore.
  • +
+

The following table explains how JAR files that were signed in JDK 1.1.x + are treated in the Java 2 SDK.

+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
JAR File TypeIdentity in 1.1 databaseTrusted Identity imported into Java 2 Platform keystore from 1.1 + database (4)Policy File grants privileges to Identity/AliasPrivileges Granted
Signed JARNONONODefault privileges granted to all code.
Unsigned JARNONONODefault privileges granted to all code.
Signed JARNOYESNODefault privileges granted to all code.
Signed JARYES/UntrustedNONODefault privileges granted to all code. (3)
Signed JARYES/UntrustedNOYESDefault privileges granted to all code. (1,3)
Signed JARNOYESYESDefault privileges granted to all code plus privileges granted in + policy file.
Signed JARYES/TrustedYESYESDefault privileges granted to all code plus privileges granted in + policy file. (2)
Signed JARYES/TrustedNONOAll privileges
Signed JARYES/TrustedYESNOAll privileges (1)
Signed JARYES/TrustedNOYESAll privileges (1)
+

 

+

Notes:

+
    +
  1. If an identity/alias is mentioned in the policy file, it must be + imported into the keystore for the policy file to have any effect on + privileges granted.

     

  2. +
  3. The policy file/keystore combination has precedence over a trusted + identity in the identity database.

     

  4. +
  5. Untrusted identities are ignored in the Java 2 SDK.

     

  6. +
  7. Only trusted identities can be imported into Java 2 SDK keystores.

     

  8. +
+
+

Keystore Aliases

+
+

All keystore entities are accessed via unique aliases.

+

When using jarsigner to sign a JAR file, you must specify the + alias for the keystore entry containing the private key needed to generate + the signature. For example, the following will sign the JAR file named "MyJarFile.jar", + using the private key associated with the alias "duke" in the keystore named + "mystore" in the "working" directory on the C drive. Since no output file is + specified, it overwrites MyJarFile.jar with the signed JAR file.

+
    jarsigner -keystore C:\working\mystore -storepass myspass
+      -keypass dukekeypasswd MyJarFile.jar duke 
+
+

Keystores are protected with a password, so the store password (in this + case "myspass") must be specified. You will be prompted for it if you don't + specify it on the command line. Similarly, private keys are protected in a + keystore with a password, so the private key's password (in this case "dukekeypasswd") + must be specified, and you will be prompted for it if you don't specify it + on the command line and it isn't the same as the store password.

+
+

Keystore Location

+
+

jarsigner has a -keystore option for specifying the + URL of the keystore to be used. The keystore is by default stored in a file + named .keystore in the user's home directory, as determined by the "user.home" + system property. Given user name uName, the "user.home" property + value defaults to

+
C:\Winnt\Profiles\uName on multi-user Windows NT systems
+C:\Windows\Profiles\uName on multi-user Windows 95 systems
+C:\Windows on single-user Windows 95 systems
+
+

Thus, if the user name is "cathy", "user.home" defaults to

+
C:\Winnt\Profiles\cathy on multi-user Windows NT systems
+C:\Windows\Profiles\cathy on multi-user Windows 95 systems
+
+
+

Keystore Implementation

+
+

The KeyStore class provided in the java.security + package supplies well-defined interfaces to access and modify the + information in a keystore. It is possible for there to be multiple different + concrete implementations, where each implementation is that for a particular + type of keystore.

+

Currently, there are two command-line tools that make use of keystore + implementations (keytool and jarsigner), and also a GUI-based + tool named Policy Tool. Since KeyStore is publicly + available, JDK users can write additional security applications that use it. +

+

There is a built-in default implementation, provided by Sun Microsystems. + It implements the keystore as a file, utilizing a proprietary keystore type + (format) named "JKS". It protects each private key with its individual + password, and also protects the integrity of the entire keystore with a + (possibly different) password.

+

Keystore implementations are provider-based. More specifically, the + application interfaces supplied by KeyStore are implemented in + terms of a "Service Provider Interface" (SPI). That is, there is a + corresponding abstract KeystoreSpi class, also in the + java.security package, which defines the Service Provider Interface + methods that "providers" must implement. (The term "provider" refers to a + package or a set of packages that supply a concrete implementation of a + subset of services that can be accessed by the Java Security API.) Thus, to + provide a keystore implementation, clients must implement a provider and + supply a KeystoreSpi subclass implementation, as described in + + How to Implement a Provider for the Java Cryptography Architecture.

+

Applications can choose different types of keystore + implementations from different providers, using the "getInstance" factory + method supplied in the KeyStore class. A keystore type defines + the storage and data format of the keystore information, and the algorithms + used to protect private keys in the keystore and the integrity of the + keystore itself. Keystore implementations of different types are not + compatible.

+

keytool works on any file-based keystore implementation. (It + treats the keytore location that is passed to it at the command line as a + filename and converts it to a FileInputStream, from which it loads the + keystore information.) The jarsigner and policytool tools, on + the other hand, can read a keystore from any location that can be specified + using a URL.

+

For jarsigner and keytool, you can specify a keystore type + at the command line, via the -storetype option. For Policy Tool, + you can specify a keystore type via the "Change Keystore" command in the + Edit menu.

+

If you don't explicitly specify a keystore type, the tools choose a + keystore implementation based simply on the value of the keystore.type + property specified in the security properties file. The security properties + file is called java.security, and it resides in the JDK security + properties directory, java.home\lib\security, where + java.home is the runtime environment's directory (the jre + directory in the SDK or the top-level directory of the Java 2 Runtime + Environment).

+

Each tool gets the keystore.type value and then examines all + the currently-installed providers until it finds one that implements + keystores of that type. It then uses the keystore implementation from that + provider.

+

The KeyStore class defines a static method named + getDefaultType that lets applications and applets retrieve the value + of the keystore.type property. The following line of code + creates an instance of the default keystore type (as specified in the + keystore.type property):

+
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+
+

The default keystore type is "jks" (the proprietary type of the keystore + implementation provided by Sun). This is specified by the following line in + the security properties file:

+
    keystore.type=jks
+
+

To have the tools utilize a keystore implementation other than the + default, change that line to specify a different keystore type.

+

For example, if you have a provider package that supplies a keystore + implementation for a keystore type called "pkcs12", change the line to

+
    keystore.type=pkcs12
+
+

Note: case doesn't matter in keystore type designations. For example, "JKS" + would be considered the same as "jks".

+
+

Supported Algorithms

+
+

At this time, jarsigner can sign a JAR file using either

+
    +
  • DSA (Digital Signature Algorithm) with the SHA-1 digest algorithm, or

     

  • +
  • the RSA algorithm with the MD5 digest algorithm.
  • +
+

That is, if the signer's public and private keys are DSA keys, + jarsigner will sign the JAR file using the "SHA1withDSA" algorithm. If + the signer's keys are RSA keys, jarsigner will attempt to sign the + JAR file using the "MD5withRSA" algorithm. This is only possible if there is + a statically installed + + provider supplying an implementation for the "MD5withRSA" algorithm. + (There is always a "SHA1withDSA" algorithm available, from the default "SUN" + provider.)

+
+

The Signed JAR File

+
+

When jarsigner is used to sign a JAR file, the output signed JAR + file is exactly the same as the input JAR file, except that it has two + additional files placed in the META-INF directory:

+
    +
  • a signature file, with a .SF extension, and

     

  • +
  • a signature block file, with a .DSA extension.
  • +
+

The base file names for these two files come from the value of the + -sigFile option. For example, if the option appears as

+
  -sigFile MKSIGN
+
+

the files are named "MKSIGN.SF" and "MKSIGN.DSA".

+

If no -sigfile option appears on the command line, the base + file name for the .SF and .DSA files will be the first 8 characters of the + alias name specified on the command line, all converted to upper case. If + the alias name has fewer than 8 characters, the full alias name is used. If + the alias name contains any characters that are not allowed in a signature + file name, each such character is converted to an underscore ("_") character + in forming the file name. Legal characters include letters, digits, + underscores, and hyphens.

+

The Signature (.SF) File

+
+

A signature file (the .SF file) looks similar to the manifest file that + is always included in a JAR file when jarsigner is used to sign the + file. That is, for each source file included in the JAR file, the .SF file + has three lines, just as in the manifest file, listing the following:

+
    +
  • the file name,

     

  • +
  • the name of the digest algorithm used (SHA), and +

     

  • +
  • a SHA digest value.
  • +
+

In the manifest file, the SHA digest value for each source file is the + digest (hash) of the binary data in the source file. In the .SF file, on + the other hand, the digest value for a given source file is the hash of + the three lines in the manifest file for the source file.

+

The signature file also, by default, includes a header containing a + hash of the whole manifest file. The presence of the header enables + verification optimization, as described in + + JAR File Verification.

+
+

The Signature Block (.DSA) File

+
+

The .SF file is signed and the signature is placed in the .DSA file. + The .DSA file also contains, encoded inside it, the certificate or + certificate chain from the keystore which authenticates the public key + corresponding to the private key used for signing.

+
+
+

JAR File Verification

+
+

A successful JAR file verification occurs if the signature(s) are valid, + and none of the files that were in the JAR file when the signatures were + generated have been changed since then. JAR file verification involves the + following steps:

+
    +
  1. Verify the signature of the .SF file itself. +

    That is, the verification ensures that the signature stored in each + signature block (.DSA) file was in fact generated using the private key + corresponding to the public key whose certificate (or certificate chain) + also appears in the .DSA file. It also ensures that the signature is a + valid signature of the corresponding signature (.SF) file, and thus the + .SF file has not been tampered with.

    +

     

  2. +
  3. Verify the digest listed in each entry in the .SF file with each + corresponding section in the manifest. +

    The .SF file by default includes a header containing a hash of the + entire manifest file. When the header is present, then the verification + can check to see whether or not the hash in the header indeed matches the + hash of the manifest file. If that is the case, verification proceeds to + the next step.

    +

    If that is not the case, a less optimized verification is required to + ensure that the hash in each source file information section in the .SF + file equals the hash of its corresponding section in the manifest file + (see + + The Signature (.SF) File).

    +

    One reason the hash of the manifest file that is stored in the .SF file + header may not equal the hash of the current manifest file would be + because one or more files were added to the JAR file (using the jar + tool) after the signature (and thus the .SF file) was generated. When the + jar tool is used to add files, the manifest file is changed + (sections are added to it for the new files), but the .SF file is not. A + verification is still considered successful if none of the files that were + in the JAR file when the signature was generated have been changed since + then, which is the case if the hashes in the non-header sections of the + .SF file equal the hashes of the corresponding sections in the manifest + file.

    +

     

  4. +
  5. Read each file in the JAR file that has an entry in the .SF file. + While reading, compute the file's digest, and then compare the result with + the digest for this file in the manifest section. The digests should be + the same, or verification fails.
  6. +
+

If any serious verification failures occur during the verification + process, the process is stopped and a security exception is thrown. It is + caught and displayed by jarsigner.

+
+

Multiple Signatures for a JAR File

+
+

A JAR file can be signed by multiple people simply by running the + jarsigner tool on the file multiple times, specifying the alias for a + different person each time, as in:

+
  jarsigner myBundle.jar susan
+  jarsigner myBundle.jar kevin
+
+

When a JAR file is signed multiple times, there are multiple .SF and .DSA + files in the resulting JAR file, one pair for each signature. Thus, in the + example above, the output JAR file includes files with the following names: +

+
  SUSAN.SF
+  SUSAN.DSA
+  KEVIN.SF
+  KEVIN.DSA
+
+

Note: It is also possible for a JAR file to have mixed signatures, some + generated by the JDK 1.1 javakey tool and others by jarsigner. + That is, jarsigner can be used to sign JAR files already previously + signed using javakey.

+
+
+

OPTIONS

+
+

The various jarsigner options are listed and described below. Note: +

+
    +
  • All option names are preceded by a minus sign (-).

     

  • +
  • The options may be provided in any order.

     

  • +
  • Items in italics (option values) represent the actual values that must + be supplied.

     

  • +
  • The -keystore, -storepass, -keypass, + -sigfile , and -signedjar options are only + relevant when signing a JAR file, not when verifying a signed JAR file. + Similarly, an alias is only specified on the command line when signing a JAR + file.
  • +
+

 

+
+
-keystore url
+
Specifies the URL that tells the keystore location. This defaults to the + file .keystore in the user's home directory, as determined by the "user.home" + system property. +

A keystore is required when signing, so you must explicitly specify one + if the default keystore does not exist (or you want to use one other than + the default).

+

A keystore is not required when verifying, but if one is + specified, or the default exists, and the -verbose option was + also specified, additional information is output regarding whether or not + any of the certificates used to verify the JAR file are contained in that + keystore.

+

Note: the -keystore argument can actually be a file name + (and path) specification rather than a URL, in which case it will be treated + the same as a "file:" URL. That is,

+
  -keystore filePathAndName
+
+

is treated as equivalent to

+
  -keystore file:filePathAndName
+
+

 

+
-storetype storetype
+
Specifies the type of keystore to be instantiated. The default keystore + type is the one that is specified as the value of the "keystore.type" + property in the security properties file, which is returned by the static + getDefaultType method in java.security.KeyStore. +

 

+
-storepass password
+
Specifies the password which is required to access the keystore. This is + only needed when signing (not verifying) a JAR file. In that case, if a + -storepass option is not provided at the command line, the user + is prompted for the password. +

Note: The password shouldn't be specified on the command line or in a + script unless it is for testing purposes, or you are on a secure system. + Also, when typing in a password at the password prompt, the password is + echoed (displayed exactly as typed), so be careful not to type it in front + of anyone.

+

 

+
-keypass password
+
Specifies the password used to protect the private key of the keystore + entry addressed by the alias specified on the command line. The password is + required when using jarsigner to sign a JAR file. If no password is + provided on the command line, and the required password is different from + the store password, the user is prompted for it. +

Note: The password shouldn't be specified on the command line or in a + script unless it is for testing purposes, or you are on a secure system. + Also, when typing in a password at the password prompt, the password is + echoed (displayed exactly as typed), so be careful not to type it in front + of anyone.

+

 

+
-sigfile file
+
Specifies the base file name to be used for the generated .SF and .DSA + files. For example, if file is "DUKESIGN", the generated .SF and .DSA + files will be named "DUKESIGN.SF" and "DUKESIGN.DSA", and will be placed in + the "META-INF" directory of the signed JAR file. +

The characters in file must come from the set "a-zA-Z0-9_-". That + is, only letters, numbers, underscore, and hyphen characters are allowed. + Note: All lowercase characters will be converted to uppercase for the .SF + and .DSA file names.

+

If no -sigfile option appears on the command line, the base + file name for the .SF and .DSA files will be the first 8 characters of the + alias name specified on the command line, all converted to upper case. If + the alias name has fewer than 8 characters, the full alias name is used. If + the alias name contains any characters that are not legal in a signature + file name, each such character is converted to an underscore ("_") character + in forming the file name.

+

 

+
-signedjar file
+
Specifies the name to be used for the signed JAR file. +

If no name is specified on the command line, the name used is the same as + the input JAR file name (the name of the JAR file to be signed); in other + words, that file is overwritten with the signed JAR file.

+

 

+
-verify
+
If this appears on the command line, the specified JAR file will be + verified, not signed. If the verification is successful, "jar verified" will + be displayed. If you try to verify an unsigned JAR file, or a JAR file + signed with an unsupported algorithm (e.g., RSA when you don't have an RSA + provider installed), the following is displayed: "jar is unsigned. + (signatures missing or not parsable)" +

It is possible to verify JAR files signed using either jarsigner + or the JDK 1.1 javakey tool, or both.

+

For further information on verification, see + + JAR File Verification.

+

 

+
-certs
+
If this appears on the command line, along with the -verify + and -verbose options, the output includes certificate + information for each signer of the JAR file. This information includes

 

    +
  • the name of the type of certificate (stored in the .DSA file) that + certifies the signer's public key

     

  • +
  • if the certificate is an X.509 certificate (more specifically, an + instance of java.security.cert.X509Certificate): the + distinguished name of the signer
  • +
+

The keystore is also examined. If no keystore value is specified on the + command line, the default keystore file (if any) will be checked. If the + public key certificate for a signer matches an entry in the keystore, then + the following information will also be displayed:

+

 

    +
  • in parentheses, the alias name for the keystore entry for that signer. + If the signer actually comes from a JDK 1.1 identity database instead of + from a keystore, the alias name will appear in brackets instead of + parentheses.
  • +
+

 

+
-verbose
+
If this appears on the command line, it indicates "verbose" mode, which + causes jarsigner to output extra information as to the progress of + the JAR signing or verification. +

 

+
-internalsf
+
In the past, the .DSA (signature block) file generated when a JAR file + was signed used to include a complete encoded copy of the .SF file + (signature file) also generated. This behavior has been changed. To reduce + the overall size of the output JAR file, the .DSA file by default doesn't + contain a copy of the .SF file anymore. But if -internalsf + appears on the command line, the old behavior is utilized. This option is + mainly useful for testing; in practice, it should not be used, since doing + so eliminates a useful optimization. +

 

+
-sectionsonly
+
If this appears on the command line, the .SF file (signature file) + generated when a JAR file is signed does not include a header + containing a hash of the whole manifest file. It just contains information + and hashes related to each individual source file included in the JAR file, + as described in + + The Signature (.SF) File . +

By default, this header is added, as an optimization. When the header is + present, then whenever the JAR file is verified, the verification can first + check to see whether or not the hash in the header indeed matches the hash + of the whole manifest file. If so, verification proceeds to the next step. + If not, it is necessary to do a less optimized verification that the hash in + each source file information section in the .SF file equals the hash of its + corresponding section in the manifest file.

+

For further information, see + + JAR File Verification.

+

This option is mainly useful for testing; in practice, it should not + be used, since doing so eliminates a useful optimization.

+

 

+
-provider provider-class-name
+
Used to specify the name of cryptographic service provider's master + class file when the service provider is not listed in the security + properties file.

 

+
-Jjavaoption
+
Passes through the specified javaoption string directly to the + Java interpreter. (jarsigner is actually a "wrapper" around the + interpreter.) This option should not contain any spaces. It is useful for + adjusting the execution environment or memory usage. For a list of possible + interpreter options, type java -h or java -X at + the command line.

 

+
+
+

EXAMPLES

+
+

Signing a JAR File

+
+

Suppose you have a JAR file named "bundle.jar" and you'd like to sign it + using the private key of the user whose keystore alias is "jane" in the + keystore named "mystore" in the "working" directory on the C drive. Suppose + the keystore password is "myspass" and the password for jane's + private key is "j638klm". You can use the following to sign the JAR file and + name the signed JAR file "sbundle.jar":

+
    jarsigner -keystore C:\working\mystore -storepass myspass
+      -keypass j638klm -signedjar sbundle.jar bundle.jar jane 
+
+

Note that there is no -sigfile specified in the command + above, so the generated .SF and .DSA files to be placed in the signed JAR + file will have default names based on the alias name. That is, they will be + named JANE.SF and JANE.DSA.

+

If you want to be prompted for the store password and the private key + password, you could shorten the above command to

+
    jarsigner -keystore C:\working\mystore
+      -signedjar sbundle.jar bundle.jar jane 
+
+

If the keystore to be used is the default keystore (the one named ".keystore" + in your home directory), you don't need to specify a keystore, as in:

+
    jarsigner -signedjar sbundle.jar bundle.jar jane 
+
+

Finally, if you want the signed JAR file to simply overwrite the input + JAR file (bundle.jar), you don't need to specify a -signedjar + option:

+
    jarsigner bundle.jar jane 
+
+
+

Verifying a Signed JAR File

+
+

To verify a signed JAR file, that is, to verify that the signature is + valid and the JAR file has not been tampered with, use a command such as the + following:

+
    jarsigner -verify sbundle.jar 
+
+

If the verification is successful,

+
    jar verified.
+
+

is displayed. Otherwise, an error message appears.

+

You can get more information if you use the -verbose option. + A sample use of jarsigner with the -verbose option is + shown below, along with sample output:

+
    jarsigner -verify -verbose sbundle.jar
+
+           198 Fri Sep 26 16:14:06 PDT 1997 META-INF/MANIFEST.MF
+           199 Fri Sep 26 16:22:10 PDT 1997 META-INF/JANE.SF
+          1013 Fri Sep 26 16:22:10 PDT 1997 META-INF/JANE.DSA
+    smk   2752 Fri Sep 26 16:12:30 PDT 1997 AclEx.class
+    smk    849 Fri Sep 26 16:12:46 PDT 1997 test.class
+
+      s = signature was verified
+      m = entry is listed in manifest
+      k = at least one certificate was found in keystore
+
+    jar verified.
+
+

Verification with Certificate Information

+

If you specify the -certs option when verifying, along with + the -verify and -verbose options, the output + includes certificate information for each signer of the JAR file, including + the certificate type, the signer distinguished name information (iff it's an + X.509 certificate), and, in parentheses, the keystore alias for the signer + if the public key certificate in the JAR file matches that in a keystore + entry. For example,

+
    jarsigner -keystore /working/mystore -verify -verbose -certs myTest.jar
+
+           198 Fri Sep 26 16:14:06 PDT 1997 META-INF/MANIFEST.MF
+           199 Fri Sep 26 16:22:10 PDT 1997 META-INF/JANE.SF
+          1013 Fri Sep 26 16:22:10 PDT 1997 META-INF/JANE.DSA
+           208 Fri Sep 26 16:23:30 PDT 1997 META-INF/JAVATEST.SF
+          1087 Fri Sep 26 16:23:30 PDT 1997 META-INF/JAVATEST.DSA
+    smk   2752 Fri Sep 26 16:12:30 PDT 1997 Tst.class
+
+      X.509, CN=Test Group, OU= , O=Sun Microsystems, L=CUP, S=CA, C=US (javatest)
+      X.509, CN=Jane Smith, OU= , O=Sun, L=cup, S=ca, C=us (jane)
+
+      s = signature was verified
+      m = entry is listed in manifest
+      k = at least one certificate was found in keystore
+
+    jar verified.
+
+

If the certificate for a signer is not an X.509 certificate, there is no + distinguished name information. In that case, just the certificate type and + the alias are shown. For example, if the certificate is a PGP certificate, + and the alias is "bob", you'd get

+
      PGP, (bob)
+
+

Verification of a JAR File that Includes Identity Database Signers

+

If a JAR file has been signed using the JDK 1.1 javakey tool, and + thus the signer is an alias in an identity database, the verification output + includes an "i" symbol. If the JAR file has been signed by both an alias in + an identity database and an alias in a keystore, both "k" and "i" appear. +

+

When the -certs option is used, any identity database + aliases are shown in square brackets rather than the parentheses used for + keystore aliases. For example:

+
    jarsigner -keystore /working/mystore -verify -verbose -certs writeFile.jar
+
+           198 Fri Sep 26 16:14:06 PDT 1997 META-INF/MANIFEST.MF
+           199 Fri Sep 26 16:22:10 PDT 1997 META-INF/JANE.SF
+          1013 Fri Sep 26 16:22:10 PDT 1997 META-INF/JANE.DSA
+           199 Fri Sep 27 12:22:30 PDT 1997 META-INF/DUKE.SF
+          1013 Fri Sep 27 12:22:30 PDT 1997 META-INF/DUKE.DSA
+   smki   2752 Fri Sep 26 16:12:30 PDT 1997 writeFile.html
+
+      X.509, CN=Jane Smith, OU= , O=Sun, L=cup, S=ca, C=us (jane)
+      X.509, CN=Duke, OU= , O=Sun, L=cup, S=ca, C=us [duke]
+
+      s = signature was verified
+      m = entry is listed in manifest
+      k = at least one certificate was found in keystore
+      i = at least one certificate was found in identity scope
+
+    jar verified.
+
+

Note that the alias "duke" is in brackets to denote that it is an + identity database alias, not a keystore alias.

+
+
+

SEE ALSO

+
+ +
+
+ + + + + +
+ + Copyright © 1995, 2010 Oracle and/or its + affiliates. All rights reserved. + Sun +
+ + \ No newline at end of file diff --git a/help/keytool.html b/help/keytool.html new file mode 100644 index 0000000..abc5252 --- /dev/null +++ b/help/keytool.html @@ -0,0 +1,1341 @@ + + + + +keytool-Key and Certificate Management Tool + + + +

keytool - Key and Certificate Management Tool

+
+

Manages a keystore (database) of private keys and their associated X.509 + certificate chains authenticating the corresponding public keys. Also manages + certificates from trusted entities.

+
+

SYNOPSIS

+
+
keytool [ commands ]
+
+
+

DESCRIPTION

+
+

keytool is a key and certificate management utility. It enables + users to administer their own public/private key pairs and associated + certificates for use in self-authentication (where the user authenticates + himself/herself to other users/services) or data integrity and authentication + services, using digital signatures. It also allows users to cache the public + keys (in the form of certificates) of their communicating peers.

+

A certificate is a digitally signed statement from one entity + (person, company, etc.), saying that the public key (and some other + information) of some other entity has a particular value. (See + + Certificates.) When data is digitally signed, the signature can be + verified to check the data integrity and authenticity. Integrity means + that the data has not been modified or tampered with, and authenticity + means the data indeed comes from whoever claims to have created and signed it. +

+

keytool stores the keys and certificates in a so-called keystore. + The default + + keystore implementation implements the keystore as a file. It protects + private keys with a password.

+

The + + jarsigner tool uses information from a keystore to generate or + verify digital signatures for Java ARchive (JAR) files. (A JAR file packages + class files, images, sounds, and/or other digital data in a single file). + jarsigner verifies the digital signature of a JAR file, using the + certificate that comes with it (it is included in the signature block file of + the JAR file), and then checks whether or not the public key of that + certificate is "trusted", i.e., is contained in the specified keystore.

+

Please note: the keytool and jarsigner tools completely + replace the javakey tool provided in JDK 1.1. These new tools provide + more features than javakey, including the ability to protect the + keystore and private keys with passwords, and the ability to verify signatures + in addition to generating them. The new keystore architecture replaces the + identity database that javakey created and managed. It is possible to + import the information from an identity database into a keystore, via the + -identitydb keytool command.

+

Keystore Entries

+
+

There are two different types of entries in a keystore:

+
    +
  1. key entries - each holds very sensitive cryptographic key + information, which is stored in a protected format to prevent unauthorized + access. Typically, a key stored in this type of entry is a secret key, or + a private key accompanied by the + + certificate "chain" for the corresponding public key. The keytool + and jarsigner tools only handle the latter type of entry, that is + private keys and their associated certificate chains.

     

  2. +
  3. trusted certificate entries - each contains a single public key + certificate belonging to another party. It is called a "trusted + certificate" because the keystore owner trusts that the public key in the + certificate indeed belongs to the identity identified by the "subject" + (owner) of the certificate. The issuer of the certificate vouches for + this, by signing the certificate.
  4. +
+
+

Keystore Aliases

+
+

All keystore entries (key and trusted certificate entries) are accessed + via unique aliases. Aliases are case-insensitive; the aliases + Hugo and hugo would refer to the same keystore entry. +

+

An alias is specified when you add an entity to the keystore using the + + -genkey command to generate a key pair (public and private key) or the + + -import command to add a certificate or certificate chain to the list of + trusted certificates. Subsequent keytool commands must use this same + alias to refer to the entity.

+

For example, suppose you use the alias duke to generate a new + public/private key pair and wrap the public key into a self-signed + certificate (see + + Certificate Chains) via the following command:

+
    keytool -genkey -alias duke -keypass dukekeypasswd
+
+

This specifies an inital password of "dukekeypasswd" required by + subsequent commands to access the private key assocated with the alias + duke. If you later want to change duke's private key password, you + use a command like the following:

+
    keytool -keypasswd -alias duke -keypass dukekeypasswd -new newpass
+
+

This changes the password from "dukekeypasswd" to "newpass".

+

Please note: A password should not actually be specified on a command + line or in a script unless it is for testing purposes, or you are on a + secure system. If you don't specify a required password option on a command + line, you will be prompted for it. When typing in a password at the password + prompt, the password is currently echoed (displayed exactly as typed), so be + careful not to type it in front of anyone.

+
+

Keystore Location

+
+

Each keytool command has a -keystore option for + specifying the name and location of the persistent keystore file for the + keystore managed by keytool. The keystore is by default stored in a + file named .keystore in the user's home directory, as determined by + the "user.home" system property. Given user name uName, the "user.home" + property value defaults to

+
C:\Winnt\Profiles\uName on multi-user Windows NT systems
+C:\Windows\Profiles\uName on multi-user Windows 95 systems
+C:\Windows on single-user Windows 95 systems
+
+

Thus, if the user name is "cathy", "user.home" defaults to

+
C:\Winnt\Profiles\cathy on multi-user Windows NT systems
+C:\Windows\Profiles\cathy on multi-user Windows 95 systems
+
+
+

Keystore Creation

+
+

A keystore is created whenever you use a + + -genkey, + + -import, or + + -identitydb command to add data to a keystore that doesn't yet exist. +

+

More specifically, if you specify, in the -keystore option, + a keystore that doesn't yet exist, that keystore will be created.

+

If you don't specify a -keystore option, the default + keystore is a file named .keystore in your home directory. If + that file does not yet exist, it will be created.

+
+

Keystore Implementation

+
+

The KeyStore class provided in the java.security + package supplies well-defined interfaces to access and modify the + information in a keystore. It is possible for there to be multiple different + concrete implementations, where each implementation is that for a particular + type of keystore.

+

Currently, two command-line tools (keytool and jarsigner) + and a GUI-based tool named Policy Tool make use of keystore + implementations. Since KeyStore is publicly available, JDK + users can write additional security applications that use it.

+

There is a built-in default implementation, provided by Sun Microsystems. + It implements the keystore as a file, utilizing a proprietary keystore type + (format) named "JKS". It protects each private key with its individual + password, and also protects the integrity of the entire keystore with a + (possibly different) password.

+

Keystore implementations are provider-based. More specifically, the + application interfaces supplied by KeyStore are implemented in + terms of a "Service Provider Interface" (SPI). That is, there is a + corresponding abstract KeystoreSpi class, also in the + java.security package, which defines the Service Provider Interface + methods that "providers" must implement. (The term "provider" refers to a + package or a set of packages that supply a concrete implementation of a + subset of services that can be accessed by the Java Security API.) Thus, to + provide a keystore implementation, clients must implement a "provider" and + supply a KeystoreSpi subclass implementation, as described in + + How to Implement a Provider for the Java Cryptography Architecture.

+

Applications can choose different types of keystore + implementations from different providers, using the "getInstance" factory + method supplied in the KeyStore class. A keystore type defines + the storage and data format of the keystore information, and the algorithms + used to protect private keys in the keystore and the integrity of the + keystore itself. Keystore implementations of different types are not + compatible.

+

keytool works on any file-based keystore implementation. (It + treats the keytore location that is passed to it at the command line as a + filename and converts it to a FileInputStream, from which it loads the + keystore information.) The jarsigner and policytool tools, on + the other hand, can read a keystore from any location that can be specified + using a URL.

+

For keytool and jarsigner, you can specify a keystore type + at the command line, via the -storetype option. For Policy Tool, + you can specify a keystore type via the "Change Keystore" command in the + Edit menu.

+

If you don't explicitly specify a keystore type, the tools choose a + keystore implementation based simply on the value of the keystore.type + property specified in the security properties file. The security properties + file is called java.security, and it resides in the JDK security + properties directory, java.home\lib\security, where + java.home is the runtime environment's directory (the jre + directory in the SDK or the top-level directory of the Java 2 Runtime + Environment).

+

Each tool gets the keystore.type value and then examines all + the currently-installed providers until it finds one that implements + keystores of that type. It then uses the keystore implementation from that + provider.

+

The KeyStore class defines a static method named + getDefaultType that lets applications and applets retrieve the value + of the keystore.type property. The following line of code + creates an instance of the default keystore type (as specified in the + keystore.type property):

+
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+
+

The default keystore type is "jks" (the proprietary type of the keystore + implementation provided by Sun). This is specified by the following line in + the security properties file:

+
    keystore.type=jks
+
+

To have the tools utilize a keystore implementation other than the + default, you can change that line to specify a different keystore type.

+

For example, if you have a provider package that supplies a keystore + implementation for a keystore type called "pkcs12", change the line to

+
    keystore.type=pkcs12
+
+

Note: case doesn't matter in keystore type designations. For example, "JKS" + would be considered the same as "jks".

+
+

Supported Algorithms and Key Sizes

+
+

keytool allows users to specify any key pair generation and + signature algorithm supplied by any of the registered cryptographic service + providers. That is, the keyalg and sigalg options for various + commands must be supported by a provider implementation. The default key + pair generation algorithm is "DSA". The signature algorithm is derived from + the algorithm of the underlying private key: If the underlying private key + is of type "DSA", the default signature algorithm is "SHA1withDSA", and if + the underlying private key is of type "RSA", the default signature algorithm + is "MD5withRSA".

+

When generating a DSA key pair, the key size must be in the range from + 512 to 1024 bits, and must be a multiple of 64. The default key size for any + algorithm is 1024 bits.

+
+

Certificates

+
+

A certificate (also known as a public-key certificate) is a + digitally signed statement from one entity (the issuer), saying that + the public key (and some other information) of another entity (the + subject) has some specific value.

+

Let us expand on some of the key terms used in this sentence:

+
+
Public Keys
+
These are numbers associated with a particular entity, and are + intended to be known to everyone who needs to have trusted interactions + with that entity. Public keys are used to verify signatures.
+
Digitally Signed
+
If some data is digitally signed it has been stored with the + "identity" of an entity, and a signature that proves that entity knows + about the data. The data is rendered unforgeable by signing with the + entity's private key.
+
Identity
+
A known way of addressing an entity. In some systems the identity is + the public key, in others it can be anything from a Unix UID to an Email + address to an X.509 Distinguished Name.
+
Signature
+
A signature is computed over some data using the private key of an + entity (the signer, which in the case of a certificate is also + known as the issuer).
+
Private Keys
+
These are numbers, each of which is supposed to be known only to the + particular entity whose private key it is (that is, it's supposed to be + kept secret). Private and public keys exist in pairs in all public key + cryptography systems (also referred to as "public key crypto systems"). In + a typical public key crypto system, such as DSA, a private key corresponds + to exactly one public key. Private keys are used to compute signatures. +
+
Entity
+
An entity is a person, organization, program, computer, business, + bank, or something else you are trusting to some degree.
+
+

Basically, public key cryptography requires access to users' public keys. + In a large-scale networked environment it is impossible to guarantee that + prior relationships between communicating entities have been established or + that a trusted repository exists with all used public keys. Certificates + were invented as a solution to this public key distribution problem. Now a + Certification Authority (CA) can act as a trusted third party. CAs + are entities (for example, businesses) that are trusted to sign (issue) + certificates for other entities. It is assumed that CAs will only create + valid and reliable certificates, as they are bound by legal agreements. + There are many public Certification Authorities, such as + VeriSign, + Thawte, + Entrust, and so on. You can also run your own Certification Authority + using products such as the Netscape/Microsoft Certificate Servers or the + Entrust CA product for your organization.

+

Using keytool, it is possible to display, import, and export + certificates. It is also possible to generate self-signed certificates.

+

keytool currently handles X.509 certificates.

+

X.509 Certificates

+
+

The X.509 standard defines what information can go into a certificate, + and describes how to write it down (the data format). All X.509 + certificates have the following data, in addition to the signature:

+
+
Version
+
This identifies which version of the X.509 standard applies to this + certificate, which affects what information can be specified in it. Thus + far, three versions are defined. keytool can import and export + v1, v2, and v3 certificates. It generates v1 certificates.
+
Serial Number
+
The entity that created the certificate is responsible for assigning + it a serial number to distinguish it from other certificates it issues. + This information is used in numerous ways, for example when a + certificate is revoked its serial number is placed in a Certificate + Revocation List (CRL).
+
Signature Algorithm Identifier
+
This identifies the algorithm used by the CA to sign the + certificate.
+
Issuer Name
+
The + + X.500 Distinguished Name of the entity that signed the certificate. + This is normally a CA. Using this certificate implies trusting the + entity that signed this certificate. (Note that in some cases, such as + root or top-level CA certificates, the issuer signs its own + certificate.)
+
Validity Period
+
Each certificate is valid only for a limited amount of time. This + period is described by a start date and time and an end date and time, + and can be as short as a few seconds or almost as long as a century. The + validity period chosen depends on a number of factors, such as the + strength of the private key used to sign the certificate or the amount + one is willing to pay for a certificate. This is the expected period + that entities can rely on the public value, if the associated private + key has not been compromised.
+
Subject Name
+
The name of the entity whose public key the certificate identifies. + This name uses the X.500 standard, so it is intended to be unique across + the Internet. This is the + + X.500 Distinguished Name (DN) of the entity, for example, +
    CN=Java Duke, OU=  Division, O=Sun Microsystems Inc, C=US
+
+

(These refer to the subject's Common Name, Organizational Unit, + Organization, and Country.)

+
Subject Public Key Information
+
This is the public key of the entity being named, together with an + algorithm identifier which specifies which public key crypto system this + key belongs to and any associated key parameters.
+
+

X.509 Version 1 has been available since 1988, is widely + deployed, and is the most generic.

+

X.509 Version 2 introduced the concept of subject and issuer + unique identifiers to handle the possibility of reuse of subject and/or + issuer names over time. Most certificate profile documents strongly + recommend that names not be reused, and that certificates should not make + use of unique identifiers. Version 2 certificates are not widely used.

+

X.509 Version 3 is the most recent (1996) and supports the + notion of extensions, whereby anyone can define an extension and include + it in the certificate. Some common extensions in use today are: + KeyUsage (limits the use of the keys to particular purposes such as + "signing-only") and AlternativeNames (allows other identities to + also be associated with this public key, e.g. DNS names, Email addresses, + IP addresses). Extensions can be marked critical to indicate that + the extension should be checked and enforced/used. For example, if a + certificate has the KeyUsage extension marked critical and set to "keyCertSign" + then if this certificate is presented during SSL communication, it should + be rejected, as the certificate extension indicates that the associated + private key should only be used for signing certificates and not for SSL + use.

+

All the data in a certificate is encoded using two related standards + called ASN.1/DER. Abstract Syntax Notation 1 describes data. The + Definite Encoding Rules describe a single way to store and transfer + that data.

+
+

X.500 Distinguished Names

+
+

X.500 Distinguished Names are used to identify entities, such as those + which are named by the subject and issuer + (signer) fields of X.509 certificates. keytool supports the + following subparts:

+
    +
  • commonName - common name of a person, e.g., "Susan Jones"

     

  • +
  • organizationUnit - small organization (e.g, department or + division) name, e.g., "Purchasing"

     

  • +
  • organizationName - large organization name, e.g., "ABCSystems, + Inc."

     

  • +
  • localityName - locality (city) name, e.g., "Palo Alto"

     

  • +
  • stateName - state or province name, e.g., "California"

     

  • +
  • country - two-letter country code, e.g., "CH"

     

  • +
+

When supplying a distinguished name string as the value of a -dname + option, as for the -genkey or -selfcert + commands, the string must be in the following format:

+
CN=cName, OU=orgUnit, O=org, L=city, S=state, C=countryCode
+
+

where all the italicized items represent actual values and the above + keywords are abbreviations for the following:

+
	CN=commonName
+	OU=organizationUnit
+	O=organizationName
+	L=localityName
+	S=stateName
+	C=country
+
+

A sample distinguished name string is

+
CN=Mark Smith, OU=JavaSoft, O=Sun, L=Cupertino, S=California, C=US
+
+

and a sample command using such a string is

+
keytool -genkey -dname "CN=Mark Smith, OU=JavaSoft, O=Sun, L=Cupertino, 
+S=California, C=US" -alias mark
+
+

Case does not matter for the keyword abbreviations. For example, "CN", + "cn", and "Cn" are all treated the same.

+

Order matters; each subcomponent must appear in the designated order. + However, it is not necessary to have all the subcomponents. You may use a + subset, for example:

+
CN=Steve Meier, OU=SunSoft, O=Sun, C=US
+
+

If a distinguished name string value contains a comma, the comma must + be escaped by a "\" character when you specify the string on a command + line, as in

+
   cn=peter schuster, o=Sun Microsystems\, Inc., o=sun, c=us
+
+

It is never necessary to specify a distinguished name string on a + command line. If it is needed for a command, but not supplied on the + command line, the user is prompted for each of the subcomponents. In this + case, a comma does not need to be escaped by a "\".

+
+

The Internet RFC 1421 Certificate Encoding + Standard

+
+

Certificates are often stored using the printable encoding format + defined by the Internet RFC 1421 standard, instead of their binary + encoding. This certificate format, also known as "Base 64 encoding", + facilitates exporting certificates to other applications by email or + through some other mechanism.

+

Certificates read by the -import and -printcert + commands can be in either this format or binary encoded.

+

The -export command by default outputs a certificate in + binary encoding, but will instead output a certificate in the printable + encoding format, if the -rfc option is specified.

+

The -list command by default prints the MD5 fingerprint of + a certificate. If the -v option is specified, the certificate + is printed in human-readable format, while if the -rfc option + is specified, the certificate is output in the printable encoding format. +

+

In its printable encoding format, the encoded certificate is bounded at + the beginning by

+
-----BEGIN CERTIFICATE-----
+
+

and at the end by

+
-----END CERTIFICATE-----
+
+
+

Certificate Chains

+
+

keytool can create and manage keystore "key" entries that each + contain a private key and an associated certificate "chain". The first + certificate in the chain contains the public key corresponding to the + private key.

+

When keys are first generated (see the + + -genkey command), the chain starts off containing a single element, a + self-signed certificate. A self-signed certificate is one for which + the issuer (signer) is the same as the subject (the entity whose public + key is being authenticated by the certificate). Whenever the -genkey + command is called to generate a new public/private key pair, it also wraps + the public key into a self-signed certificate.

+

Later, after a Certificate Signing Request (CSR) has been generated + (see the + + -certreq command) and sent to a Certification Authority (CA), the + response from the CA is imported (see + + -import), and the self-signed certificate is replaced by a chain of + certificates. At the bottom of the chain is the certificate (reply) issued + by the CA authenticating the subject's public key. The next certificate in + the chain is one that authenticates the CA's public key.

+

In many cases, this is a self-signed certificate (that is, a + certificate from the CA authenticating its own public key) and the last + certificate in the chain. In other cases, the CA may return a chain of + certificates. In this case, the bottom certificate in the chain is the + same (a certificate signed by the CA, authenticating the public key of the + key entry), but the second certificate in the chain is a certificate + signed by a different CA, authenticating the public key of the CA + you sent the CSR to. Then, the next certificate in the chain will be a + certificate authenticating the second CA's key, and so on, until a + self-signed "root" certificate is reached. Each certificate in the chain + (after the first) thus authenticates the public key of the signer of the + previous certificate in the chain.

+

Many CAs only return the issued certificate, with no supporting chain, + especially when there is a flat hierarchy (no intermediates CAs). In this + case, the certificate chain must be established from trusted certificate + information already stored in the keystore.

+

A different reply format (defined by the PKCS#7 standard) also includes + the supporting certificate chain, in addition to the issued certificate. + Both reply formats can be handled by keytool.

+

The top-level (root) CA certificate is self-signed. However, the trust + into the root's public key does not come from the root certificate itself + (anybody could generate a self-signed certificate with the distinguished + name of say, the VeriSign root CA!), but from other sources like a + newspaper. The root CA public key is widely known. The only reason it is + stored in a certificate is because this is the format understood by most + tools, so the certificate in this case is only used as a "vehicle" to + transport the root CA's public key. Before you add the root CA certificate + to your keystore, you should view it (using the -printcert + option) and compare the displayed fingerprint with the well-known + fingerprint (obtained from a newspaper, the root CA's webpage, etc.).

+
+

Importing Certificates

+
+

To import a certificate from a file, use the + + -import command, as in

+
    keytool -import -alias joe -file jcertfile.cer
+
+

This sample command imports the certificate(s) in the file + jcertfile.cer and stores it in the keystore entry identified by the + alias joe.

+

You import a certificate for two reasons:

+
    +
  1. to add it to the list of trusted certificates, or

     

  2. +
  3. to import a certificate reply received from a CA as the result of + submitting a Certificate Signing Request (see the + + -certreq command) to that CA.
  4. +
+

Which type of import is intended is indicated by the value of the + -alias option. If the alias exists in the database, and identifies + an entry with a private key, then it is assumed you want to import a + certificate reply. keytool checks whether the public key in the + certificate reply matches the public key stored with the alias, and exits + if they are different. If the alias identifies the other type of keystore + entry, the certificate will not be imported. If the alias does not exist, + then it will be created and associated with the imported certificate.

+

WARNING Regarding Importing Trusted + Certificates

+
+

IMPORTANT: Be sure to check a certificate very carefully before + importing it as a trusted certificate!

+

View it first (using the -printcert command, or the + -import command without the -noprompt option), + and make sure that the displayed certificate fingerprint(s) match the + expected ones. For example, suppose someone sends or emails you a + certificate, and you put it in a file named /tmp/cert. + Before you consider adding the certificate to your list of trusted + certificates, you can execute a -printcert command to view + its fingerprints, as in

+
  keytool -printcert -file /tmp/cert
+    Owner: CN=ll, OU=ll, O=ll, L=ll, S=ll, C=ll
+    Issuer: CN=ll, OU=ll, O=ll, L=ll, S=ll, C=ll
+    Serial Number: 59092b34
+    Valid from: Thu Sep 25 18:01:13 PDT 1997 until: Wed Dec 24 17:01:13 PST 1997
+    Certificate Fingerprints:
+         MD5:  11:81:AD:92:C8:E5:0E:A2:01:2E:D4:7A:D7:5F:07:6F
+         SHA1: 20:B6:17:FA:EF:E5:55:8A:D0:71:1F:E8:D6:9D:C0:37:13:0E:5E:FE
+
+

Then call or otherwise contact the person who sent the certificate, + and compare the fingerprint(s) that you see with the ones that they + show. Only if the fingerprints are equal is it guaranteed that the + certificate has not been replaced in transit with somebody else's (for + example, an attacker's) certificate. If such an attack took place, and + you did not check the certificate before you imported it, you would end + up trusting anything the attacker has signed (for example, a JAR file + with malicious class files inside).

+

Note: it is not required that you execute a -printcert + command prior to importing a certificate, since before adding a + certificate to the list of trusted certificates in the keystore, the + -import command prints out the certificate information and + prompts you to verify it. You then have the option of aborting the + import operation. Note, however, this is only the case if you invoke the + -import command without the -noprompt option. + If the -noprompt option is given, there is no interaction + with the user.

+
+
+

Exporting Certificates

+
+

To export a certificate to a file, use the + + -export command, as in

+
    keytool -export -alias jane -file janecertfile.cer
+
+

This sample command exports jane's certificate to the file + janecertfile.cer. That is, if jane is the alias for a + key entry, the command exports the certificate at the bottom of the + certificate chain in that keystore entry. This is the certificate that + authenticates jane's public key.

+

If, instead, jane is the alias for a trusted certificate + entry, then that trusted certificate is exported.

+
+

Displaying Certificates

+
+

To print out the contents of a keystore entry, use the + + -list command, as in

+
    keytool -list -alias joe
+
+

If you don't specify an alias, as in

+
    keytool -list
+
+

the contents of the entire keystore are printed.

+

To display the contents of a certificate stored in a file, use the + + -printcert command, as in

+
    keytool -printcert -file certfile.cer
+
+

This displays information about the certificate stored in the file + certfile.cer.

+

Note: This works independently of a keystore, i.e., you do not + need a keystore in order to display a certificate that's stored in a file. +

+
+

Generating a self-signed certificate

+
+

A self-signed certificate is one for which the issuer (signer) + is the same as the subject (the entity whose public key is being + authenticated by the certificate). Whenever the -genkey + command is called to generate a new public/private key pair, it also wraps + the public key into a self-signed certificate.

+

You may occasionally wish to generate a new self-signed certificate. + For example, you may want to use the same key pair under a different + identity (distinguished name). For example, suppose you change + departments. You can then:

+
    +
  1. copy (clone) the original key entry. See + + -keyclone.

     

  2. +
  3. generate a new self-signed certificate for the cloned entry, using + your new distinguished name. See below.

     

  4. +
  5. generate a Certificate Signing Requests for the cloned entry, and + import the reply certificate or certificate chain. See the + + -certreq and + + -import commands.

     

  6. +
  7. delete the original (now obsolete) entry. See + + -delete.

     

  8. +
+

To generate a self-signed certificate, use the + + -selfcert command, as in

+
    keytool -selfcert -alias dukeNew -keypass b92kqmp
+      -dname "cn=Duke Smith, ou=Purchasing, o=BlueSoft, c=US"
+
+

The generated certificate is stored as a single-element certificate + chain in the keystore entry identified by the specified alias (in this + case "dukeNew"), where it replaces the existing certificate chain.

+
+
+
+

COMMAND AND OPTION NOTES

+
+

The various commands and their options are listed and described + + below . Note:

+
    +
  • All command and option names are preceded by a minus sign (-).

     

  • +
  • The options for each command may be provided in any order.

     

  • +
  • All items not italicized or in braces or square brackets are required to + appear as is.

     

  • +
  • Braces surrounding an option generally signify that a + + default value will be used if the option is not specified on the command + line. Braces are also used around the -v, -rfc, + and -J options, which only have meaning if they appear on the + command line (that is, they don't have any "default" values other than not + existing).

     

  • +
  • Brackets surrounding an option signify that the user is prompted for the + value(s) if the option is not specified on the command line. (For a -keypass + option, if you do not specify the option on the command line, keytool + will first attempt to use the keystore password to recover the private key, + and if this fails, will then prompt you for the private key password.)

     

  • +
  • Items in italics (option values) represent the actual values that must + be supplied. For example, here is the format of the -printcert + command: +
      keytool -printcert {-file cert_file} {-v}
    +
    +

    When specifying a -printcert command, replace cert_file + with the actual file name, as in:

    +
      keytool -printcert -file VScert.cer
    +
    +

     

  • +
  • Option values must be quoted if they contain a blank (space).

     

  • +
  • The -help command is the default. Thus, the command line +
      keytool
    +
    +

    is equivalent to

    +
      keytool -help
    +
    +
  • +
+

Option Defaults

+
+

Below are the defaults for various option values.

+
-alias "mykey"
+
+-keyalg "DSA"
+
+-keysize 1024
+
+-validity 90
+
+-keystore the file named .keystore in the user's home directory
+
+-file stdin if reading, stdout if writing
+
+
+

The signature algorithm (-sigalg option) is derived from the + algorithm of the underlying private key: If the underlying private key is of + type "DSA", the -sigalg option defaults to "SHA1withDSA", and if the + underlying private key is of type "RSA", -sigalg defaults to + "MD5withRSA".

+
+

Options that Appear for Most Commands

+
+

The -v option can appear for all commands except -help. + If it appears, it signifies "verbose" mode; detailed certificate information + will be output.

+

There is also a -Jjavaoption option that may appear + for any command. If it appears, the specified javaoption string is + passed through directly to the Java interpreter. (keytool is actually + a "wrapper" around the interpreter.) This option should not contain any + spaces. It is useful for adjusting the execution environment or memory + usage. For a list of possible interpreter options, type java -h + or java -X at the command line.

+

These options may appear for all commands operating on a keystore:

+
+
-storetype storetype
+
This qualifier specifies the type of keystore to be instantiated. The + default keystore type is the one that is specified as the value of the "keystore.type" + property in the security properties file, which is returned by the static + getDefaultType method in java.security.KeyStore. +

 

+
-keystore keystore
+
The keystore (database file) location. Defaults to the file .keystore + in the user's home directory, as determined by the "user.home" system + property, whose value is described in + + Keystore Location. +

 

+
-storepass storepass
+
The password which is used to protect the integrity of the keystore. +

storepass must be at least 6 characters long. It must be + provided to all commands that access the keystore contents. For such + commands, if a -storepass option is not provided at the + command line, the user is prompted for it.

+

When retrieving information from the keystore, the password is + optional; if no password is given, the integrity of the retrieved + information cannot be checked and a warning is displayed.

+

Be careful with passwords - see + + Warning Regarding Passwords.

+

 

+
-provider provider-class-name
+
Used to specify the name of cryptographic service provider's master + class file when the service provider is not listed in the security + properties file.

 

+
+
+

Warning Regarding Passwords

+
+

Most commands operating on a keystore require the store password. Some + commands require a private key password.

+

Passwords can be specified on the command line (in the -storepass + and -keypass options, respectively). However, a password should + not be specified on a command line or in a script unless it is for testing + purposes, or you are on a secure system.

+

If you don't specify a required password option on a command line, you + will be prompted for it. When typing in a password at the password prompt, + the password is currently echoed (displayed exactly as typed), so be careful + not to type it in front of anyone.

+
+
+

COMMANDS

+
+

See also the + + Command and Option Notes.

+

Adding Data to the Keystore

+
+
+
-genkey + {-alias alias} {-keyalg keyalg} {-keysize keysize} {-sigalg + sigalg} [-dname dname] [-keypass keypass] {-validity + valDays} {-storetype storetype} {-keystore keystore} + [-storepass storepass] [-provider provider_class_name] {-v} + {-Jjavaoption}
+
Generates a key pair (a public key and associated private key). Wraps + the public key into an X.509 v1 self-signed certificate, which is stored + as a single-element certificate chain. This certificate chain and the + private key are stored in a new keystore entry identified by alias. +

keyalg specifies the algorithm to be used to generate the key + pair, and keysize specifies the size of each key to be generated. + sigalg specifies the algorithm that should be used to sign the + self-signed certificate; this algorithm must be compatible with keyalg. + See + + Supported Algorithms and Key Sizes.

+

dname specifies the + + X.500 Distinguished Name to be associated with alias, and is + used as the issuer and subject fields in the + self-signed certificate. If no distinguished name is provided at the + command line, the user will be prompted for one.

+

keypass is a password used to protect the private key of the + generated key pair. If no password is provided, the user is prompted for + it. If you press RETURN at the prompt, the key password is set to the same + password as that used for the keystore. keypass must be at least + 6 characters long. Be careful with passwords - see + + Warning Regarding Passwords.

+

valDays tells the number of days for which the certificate + should be considered valid.

+

 

+
-import + {-alias alias} {-file cert_file} [-keypass keypass] + {-noprompt} {-trustcacerts} {-storetype storetype} {-keystore + keystore} [-storepass storepass] [-provider + provider_class_name] {-v} {-Jjavaoption}
+
Reads the certificate or certificate chain (where the latter is + supplied in a PKCS#7 formatted reply) from the file cert_file, and + stores it in the keystore entry identified by alias. If no file is + given, the certificate or PKCS#7 reply is read from stdin. keytool + can import X.509 v1, v2, and v3 certificates, and PKCS#7 formatted + certificate chains consisting of certificates of that type. The data to be + imported must be provided either in binary encoding format, or in + printable encoding format (also known as Base64 encoding) as defined by + the + + Internet RFC 1421 standard. In the latter case, the encoding must be + bounded at the beginning by a string that starts with "-----BEGIN", and + bounded at the end by a string that starts with "-----END". +

When importing a new trusted certificate, alias must not + yet exist in the keystore. Before adding the certificate to the keystore, + keytool tries to verify it by attempting to construct a chain of + trust from that certificate to a self-signed certificate (belonging to a + root CA), using trusted certificates that are already available in the + keystore.

+

If the -trustcacerts option has been specified, additional + certificates are considered for the chain of trust, namely the + certificates in a file named "cacerts", which resides in the JDK + security properties directory, java.home\lib\security, + where java.home is the runtime environment's directory (the jre + directory in the SDK or the top-level directory of the Java 2 Runtime + Environment). The "cacerts" file represents a system-wide keystore with CA + certificates. System administrators can configure and manage that file + using keytool, specifying "jks" as the keystore type. The "cacerts" + keystore file ships with five VeriSign root CA certificates with the + following X.500 distinguished names:

+
1. OU=Class 1 Public Primary Certification Authority, O="VeriSign, Inc.",
+C=US
+
+2. OU=Class 2 Public Primary Certification Authority, O="VeriSign,
+Inc.", C=US
+
+3. OU=Class 3 Public Primary Certification Authority,
+O="VeriSign, Inc.", C=US
+
+4. OU=Class 4 Public Primary Certification
+Authority, O="VeriSign, Inc.", C=US
+
+5. OU=Secure Server Certification
+Authority, O="RSA Data Security, Inc.", C=US
+
+

The initial password of the "cacerts" keystore file is "changeit". + System administrators should change that password and the default access + permission of that file upon installing the JDK.

+

If keytool fails to establish a trust path from the certificate + to be imported up to a self-signed certificate (either from the keystore + or the "cacerts" file), the certificate information is printed out, and + the user is prompted to verify it, e.g., by comparing the displayed + certificate fingerprints with the fingerprints obtained from some other + (trusted) source of information, which might be the certificate owner + himself/herself. Be very careful to ensure the certificate is valid prior + to importing it as a "trusted" certificate! -- see + + WARNING Regarding Importing Trusted Certificates. The user then has + the option of aborting the import operation. If the -noprompt + option is given, however, there will be no interaction with the user.

+

When importing a certificate reply, the certificate reply is + validated using trusted certificates from the keystore, and optionally + using the certificates configured in the "cacerts" keystore file (if the + -trustcacerts option was specified).

+

If the reply is a single X.509 certificate, keytool attempts to + establish a trust chain, starting at the certificate reply and ending at a + self-signed certificate (belonging to a root CA). The certificate reply + and the hierarchy of certificates used to authenticate the certificate + reply form the new certificate chain of alias.

+

If the reply is a PKCS#7 formatted certificate chain, the chain is + first ordered (with the user certificate first and the self-signed root CA + certificate last), before keytool attempts to match the root CA + certificate provided in the reply with any of the trusted certificates in + the keystore or the "cacerts" keystore file (if the -trustcacerts + option was specified). If no match can be found, the information of the + root CA certificate is printed out, and the user is prompted to verify it, + e.g., by comparing the displayed certificate fingerprints with the + fingerprints obtained from some other (trusted) source of information, + which might be the root CA itself. The user then has the option of + aborting the import operation. If the -noprompt option is + given, however, there will be no interaction with the user.

+

The new certificate chain of alias replaces the old certificate + chain associated with this entry. The old chain can only be replaced if a + valid keypass, the password used to protect the private key of the + entry, is supplied. If no password is provided, and the private key + password is different from the keystore password, the user is prompted for + it. Be careful with passwords - see + + Warning Regarding Passwords.

+

 

+
-selfcert + {-alias alias} {-sigalg sigalg} {-dname dname} + {-validity valDays} [-keypass keypass] {-storetype + storetype} {-keystore keystore} [-storepass storepass] + [-provider provider_class_name] {-v} {-Jjavaoption} +
+
Generates an X.509 v1 self-signed certificate, using keystore + information including the private key and public key associated with + alias. If dname is supplied at the command line, it is used as + the + + X.500 Distinguished Name for both the issuer and + subject of the certificate. Otherwise, the X.500 Distinguished Name + associated with alias (at the bottom of its existing certificate + chain) is used. +

The generated certificate is stored as a single-element certificate + chain in the keystore entry identified by alias, where it + replaces the existing certificate chain.

+

sigalg specifies the algorithm that should be used to sign the + certificate. See + + Supported Algorithms and Key Sizes.

+

In order to access the private key, the appropriate password must be + provided, since private keys are protected in the keystore with a + password. If keypass is not provided at the command line, and is + different from the password used to protect the integrity of the keystore, + the user is prompted for it. Be careful with passwords - see + + Warning Regarding Passwords.

+

valDays tells the number of days for which the certificate + should be considered valid.

+

 

+
-identitydb + {-file idb_file} {-storetype storetype} {-keystore + keystore} [-storepass storepass] [-provider + provider_class_name] {-v} {-Jjavaoption}
+
Reads the JDK 1.1.x-style identity database from the file idb_file, + and adds its entries to the keystore. If no file is given, the identity + database is read from stdin. If a keystore does not exist, it is created. +

Only identity database entries ("identities") that were marked as + trusted will be imported in the keystore. All other identities will be + ignored. For each trusted identity, a keystore entry will be created. The + identity's name is used as the "alias" for the keystore entry.

+

The private keys from trusted identities will all be encrypted under + the same password, storepass. This is the same password that is + used to protect the keystore's integrity. Users can later assign + individual passwords to those private keys by using the "-keypasswd" + keytool command option.

+

An identity in an identity database may hold more than one certificate, + each certifying the same public key. But a keystore key entry for a + private key has that private key and a single "certificate chain" + (initially just a single certificate), where the first certificate in the + chain contains the public key corresponding to the private key. When + importing the information from an identity, only the first certificate of + the identity is stored in the keystore. This is because an identity's name + in an identity database is used as the alias for its corresponding + keystore entry, and alias names are unique within a keystore,

+
+

 

+
+

Exporting Data

+
+
+
-certreq + {-alias alias} {-sigalg sigalg} {-file certreq_file} + [-keypass keypass] {-storetype storetype} {-keystore + keystore} [-storepass storepass] [-provider + provider_class_name] {-v} {-Jjavaoption}
+
Generates a Certificate Signing Request (CSR), using the PKCS#10 + format. +

A CSR is intended to be sent to a certificate authority (CA). The CA + will authenticate the certificate requestor (usually off-line) and will + return a certificate or certificate chain, used to replace the existing + certificate chain (which initially consists of a self-signed certificate) + in the keystore.

+

The private key and X.500 Distinguished Name associated with alias + are used to create the PKCS#10 certificate request. In order to access the + private key, the appropriate password must be provided, since private keys + are protected in the keystore with a password. If keypass is not + provided at the command line, and is different from the password used to + protect the integrity of the keystore, the user is prompted for it.

+

Be careful with passwords - see + + Warning Regarding Passwords.

+

sigalg specifies the algorithm that should be used to sign the + CSR. See + + Supported Algorithms and Key Sizes.

+

The CSR is stored in the file certreq_file. If no file is + given, the CSR is output to stdout.

+

Use the import command to import the response from the CA.

+

 

+
-export + {-alias alias} {-file cert_file} {-storetype storetype} + {-keystore keystore} [-storepass storepass] [-provider + provider_class_name] {-rfc} {-v} {-Jjavaoption}
+
Reads (from the keystore) the certificate associated with alias, + and stores it in the file cert_file. +

If no file is given, the certificate is output to stdout.

+

The certificate is by default output in binary encoding, but will + instead be output in the printable encoding format, as defined by the + + Internet RFC 1421 standard, if the -rfc option is + specified.

+

If alias refers to a trusted certificate, that certificate is + output. Otherwise, alias refers to a key entry with an associated + certificate chain. In that case, the first certificate in the chain is + returned. This certificate authenticates the public key of the entity + addressed by alias.

+

 

+
+
+

Displaying Data

+
+
+
-list + {-alias alias} {-storetype storetype} {-keystore keystore} + [-storepass storepass] [-provider provider_class_name] {-v | + -rfc} {-Jjavaoption}
+
Prints (to stdout) the contents of the keystore entry identified by + alias. If no alias is specified, the contents of the entire keystore + are printed. +

This command by default prints the MD5 fingerprint of a certificate. If + the -v option is specified, the certificate is printed in + human-readable format, with additional information such as the owner, + issuer, and serial number. If the -rfc option is specified, + certificate contents are printed using the printable encoding format, as + defined by the + + Internet RFC 1421 standard

+

You cannot specify both -v and -rfc.

+

 

+
-printcert + {-file cert_file} {-v} {-Jjavaoption} +
+
Reads the certificate from the file + cert_file, and prints its contents in a human-readable format. If no + file is given, the certificate is read from stdin. +

The certificate may be either binary encoded or + in printable encoding format, as defined by the + + Internet RFC 1421 standard.

+

Note: This option can be used independently of a keystore.

+

 

+
+
+

Managing the Keystore

+
+
+
-keyclone + {-alias alias} [-dest dest_alias] [-keypass keypass] + [-new new_keypass] {-storetype storetype} {-keystore + keystore} [-storepass storepass] [-provider + provider_class_name] {-v} {-Jjavaoption}
+
Creates a new keystore entry, which has the same private key and + certificate chain as the original entry. +

The original entry is identified by alias (which defaults to "mykey" + if not provided). The new (destination) entry is identified by + dest_alias. If no destination alias is supplied at the command line, + the user is prompted for it.

+

If the private key password is different from the keystore password, + then the entry will only be cloned if a valid keypass is supplied. + This is the password used to protect the private key associated with + alias. If no key password is supplied at the command line, and the + private key password is different from the keystore password, the user is + prompted for it. The private key in the cloned entry may be protected with + a different password, if desired. If no -new option is + supplied at the command line, the user is prompted for the new entry's + password (and may choose to let it be the same as for the cloned entry's + private key).

+

Be careful with passwords - see + + Warning Regarding Passwords.

+

This command can be used to establish multiple certificate chains + corresponding to a given key pair, or for backup purposes.

+

 

+
-storepasswd [-new + new_storepass] {-storetype storetype} {-keystore keystore} + [-storepass storepass] [-provider provider_class_name] {-v} + {-Jjavaoption}
+
Changes the password used to protect the integrity of the keystore + contents. The new password is new_storepass, which must be at + least 6 characters long.

Be careful with passwords - see + + Warning Regarding Passwords.

+

 

+
-keypasswd {-alias alias} + [-keypass old_keypass] [-new new_keypass] {-storetype + storetype} {-keystore keystore} [-storepass storepass] + [-provider provider_class_name] {-v} {-Jjavaoption} +
+
Changes the password under which the private key identified by + alias is protected, from old_keypass to new_keypass. +

If the -keypass option is not provided at the command + line, and the private key password is different from the keystore + password, the user is prompted for it.

+

If the -new option is not provided at the command line, + the user is prompted for it.

+

 

+

Be careful with passwords - see + + Warning Regarding Passwords.

+

 

+
-delete + [-alias alias] {-storetype storetype} {-keystore keystore} + [-storepass storepass] [-provider provider_class_name] {-v} + {-Jjavaoption}
+
Deletes from the keystore the entry identified by alias. The + user is prompted for the alias, if no alias is provided at the command + line.

 

+
+
+

Getting Help

+
+
+
-help
+
Lists all the commands and their options.

 

+
+
+
+

EXAMPLES

+
+

Suppose you want to create a keystore for managing your public/private key + pair and certificates from entities you trust.

+

Generating Your Key Pair

+
+

The first thing you need to do is create a keystore and generate the key + pair. You could use a command such as the following:

+
    keytool -genkey -dname "cn=Mark Jones, ou=JavaSoft, o=Sun, c=US" 
+      -alias business -keypass kpi135 -keystore C:\working\mykeystore 
+      -storepass ab987c -validity 180
+
+

(Please note: This must be typed as a single line. Multiple lines are + used in the examples just for legibility purposes.)

+

This command creates the keystore named "mykeystore" in the "working" + directory on the C drive (assuming it doesn't already exist), and assigns it + the password "ab987c". It generates a public/private key pair for the entity + whose "distinguished name" has a common name of "Mark Jones", organizational + unit of "JavaSoft", organization of "Sun" and two-letter country code of + "US". It uses the default "DSA" key generation algorithm to create the keys, + both 1024 bits long.

+

It creates a self-signed certificate (using the default "SHA1withDSA" + signature algorithm) that includes the public key and the distinguished name + information. This certificate will be valid for 180 days, and is associated + with the private key in a keystore entry referred to by the alias + "business". The private key is assigned the password "kpi135".

+

The command could be significantly shorter if option defaults were + accepted. As a matter of fact, no options are required; defaults are used + for unspecified options that have default values, and you are prompted for + any required values. Thus, you could simply have the following:

+
    keytool -genkey 
+
+

In this case, a keystore entry with alias "mykey" is created, with a + newly-generated key pair and a certificate that is valid for 90 days. This + entry is placed in the keystore named ".keystore" in your home directory. + (The keystore is created if it doesn't already exist.) You will be prompted + for the distinguished name information, the keystore password, and the + private key password.

+

The rest of the examples assume you executed the -genkey + command without options specified, and that you responded to the prompts + with values equal to those given in the first -genkey command, + above (a private key password of "kpi135", etc.)

+
+

Requesting a Signed Certificate from a Certification Authority

+
+

So far all we've got is a self-signed certificate. A certificate is more + likely to be trusted by others if it is signed by a Certification Authority + (CA). To get such a signature, you first generate a Certificate Signing + Request (CSR), via the following:

+
    keytool -certreq -file MarkJ.csr
+
+

This creates a CSR (for the entity identified by the default alias "mykey") + and puts the request in the file named "MarkJ.csr". Submit this file to a + CA, such as VeriSign, Inc. The CA will authenticate you, the requestor + (usually off-line), and then will return a certificate, signed by them, + authenticating your public key. (In some cases, they will actually return a + chain of certificates, each one authenticating the public key of the signer + of the previous certificate in the chain.)

+
+

Importing a Certificate for the CA

+
+

You need to replace your self-signed certificate with a certificate + chain, where each certificate in the chain authenticates the public key of + the signer of the previous certificate in the chain, up to a "root" CA.

+

Before you import the certificate reply from a CA, you need one or more + "trusted certificates" in your keystore or in the cacerts + keystore file (which is described in + + import command):

+
    +
  • If the certificate reply is a certificate chain, you just need the top + certificate of the chain (that is, the "root" CA certificate + authenticating that CA's public key).

     

  • +
  • If the certificate reply is a single certificate, you need a + certificate for the issuing CA (the one that signed it), and if that + certificate is not self-signed, you need a certificate for its signer, and + so on, up to a self-signed "root" CA certificate.
  • +
+

The "cacerts" keystore file ships with five VeriSign root CA + certificates, so you probably won't need to import a VeriSign certificate as + a trusted certificate in your keystore. But if you request a signed + certificate from a different CA, and a certificate authenticating that CA's + public key hasn't been added to "cacerts", you will need to import a + certificate from the CA as a "trusted certificate".

+

A certificate from a CA is usually either self-signed, or signed by + another CA (in which case you also need a certificate authenticating that + CA's public key). Suppose company ABC, Inc., is a CA, and you obtain a file + named "ABCCA.cer" that is purportedly a self-signed certificate from ABC, + authenticating that CA's public key.

+

Be very careful to ensure the certificate is valid prior to importing it + as a "trusted" certificate! View it first (using the keytool -printcert + command, or the keytool -import command without the + -noprompt option), and make sure that the displayed certificate + fingerprint(s) match the expected ones. You can call the person who sent the + certificate, and compare the fingerprint(s) that you see with the ones that + they show (or that a secure public key repository shows). Only if the + fingerprints are equal is it guaranteed that the certificate has not been + replaced in transit with somebody else's (for example, an attacker's) + certificate. If such an attack took place, and you did not check the + certificate before you imported it, you would end up trusting anything the + attacker has signed.

+

If you trust that the certificate is valid, then you can add it to your + keystore via the following:

+
    keytool -import -alias abc -file ABCCA.cer
+
+

This creates a "trusted certificate" entry in the keystore, with the data + from the file "ABCCA.cer", and assigns the alias "abc" to the entry.

+
+

Importing the Certificate Reply from the CA

+
+

Once you've imported a certificate authenticating the public key of the + CA you submitted your certificate signing request to (or there's already + such a certificate in the "cacerts" file), you can import the certificate + reply and thereby replace your self-signed certificate with a certificate + chain. This chain is the one returned by the CA in response to your request + (if the CA reply is a chain), or one constructed (if the CA reply is a + single certificate) using the certificate reply and trusted certificates + that are already available in the keystore where you import the reply or in + the "cacerts" keystore file.

+

For example, suppose you sent your certificate signing request to + VeriSign. You can then import the reply via the following, which assumes the + returned certificate is named "VSMarkJ.cer":

+
    keytool -import -trustcacerts -file VSMarkJ.cer
+
+
+

Exporting a Certificate Authenticating Your Public Key

+
+

Suppose you have used the + + jarsigner tool to sign a Java ARchive (JAR) file. Clients that want to + use the file will want to authenticate your signature.

+

One way they can do this is by first importing your public key + certificate into their keystore as a "trusted" entry. You can export the + certificate and supply it to your clients. As an example, you can copy your + certificate to a file named MJ.cer via the following, assuming + the entry is aliased by "mykey":

+
    keytool -export -alias mykey -file MJ.cer
+
+

Given that certificate, and the signed JAR file, a client can use the + jarsigner tool to authenticate your signature.

+
+

Changing Your Distinguished Name but Keeping your Key Pair

+
+

Suppose your distinguished name changes, for example because you have + changed departments or moved to a different city. If desired, you may still + use the public/private key pair you've previously used, and yet update your + distinguished name. For example, suppose your name is Susan Miller, and you + created your initial key entry with the alias sMiller and the + distinguished name

+
  "cn=Susan Miller, ou=Finance Department, o=BlueSoft, c=us"
+
+

Suppose you change from the Finance Department to the Accounting + Department. You can still use the previously-generated public/private key + pair and yet update your distinguished name by doing the following. First, + copy (clone) your key entry:

+
    keytool -keyclone -alias sMiller -dest sMillerNew
+
+

(This prompts for the store password and for the initial and destination + private key passwords, since they aren't provided at the command line.) Now + you need to change the certificate chain associated with the copy, so that + the first certificate in the chain uses your different distinguished name. + Start by generating a self-signed certificate with the appropriate name:

+
    keytool -selfcert -alias sMillerNew
+      -dname "cn=Susan Miller, ou=Accounting Department, o=BlueSoft, c=us"
+
+

Then generate a Certificate Signing Request based on the information in + this new certificate:

+
    keytool -certreq -alias sMillerNew
+
+

When you get the CA certificate reply, import it:

+
    keytool -import -alias sMillerNew -file VSSMillerNew.cer
+
+

After importing the certificate reply, you may want to remove the initial + key entry that used your old distinguished name:

+
    keytool -delete -alias sMiller
+
+
+
+

SEE ALSO

+
+ +
+
+ + + + + +
+ + Copyright © 1995, 2010 Oracle and/or its + affiliates. All rights reserved. + Sun +
+ + \ No newline at end of file diff --git a/help/require.html b/help/require.html new file mode 100644 index 0000000..861b543 --- /dev/null +++ b/help/require.html @@ -0,0 +1,88 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Requirements

+

All software applications have hardware requirements (and +sometimes have software requirements as well). BrowzOS is no different, except +that the software requirements for BrowzOS are very dynamic and, at times, may +differ depending on the way it is used. Even though this project has been +created and tested using a computer with 512 MB of memory and a 1.6 GHz +microprocessor, there is nothing that dictates the absolute minimum and absolute +maximum hardware requirements for any computer.

+

These are the basic requirements for any client computer user +who wishes to run BrowzOS from the internet:

+
  • Any computer with internet access and any client-side operating +system (i.e.: GNU/Linux, Windows, Mac OS X, etc.)
  • +
  • Any mainstream web browser with the ability to run web applets +and scripts (i.e.: Internet Explorer 6 and over, Mozilla 1.7 and over, Mozilla +Firefox, Safari, Netscape 6 and over, etc.)
  • +
  • Software drivers (i.e.: ActiveX controls and/or web browser +plug-ins) which enable mainstream web browsers to properly run certain web +applets and scripts (examples are Java, Flash, Shockwave, JavaScript, Visual +BASIC script, etc.)
  • +
+

These are the basic requirements for any computer user +who wishes to run BrowzOS from the hard drive:

+
  • Any computer with internet access and any client-side operating +system (i.e.: GNU/Linux, Windows, Mac OS X, etc.)
  • +
  • Any file archiving program or file compression program with (at +least) ZIP file read/write access
  • +
  • Any mainstream web browser with the ability to run web applets +and scripts (i.e.: Internet Explorer 6 and over, Mozilla 1.7 and over, Mozilla +Firefox, Safari, Netscape 6 and over, etc.)
  • +
  • Software drivers (i.e.: ActiveX controls and/or web browser +plug-ins) which enable mainstream web browsers to properly run certain web +applets and scripts (examples are Java, Flash, Shockwave, JavaScript, Visual +BASIC script, etc.)
  • +
  • Software drivers (i.e.: programming language run-time files) +which enable mainstream operating systems to properly run certain web applets +and scripts (such as ASP, .NET framework, PHP, SQL, etc.)
  • +
+

These are the basic requirements for web designers who wish to run BrowzOS +from a server:

+
  • Any client computer with internet access and any client-side operating +system (i.e.: GNU/Linux, Windows, Mac OS X, etc.)
  • +
  • Any file archiving program or file compression program with (at +least) ZIP file read/write access
  • +
  • Any SSH and/or FTP client program to access a server across the +internet
  • +
  • Any text editor, rich text editor or "what-you-see-is-what +you-get" editor (such as Frontpage, SciTe, Notepad, Vi, Microsoft Word, etc.)
  • +
  • Any server computer running server software (like Apache, IIS or +similar)  and any mainstream server operating system (i.e.: FreeBSD, +Windows Server 2003, etc.)
  • +
  • Any mainstream web browser with the ability to run web applets +and scripts (i.e.: Internet Explorer 6 and over, Mozilla 1.7 and over, Mozilla +Firefox, Safari, Netscape 6 and over, etc.)
  • +
  • Software drivers (i.e.: ActiveX controls and/or web browser +plug-ins) for both client computers and servers which enable mainstream web browsers to properly run certain web +applets and scripts (examples are Java, Flash, Shockwave, JavaScript, Visual +BASIC script, etc.)
  • +
  • Software drivers (i.e.: programming language run-time files) +which enable both mainstream server and client operating systems to properly run +certain web applets and scripts (like ASP, .NET framework, PHP, SQL, etc.)
  • +
+


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help/run.html b/help/run.html new file mode 100644 index 0000000..b0f2fcb --- /dev/null +++ b/help/run.html @@ -0,0 +1,55 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Installing, Running And Using "Programs" In BrowzOS

+

Installing "applications" into BrowzOS is quite easy if the +package has been extracted to a client computer's hard drive; it is simply a +matter of editing the package's web pages and scripts by adding/removing icons +and applets. The contents of the BrowzOS package is extremely modular, since each important applet +and script has its own directory and index file. So, installing an "application" +isn't too complicated, considering that a fair amount of HTML programming is required. In simple terms, +installing "applications" requires the creation of a folder in the main directory (to store +the needed applet, index and all other needed files,) as well as the editing of the Dock and Desktop scripts +in the main package.

+

Sadly, installing "programs" in BrowzOS while accessing it from a web +browser has not been implemented and is, therefore, impossible at the moment (not +to say that it's outright impossible, though). Theoretically, a web designer can have all +the needed applets installed on a server as (otherwise hidden) available +"installation" packages, then run a script which can customise the Desktop and +Dock's icons to unhide the hidden applets, thus "installing a program" into +BrowzOS through a web browser.

+

Running each "program" from BrowzOS (regardless of the package being installed on a client or server computer) is as simple as opening and using any other program on any mainstream operating system. As stated previously, an "application" can be executed by clicking on the Desktop icon's name or on the Dock icon's image with the mouse cursor. The "program" will then open in its own pop-up window. Closing the "program" is a matter of closing aid window.

+

The fact that these "applications" are so similar to the +programs available on any mainstream operating system means using each individual +"application" does not require users to have special training. However, as each +"program" is different and unique, instructions and tutorials for each +individual applet have been included inside their respective folders in the +BrowzOS package (this is especially critical for web designers). Please refer to +the included tutorials and instructions before use and modification (they're either found in text files +or in the form of comments within the index file's HTML code). If instructions for any particular +applet cannot be found, please consult the Credits page of this manual for a +list of applet/script creators and their sites. If said sites are unreachable, please consult this website's forum.

+


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help/sunlogo64x30.gif b/help/sunlogo64x30.gif new file mode 100644 index 0000000..98e0350 Binary files /dev/null and b/help/sunlogo64x30.gif differ diff --git a/help/technic.html b/help/technic.html new file mode 100644 index 0000000..66c602c --- /dev/null +++ b/help/technic.html @@ -0,0 +1,127 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Technical Information (For Web Designers And Applet Programmers)

+

As mentioned previously, these are the basic requirements for web designers who wish to run BrowzOS +from a server:

+
  • Any client computer with internet access and any client-side operating +system (i.e.: GNU/Linux, Windows, Mac OS X, etc.)
  • +
  • Any file archiving program or file compression program with ZIP +file read/write access
  • +
  • Any SSH and/or FTP client program to access a server across the +internet
  • +
  • Any text editor, rich text editor or "what-you-see-is-what +you-get" editor (such as Frontpage, SciTe, Notepad, Vi, Microsoft Word, etc.)
  • +
  • Any server computer running server software (like Apache, IIS or +similar)  and any mainstream server operating system (i.e.: FreeBSD, +Windows Server 2003, etc.)
  • +
  • Any mainstream web browser with the ability to run web applets +and scripts (i.e.: Internet Explorer 6 and over, Mozilla 1.7 and over, Mozilla +Firefox, Safari, Netscape 6 and over, etc.)
  • +
  • Software drivers (i.e.: ActiveX controls and/or web browser +plug-ins) for both client computers and servers which enable mainstream web browsers to properly run certain web +applets and scripts (examples are Java, Flash, Shockwave, JavaScript, Visual +BASIC script, etc.)
  • +
  • Software drivers (i.e.: programming language run-time files) +which enable both mainstream server and client operating systems to properly run +certain web applets and scripts (like ASP, .NET framework, PHP, SQL, etc.)
  • +
+

Also, there are 3 steps for web designers to get started with +BrowzOS:

+
  1. Download the package (it is + available in multiple file formats, including ZIP, 7zip, RAR, etc.)
  2. +
  3. Extract the package and make any necessary changes to the + scripts, web pages and applets in the package (this would also include adding + & removing various applets and scripts as needed)
  4. +
  5. Upload the files to any directory on the server in use
  6. +
+

Edit the web pages as needed. This includes adding/removing +scripts and applets as needed, editing scripts as needed, changing the file +names of the HTML pages as needed and editing the contents of the HTML pages +as needed (i.e.: adding/removing links, icons, etc.) +

If an applet or script requires directory/file access on a web server, log into the +server (using either an FTP or SSH client) and change the file attributes of the +directory to allow full read/write/execute permissions to the Owner, Group and +Public user groups. In an FTP client, this is done by right-clicking on the folder, +selecting File Attributes and either selecting all the check boxes or changing +the numeric value to "0777" or "777." In an SSH client, log into the UNIX shell +and type either of the following commands:

+
    +
  • chmod a+rwx foldername
  • +
  • chmod 0777 foldername
  • +
  • chmod 777 foldername
  • +
+

Where "foldername" is the name of the folder whose attributes need to be changed. This can +also be done to individual files that require full access and modification such as text files and database files; +simply substitute "foldername" with the name of the file whose attributes need to be changed.

+

If the extracted/edited package is uploaded into a website's +folder on a server (i.e.: if the package has been uploaded to + +ftp://www.yourdomain.com/yourdomain.com/ or + +ftp://yoursubdomain.domain.com/yoursubdomain.domain.com/,) open the +web browser, type the web address and the index page is loaded (which can be +changed as needed). If the extracted/edited package is uploaded into a folder +residing inside an actual domain or sub-domain's main website folder, changes +are need to the already-existing index page in order to allow navigation to the BrowzOS +index page (again, anyone can change the BrowzOS index page as needed).

+

When designing applets that require direct file access on a +client computer from a server, the best programming language to use for creating +the applet is Java. Typical Java applets do not have file access on a client +computer for security reasons. However, Java applets may be signed with a +certificate, which will give them the ability to access the client computer's +hard drive. Usually, web designers who work for professional organisations and +corporations can apply for a certificate from a Certificate Authority (CA,) +which can cost $150-$400 per year. For other web designers (especially hobbyists +and open-source web designers) who do not have the money to spend for a +certificate from a CA, Java applets can be self-signed (at no monetary cost) +with a home-made test certificate.

+

Even though home-made test certificates are free when compared to CA +certificates, the only true difference between them is the +fact that, when self-signed, the web browser will not recognise the applet as +having been created by a trustworthy source; it will be identified as +trustworthy only when signed using a CA certificate. Because of this, a client +computer user must grant permission for the applet to run and install the test +certificate. In any case, whether or not it is signed using a CA or +test certificate, the Java applet will still retain the unrestricted +functionality of direct file access on a client computer's hard drive.

+

When using Sun's Java SDK, the simplest way to self-sign a Java +applet is by completing the following commands in a command prompt:

+
  1. keytool -genkey -dname "cn=Creator" -validity 365 -storepass StorePassword -keypass KeyPassword
  2. +
  3. keytool -selfcert
  4. +
  5. jarsigner java_applet.jar -keystore StorePassword -keypass KeyPassword mykey
+

Where "StorePassword" and "KeyPassword" are the desired keystore +and key passwords for the certificate, "Creator" is the name of the applet's +creator, "365" forces the certificate to be valid for 365 days (use any value if desired) and "mykey" is +the default keystore alias (a.k.a.: the default name of the generated +certificate).

+

Please refer to the following guides for the applet-signing +tools available in Sun's Java SDK:

+ +


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help/usage.html b/help/usage.html new file mode 100644 index 0000000..6cda351 --- /dev/null +++ b/help/usage.html @@ -0,0 +1,47 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Using The Environment

+

Using BrowzOS is as easy as using any other mainstream operating +system. As a matter of fact, it has never been easier to navigate the +environment and complete different tasks. When BrowzOS starts, the web browser's +window will transform into an eye-pleasing Desktop, +complete with dynamic icons, an icon Dock and the ability to execute different functions.

+

On the Desktop, there are a number of icons, each representing +an executable "application." To open and run the "application," click on the +icon's name with the mouse cursor. To drag and relocate the icon to a different +part of the Desktop, click and hold the mouse cursor on top of the icon's image +and simply drag it to the desired position (in much the same manner as the +desktop icons of Windows, Mac OS X and other operating systems).

+

The Dock is just as dynamic as the Desktop; when the mouse +cursor is moved over each icon in the Dock, the icons enlarge and they're +names appear above them (in much the same manner as the dock in +Mac OS X). Once the mouse cursor is hovering over it, the icon's image can be clicked +on and the "application" will be executed.

+

Regardless of using icons from the Dock or the Desktop to run an +"application," the "application." will open in its own pop-up window. Using "applications" in BrowzOS is just as easy +as using regular applications from within any mainstream operating system. Once completed with the "application," simply click on the "Back" button of the web browser with the mouse cursor to close it and return back to the Desktop. even though the Desktop area has been occupied by an "application," the Dock will remain in full view at all times (in the case a user chooses to close one "application" only to run another in a seamless fashion).

+


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help_offline/credits.html b/help_offline/credits.html new file mode 100644 index 0000000..a405b8c --- /dev/null +++ b/help_offline/credits.html @@ -0,0 +1,44 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Credits, The Licensing Of BrowzOS And Included Applets

+

THIS WEBSITE IS IN THE PUBLIC DOMAIN!!!!!

+

Special thanks to Jamie Sanders and Team vNES of +Virtual NES for their +support of this project. Thanks to Dan Davis for initially hosting this project and special thanks to Ruben Baca of KryptonWare Solutions LLC for being its current host.

+

All applets and scripts in this website are either freely +distributed or open-source, following various different licenses that may or may +not be compatible with the GNU General Public License +or each other. The scripts responsible for the BrowzOS +Desktop and Dock are copyrighted by Brian Gosselin and Nick La, respectively. The user authenticaction system is copyrighted by Zubrag.com. The project's PNG image file rendering script is copyrighted by TwinHelix. The Clock's time and calendar components are copyrighted by both Brian Gosselin and Itamar Arjuan. Madness Interactive is copyrighted by Linger +Media Company. The scientific calculator is copyrighted by Eni +Generalić of The Faculty Of Chemistry And Technology, Split, Croatia. The command line is copyrighted by Evgeny Stephanischev. The word processor is copyrighted by Frederico Caldeira Knabben. The spreadsheet is copyrighted by +Simple Groupware. The media player is copyrighted by +Jeroen +Wijering. The instant messenger is copyrighted by +phpFreeChat.

+

The page sources for this website are freely available and accessible under the terms of the GNU Affero General Public License. The documentation for this project is available under the GNU Free Documentation License.

+


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help_offline/index.html b/help_offline/index.html new file mode 100644 index 0000000..e20d36e --- /dev/null +++ b/help_offline/index.html @@ -0,0 +1,43 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Introducing BrowzOS, an open-source project to create a full-fledged, online +operating system (well, a sort of simulated operating system); the goal of this +project is to give BrowzOS the ability to manipulate files & folders on a client +and/or server computer's hard drive from within a web browser instead of using the +client's currently installed programs, as well as to encourage programmers to create +online applets (be they in any scripting language, Flash, Shockwave, Java, etc.) +with the ability to function as such. In spite of this, anyone is free to use +BrowzOS for any purpose, including the creation of applets that do NOT have +client (or server) computer file access.

+


+

Contents

+ + + + + + + + + \ No newline at end of file diff --git a/help_offline/inst.html b/help_offline/inst.html new file mode 100644 index 0000000..96af1ec --- /dev/null +++ b/help_offline/inst.html @@ -0,0 +1,44 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Downloading And Installing BrowzOS

+

As mentioned previously, BrowzOS is an open-source project; this +means all the HTML pages and applets contained in this package can be +downloaded and modified to the designer's content (so long as all credits and +ownership of copyrights are acknowledged and retained as per the GNU Affero GPL).

+

There are only 2 simple steps for the average computer user:

+
  1. Download the source package (available in many different archive file formats)
  2. +
  3. Extract the package to any folder on the hard drive and make any necessary changes to the + scripts, web pages and applets in the package (this would also include adding + and removing various applets and scripts as needed)
+

There are 3 steps for web designers to get started:

+
  1. Download the source package (available in many different archive file formats)
  2. +
  3. Extract the package and make any necessary changes to the + scripts, web pages and applets in the package (this would also include adding + & removing various applets and scripts as needed)
  4. +
  5. Upload the files to any directory on the server in use
+

Once the package has been downloaded and extracted, edit the web pages as needed. This includes adding/removing scripts and applets as needed, editing scripts as needed, changing the file names of the HTML pages as needed and editing the contents of the HTML pages as needed (i.e.: adding/removing links, icons, etc.) If BrowzOS is to be uploaded to a web server, do so once all the desired changes have been completed.

+


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help_offline/intro.html b/help_offline/intro.html new file mode 100644 index 0000000..f07f338 --- /dev/null +++ b/help_offline/intro.html @@ -0,0 +1,77 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Introduction And Overview

+

Welcome to BrowzOS!

+

BrowzOS is an online project whose chief goal is to address an +encourage the creativity of aspiring web designers and applet +programmers. Namely, the goal of BrowzOS is to give web designers and applet +programmers a solid base for their web-related projects; a simulated operating +system in which programmers and designers can create, display and run any web +applet of any kind, using any programming language and any scripting language. +Due to the fact that this project is a simulated operating system, BrowzOS +especially encourages Java programmers (from novices to professionals) to create +applets whose functionality extends across both the web server's and the client +computer's file system.

+

With the ever-evolving functionality and +popularity of the internet, this project presents a great opportunity that +anyone can take advantage of; this project is OPEN-SOURCE AND IN THE PUBLIC +DOMAIN. This means a number of things:

+
  • For hobbyist web designers, BrowzOS can be +freely downloaded, modified and used for such things as presenting +résumés, pictures, videos and music through the free/open-source +applets included in the main package. Setting up the site is simple and is +merely a matter of time to fully implement.
  • +
  • For novice/professional web designers and +programmers, this project can not only be used as a testing facility for other +open-source applet projects, but can also present the resulting creations in a +manner that familiarly resembles the look and feel of a mainstream operating +system (such as GNU/Linux, Mac OS X and Windows). Since the simulated environment +is completely user-customisable, multiple applets can be created, displayed and +executed from within the web browser in a matter of minutes. BrowzOS can even be +customised for use as a free/charitable online service to ease various +education/business-related undertakings in which collaboration is a necessity.
  • +
  • For educational institutions, non-governmental +organisations, governments of developing nations and charitable organisations, +this project can be used as a collaboration tool, easing any undertaking in +which project planning, research, documentation and constant communication +between team members is a must.
  • +
  • For the average computer user, this project +(and the included/resulting applets) can be used in hopes of providing a better +computing environment. With such applets whose functions fully replicate word +processors, music players and various other items that would otherwise be found +on an ordinary desktop computer's operating system, BrowzOS will reduce (or +hopefully, eliminate) the user's need to search for and install trial software, +bloatware, pirated programs and programs which are potentially malicious to both +the computer and its user.
  • +
+

Currently, this project comes packaged with a +word processor, scientific calculator, instant messenger, media player, clock and a tidy user interface, reminiscent of both Mac OS X +and GNU/Linux. Hopefully, this project will inspire others to contribute, be it +for fun or for providing a much needed public service, as intended by the +concept of truly free and open-source software.

+

Happy computing!

+


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help_offline/jarsigner.html b/help_offline/jarsigner.html new file mode 100644 index 0000000..9e953a6 --- /dev/null +++ b/help_offline/jarsigner.html @@ -0,0 +1,777 @@ + + + + +jarsigner - JAR Signing and Verification Tool + + + +

jarsigner - JAR Signing and Verification Tool

+
+

Generates signatures for Java ARchive (JAR) files, and verifies the + signatures of signed JAR files.

+
+

SYNOPSIS

+
+
jarsigner [ options ] jar-file alias
+jarsigner -verify [ options ] jar-file 
+
+
+

DESCRIPTION

+
+

The jarsigner tool is used for two purposes:

+
    +
  1. to sign Java ARchive (JAR) files, and

     

  2. +
  3. to verify the signatures and integrity of signed JAR files.
  4. +
+

The JAR feature enables the packaging of class files, images, sounds, and + other digital data in a single file for faster and easier distribution. A tool + named + + jar enables developers to produce JAR files. (Technically, any zip + file can also be considered a JAR file, although when created by jar or + processed by jarsigner, JAR files also contain a META-INF/MANIFEST.MF + file.)

+

A digital signature is a string of bits that is computed from some + data (the data being "signed") and the private key of an entity (a person, + company, etc.). Like a handwritten signature, a digital signature has many + useful characteristics:

+

 

+
    +
  • Its authenticity can be verified, via a computation that uses the public + key corresponding to the private key used to generate the signature.

     

  • +
  • It cannot be forged, assuming the private key is kept secret.

     

  • +
  • It is a function of the data signed and thus can't be claimed to be the + signature for other data as well.

     

  • +
  • The signed data cannot be changed; if it is, the signature will no + longer verify as being authentic.

     

  • +
+

In order for an entity's signature to be generated for a file, the entity + must first have a public/private key pair associated with it, and also one or + more certificates authenticating its public key. A certificate is a + digitally signed statement from one entity, saying that the public key of some + other entity has a particular value.

+

jarsigner uses key and certificate information from a keystore + to generate digital signatures for JAR files. A keystore is a database of + private keys and their associated X.509 certificate chains authenticating the + corresponding public keys. The + + keytool utility is used to create and administer keystores.

+

jarsigner uses an entity's private key to generate a signature. The + signed JAR file contains, among other things, a copy of the certificate from + the keystore for the public key corresponding to the private key used to sign + the file. jarsigner can verify the digital signature of the signed JAR + file using the certificate inside it (in its signature block file).

+

At this time, jarsigner can only sign JAR files created by the JDK + + jar tool or zip files. (JAR files are the same as zip files, except + they also have a META-INF/MANIFEST.MF file. Such a file will automatically be + created when jarsigner signs a zip file.)

+

The default jarsigner behavior is to sign a JAR (or zip) + file. Use the -verify option to instead have it verify a + signed JAR file.

+

Compatibility with JDK 1.1

+
+

The keytool and jarsigner tools completely replace the + javakey tool provided in JDK 1.1. These new tools provide more features + than javakey, including the ability to protect the keystore and + private keys with passwords, and the ability to verify signatures in + addition to generating them.

+

The new keystore architecture replaces the identity database that + javakey created and managed. There is no backwards compatibility between + the keystore format and the database format used by javakey in 1.1. + However,

+
    +
  • It is possible to import the information from an identity database + into a keystore, via the keytool -identitydb command.

     

  • +
  • jarsigner can sign JAR files also previously signed using + javakey.

     

  • +
  • jarsigner can verify JAR files signed using javakey. + Thus, it recognizes and can work with signer aliases that are from a JDK + 1.1 identity database rather than a Java 2 SDK keystore.
  • +
+

The following table explains how JAR files that were signed in JDK 1.1.x + are treated in the Java 2 SDK.

+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
JAR File TypeIdentity in 1.1 databaseTrusted Identity imported into Java 2 Platform keystore from 1.1 + database (4)Policy File grants privileges to Identity/AliasPrivileges Granted
Signed JARNONONODefault privileges granted to all code.
Unsigned JARNONONODefault privileges granted to all code.
Signed JARNOYESNODefault privileges granted to all code.
Signed JARYES/UntrustedNONODefault privileges granted to all code. (3)
Signed JARYES/UntrustedNOYESDefault privileges granted to all code. (1,3)
Signed JARNOYESYESDefault privileges granted to all code plus privileges granted in + policy file.
Signed JARYES/TrustedYESYESDefault privileges granted to all code plus privileges granted in + policy file. (2)
Signed JARYES/TrustedNONOAll privileges
Signed JARYES/TrustedYESNOAll privileges (1)
Signed JARYES/TrustedNOYESAll privileges (1)
+

 

+

Notes:

+
    +
  1. If an identity/alias is mentioned in the policy file, it must be + imported into the keystore for the policy file to have any effect on + privileges granted.

     

  2. +
  3. The policy file/keystore combination has precedence over a trusted + identity in the identity database.

     

  4. +
  5. Untrusted identities are ignored in the Java 2 SDK.

     

  6. +
  7. Only trusted identities can be imported into Java 2 SDK keystores.

     

  8. +
+
+

Keystore Aliases

+
+

All keystore entities are accessed via unique aliases.

+

When using jarsigner to sign a JAR file, you must specify the + alias for the keystore entry containing the private key needed to generate + the signature. For example, the following will sign the JAR file named "MyJarFile.jar", + using the private key associated with the alias "duke" in the keystore named + "mystore" in the "working" directory on the C drive. Since no output file is + specified, it overwrites MyJarFile.jar with the signed JAR file.

+
    jarsigner -keystore C:\working\mystore -storepass myspass
+      -keypass dukekeypasswd MyJarFile.jar duke 
+
+

Keystores are protected with a password, so the store password (in this + case "myspass") must be specified. You will be prompted for it if you don't + specify it on the command line. Similarly, private keys are protected in a + keystore with a password, so the private key's password (in this case "dukekeypasswd") + must be specified, and you will be prompted for it if you don't specify it + on the command line and it isn't the same as the store password.

+
+

Keystore Location

+
+

jarsigner has a -keystore option for specifying the + URL of the keystore to be used. The keystore is by default stored in a file + named .keystore in the user's home directory, as determined by the "user.home" + system property. Given user name uName, the "user.home" property + value defaults to

+
C:\Winnt\Profiles\uName on multi-user Windows NT systems
+C:\Windows\Profiles\uName on multi-user Windows 95 systems
+C:\Windows on single-user Windows 95 systems
+
+

Thus, if the user name is "cathy", "user.home" defaults to

+
C:\Winnt\Profiles\cathy on multi-user Windows NT systems
+C:\Windows\Profiles\cathy on multi-user Windows 95 systems
+
+
+

Keystore Implementation

+
+

The KeyStore class provided in the java.security + package supplies well-defined interfaces to access and modify the + information in a keystore. It is possible for there to be multiple different + concrete implementations, where each implementation is that for a particular + type of keystore.

+

Currently, there are two command-line tools that make use of keystore + implementations (keytool and jarsigner), and also a GUI-based + tool named Policy Tool. Since KeyStore is publicly + available, JDK users can write additional security applications that use it. +

+

There is a built-in default implementation, provided by Sun Microsystems. + It implements the keystore as a file, utilizing a proprietary keystore type + (format) named "JKS". It protects each private key with its individual + password, and also protects the integrity of the entire keystore with a + (possibly different) password.

+

Keystore implementations are provider-based. More specifically, the + application interfaces supplied by KeyStore are implemented in + terms of a "Service Provider Interface" (SPI). That is, there is a + corresponding abstract KeystoreSpi class, also in the + java.security package, which defines the Service Provider Interface + methods that "providers" must implement. (The term "provider" refers to a + package or a set of packages that supply a concrete implementation of a + subset of services that can be accessed by the Java Security API.) Thus, to + provide a keystore implementation, clients must implement a provider and + supply a KeystoreSpi subclass implementation, as described in + + How to Implement a Provider for the Java Cryptography Architecture.

+

Applications can choose different types of keystore + implementations from different providers, using the "getInstance" factory + method supplied in the KeyStore class. A keystore type defines + the storage and data format of the keystore information, and the algorithms + used to protect private keys in the keystore and the integrity of the + keystore itself. Keystore implementations of different types are not + compatible.

+

keytool works on any file-based keystore implementation. (It + treats the keytore location that is passed to it at the command line as a + filename and converts it to a FileInputStream, from which it loads the + keystore information.) The jarsigner and policytool tools, on + the other hand, can read a keystore from any location that can be specified + using a URL.

+

For jarsigner and keytool, you can specify a keystore type + at the command line, via the -storetype option. For Policy Tool, + you can specify a keystore type via the "Change Keystore" command in the + Edit menu.

+

If you don't explicitly specify a keystore type, the tools choose a + keystore implementation based simply on the value of the keystore.type + property specified in the security properties file. The security properties + file is called java.security, and it resides in the JDK security + properties directory, java.home\lib\security, where + java.home is the runtime environment's directory (the jre + directory in the SDK or the top-level directory of the Java 2 Runtime + Environment).

+

Each tool gets the keystore.type value and then examines all + the currently-installed providers until it finds one that implements + keystores of that type. It then uses the keystore implementation from that + provider.

+

The KeyStore class defines a static method named + getDefaultType that lets applications and applets retrieve the value + of the keystore.type property. The following line of code + creates an instance of the default keystore type (as specified in the + keystore.type property):

+
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+
+

The default keystore type is "jks" (the proprietary type of the keystore + implementation provided by Sun). This is specified by the following line in + the security properties file:

+
    keystore.type=jks
+
+

To have the tools utilize a keystore implementation other than the + default, change that line to specify a different keystore type.

+

For example, if you have a provider package that supplies a keystore + implementation for a keystore type called "pkcs12", change the line to

+
    keystore.type=pkcs12
+
+

Note: case doesn't matter in keystore type designations. For example, "JKS" + would be considered the same as "jks".

+
+

Supported Algorithms

+
+

At this time, jarsigner can sign a JAR file using either

+
    +
  • DSA (Digital Signature Algorithm) with the SHA-1 digest algorithm, or

     

  • +
  • the RSA algorithm with the MD5 digest algorithm.
  • +
+

That is, if the signer's public and private keys are DSA keys, + jarsigner will sign the JAR file using the "SHA1withDSA" algorithm. If + the signer's keys are RSA keys, jarsigner will attempt to sign the + JAR file using the "MD5withRSA" algorithm. This is only possible if there is + a statically installed + + provider supplying an implementation for the "MD5withRSA" algorithm. + (There is always a "SHA1withDSA" algorithm available, from the default "SUN" + provider.)

+
+

The Signed JAR File

+
+

When jarsigner is used to sign a JAR file, the output signed JAR + file is exactly the same as the input JAR file, except that it has two + additional files placed in the META-INF directory:

+
    +
  • a signature file, with a .SF extension, and

     

  • +
  • a signature block file, with a .DSA extension.
  • +
+

The base file names for these two files come from the value of the + -sigFile option. For example, if the option appears as

+
  -sigFile MKSIGN
+
+

the files are named "MKSIGN.SF" and "MKSIGN.DSA".

+

If no -sigfile option appears on the command line, the base + file name for the .SF and .DSA files will be the first 8 characters of the + alias name specified on the command line, all converted to upper case. If + the alias name has fewer than 8 characters, the full alias name is used. If + the alias name contains any characters that are not allowed in a signature + file name, each such character is converted to an underscore ("_") character + in forming the file name. Legal characters include letters, digits, + underscores, and hyphens.

+

The Signature (.SF) File

+
+

A signature file (the .SF file) looks similar to the manifest file that + is always included in a JAR file when jarsigner is used to sign the + file. That is, for each source file included in the JAR file, the .SF file + has three lines, just as in the manifest file, listing the following:

+
    +
  • the file name,

     

  • +
  • the name of the digest algorithm used (SHA), and +

     

  • +
  • a SHA digest value.
  • +
+

In the manifest file, the SHA digest value for each source file is the + digest (hash) of the binary data in the source file. In the .SF file, on + the other hand, the digest value for a given source file is the hash of + the three lines in the manifest file for the source file.

+

The signature file also, by default, includes a header containing a + hash of the whole manifest file. The presence of the header enables + verification optimization, as described in + + JAR File Verification.

+
+

The Signature Block (.DSA) File

+
+

The .SF file is signed and the signature is placed in the .DSA file. + The .DSA file also contains, encoded inside it, the certificate or + certificate chain from the keystore which authenticates the public key + corresponding to the private key used for signing.

+
+
+

JAR File Verification

+
+

A successful JAR file verification occurs if the signature(s) are valid, + and none of the files that were in the JAR file when the signatures were + generated have been changed since then. JAR file verification involves the + following steps:

+
    +
  1. Verify the signature of the .SF file itself. +

    That is, the verification ensures that the signature stored in each + signature block (.DSA) file was in fact generated using the private key + corresponding to the public key whose certificate (or certificate chain) + also appears in the .DSA file. It also ensures that the signature is a + valid signature of the corresponding signature (.SF) file, and thus the + .SF file has not been tampered with.

    +

     

  2. +
  3. Verify the digest listed in each entry in the .SF file with each + corresponding section in the manifest. +

    The .SF file by default includes a header containing a hash of the + entire manifest file. When the header is present, then the verification + can check to see whether or not the hash in the header indeed matches the + hash of the manifest file. If that is the case, verification proceeds to + the next step.

    +

    If that is not the case, a less optimized verification is required to + ensure that the hash in each source file information section in the .SF + file equals the hash of its corresponding section in the manifest file + (see + + The Signature (.SF) File).

    +

    One reason the hash of the manifest file that is stored in the .SF file + header may not equal the hash of the current manifest file would be + because one or more files were added to the JAR file (using the jar + tool) after the signature (and thus the .SF file) was generated. When the + jar tool is used to add files, the manifest file is changed + (sections are added to it for the new files), but the .SF file is not. A + verification is still considered successful if none of the files that were + in the JAR file when the signature was generated have been changed since + then, which is the case if the hashes in the non-header sections of the + .SF file equal the hashes of the corresponding sections in the manifest + file.

    +

     

  4. +
  5. Read each file in the JAR file that has an entry in the .SF file. + While reading, compute the file's digest, and then compare the result with + the digest for this file in the manifest section. The digests should be + the same, or verification fails.
  6. +
+

If any serious verification failures occur during the verification + process, the process is stopped and a security exception is thrown. It is + caught and displayed by jarsigner.

+
+

Multiple Signatures for a JAR File

+
+

A JAR file can be signed by multiple people simply by running the + jarsigner tool on the file multiple times, specifying the alias for a + different person each time, as in:

+
  jarsigner myBundle.jar susan
+  jarsigner myBundle.jar kevin
+
+

When a JAR file is signed multiple times, there are multiple .SF and .DSA + files in the resulting JAR file, one pair for each signature. Thus, in the + example above, the output JAR file includes files with the following names: +

+
  SUSAN.SF
+  SUSAN.DSA
+  KEVIN.SF
+  KEVIN.DSA
+
+

Note: It is also possible for a JAR file to have mixed signatures, some + generated by the JDK 1.1 javakey tool and others by jarsigner. + That is, jarsigner can be used to sign JAR files already previously + signed using javakey.

+
+
+

OPTIONS

+
+

The various jarsigner options are listed and described below. Note: +

+
    +
  • All option names are preceded by a minus sign (-).

     

  • +
  • The options may be provided in any order.

     

  • +
  • Items in italics (option values) represent the actual values that must + be supplied.

     

  • +
  • The -keystore, -storepass, -keypass, + -sigfile , and -signedjar options are only + relevant when signing a JAR file, not when verifying a signed JAR file. + Similarly, an alias is only specified on the command line when signing a JAR + file.
  • +
+

 

+
+
-keystore url
+
Specifies the URL that tells the keystore location. This defaults to the + file .keystore in the user's home directory, as determined by the "user.home" + system property. +

A keystore is required when signing, so you must explicitly specify one + if the default keystore does not exist (or you want to use one other than + the default).

+

A keystore is not required when verifying, but if one is + specified, or the default exists, and the -verbose option was + also specified, additional information is output regarding whether or not + any of the certificates used to verify the JAR file are contained in that + keystore.

+

Note: the -keystore argument can actually be a file name + (and path) specification rather than a URL, in which case it will be treated + the same as a "file:" URL. That is,

+
  -keystore filePathAndName
+
+

is treated as equivalent to

+
  -keystore file:filePathAndName
+
+

 

+
-storetype storetype
+
Specifies the type of keystore to be instantiated. The default keystore + type is the one that is specified as the value of the "keystore.type" + property in the security properties file, which is returned by the static + getDefaultType method in java.security.KeyStore. +

 

+
-storepass password
+
Specifies the password which is required to access the keystore. This is + only needed when signing (not verifying) a JAR file. In that case, if a + -storepass option is not provided at the command line, the user + is prompted for the password. +

Note: The password shouldn't be specified on the command line or in a + script unless it is for testing purposes, or you are on a secure system. + Also, when typing in a password at the password prompt, the password is + echoed (displayed exactly as typed), so be careful not to type it in front + of anyone.

+

 

+
-keypass password
+
Specifies the password used to protect the private key of the keystore + entry addressed by the alias specified on the command line. The password is + required when using jarsigner to sign a JAR file. If no password is + provided on the command line, and the required password is different from + the store password, the user is prompted for it. +

Note: The password shouldn't be specified on the command line or in a + script unless it is for testing purposes, or you are on a secure system. + Also, when typing in a password at the password prompt, the password is + echoed (displayed exactly as typed), so be careful not to type it in front + of anyone.

+

 

+
-sigfile file
+
Specifies the base file name to be used for the generated .SF and .DSA + files. For example, if file is "DUKESIGN", the generated .SF and .DSA + files will be named "DUKESIGN.SF" and "DUKESIGN.DSA", and will be placed in + the "META-INF" directory of the signed JAR file. +

The characters in file must come from the set "a-zA-Z0-9_-". That + is, only letters, numbers, underscore, and hyphen characters are allowed. + Note: All lowercase characters will be converted to uppercase for the .SF + and .DSA file names.

+

If no -sigfile option appears on the command line, the base + file name for the .SF and .DSA files will be the first 8 characters of the + alias name specified on the command line, all converted to upper case. If + the alias name has fewer than 8 characters, the full alias name is used. If + the alias name contains any characters that are not legal in a signature + file name, each such character is converted to an underscore ("_") character + in forming the file name.

+

 

+
-signedjar file
+
Specifies the name to be used for the signed JAR file. +

If no name is specified on the command line, the name used is the same as + the input JAR file name (the name of the JAR file to be signed); in other + words, that file is overwritten with the signed JAR file.

+

 

+
-verify
+
If this appears on the command line, the specified JAR file will be + verified, not signed. If the verification is successful, "jar verified" will + be displayed. If you try to verify an unsigned JAR file, or a JAR file + signed with an unsupported algorithm (e.g., RSA when you don't have an RSA + provider installed), the following is displayed: "jar is unsigned. + (signatures missing or not parsable)" +

It is possible to verify JAR files signed using either jarsigner + or the JDK 1.1 javakey tool, or both.

+

For further information on verification, see + + JAR File Verification.

+

 

+
-certs
+
If this appears on the command line, along with the -verify + and -verbose options, the output includes certificate + information for each signer of the JAR file. This information includes

 

    +
  • the name of the type of certificate (stored in the .DSA file) that + certifies the signer's public key

     

  • +
  • if the certificate is an X.509 certificate (more specifically, an + instance of java.security.cert.X509Certificate): the + distinguished name of the signer
  • +
+

The keystore is also examined. If no keystore value is specified on the + command line, the default keystore file (if any) will be checked. If the + public key certificate for a signer matches an entry in the keystore, then + the following information will also be displayed:

+

 

    +
  • in parentheses, the alias name for the keystore entry for that signer. + If the signer actually comes from a JDK 1.1 identity database instead of + from a keystore, the alias name will appear in brackets instead of + parentheses.
  • +
+

 

+
-verbose
+
If this appears on the command line, it indicates "verbose" mode, which + causes jarsigner to output extra information as to the progress of + the JAR signing or verification. +

 

+
-internalsf
+
In the past, the .DSA (signature block) file generated when a JAR file + was signed used to include a complete encoded copy of the .SF file + (signature file) also generated. This behavior has been changed. To reduce + the overall size of the output JAR file, the .DSA file by default doesn't + contain a copy of the .SF file anymore. But if -internalsf + appears on the command line, the old behavior is utilized. This option is + mainly useful for testing; in practice, it should not be used, since doing + so eliminates a useful optimization. +

 

+
-sectionsonly
+
If this appears on the command line, the .SF file (signature file) + generated when a JAR file is signed does not include a header + containing a hash of the whole manifest file. It just contains information + and hashes related to each individual source file included in the JAR file, + as described in + + The Signature (.SF) File . +

By default, this header is added, as an optimization. When the header is + present, then whenever the JAR file is verified, the verification can first + check to see whether or not the hash in the header indeed matches the hash + of the whole manifest file. If so, verification proceeds to the next step. + If not, it is necessary to do a less optimized verification that the hash in + each source file information section in the .SF file equals the hash of its + corresponding section in the manifest file.

+

For further information, see + + JAR File Verification.

+

This option is mainly useful for testing; in practice, it should not + be used, since doing so eliminates a useful optimization.

+

 

+
-provider provider-class-name
+
Used to specify the name of cryptographic service provider's master + class file when the service provider is not listed in the security + properties file.

 

+
-Jjavaoption
+
Passes through the specified javaoption string directly to the + Java interpreter. (jarsigner is actually a "wrapper" around the + interpreter.) This option should not contain any spaces. It is useful for + adjusting the execution environment or memory usage. For a list of possible + interpreter options, type java -h or java -X at + the command line.

 

+
+
+

EXAMPLES

+
+

Signing a JAR File

+
+

Suppose you have a JAR file named "bundle.jar" and you'd like to sign it + using the private key of the user whose keystore alias is "jane" in the + keystore named "mystore" in the "working" directory on the C drive. Suppose + the keystore password is "myspass" and the password for jane's + private key is "j638klm". You can use the following to sign the JAR file and + name the signed JAR file "sbundle.jar":

+
    jarsigner -keystore C:\working\mystore -storepass myspass
+      -keypass j638klm -signedjar sbundle.jar bundle.jar jane 
+
+

Note that there is no -sigfile specified in the command + above, so the generated .SF and .DSA files to be placed in the signed JAR + file will have default names based on the alias name. That is, they will be + named JANE.SF and JANE.DSA.

+

If you want to be prompted for the store password and the private key + password, you could shorten the above command to

+
    jarsigner -keystore C:\working\mystore
+      -signedjar sbundle.jar bundle.jar jane 
+
+

If the keystore to be used is the default keystore (the one named ".keystore" + in your home directory), you don't need to specify a keystore, as in:

+
    jarsigner -signedjar sbundle.jar bundle.jar jane 
+
+

Finally, if you want the signed JAR file to simply overwrite the input + JAR file (bundle.jar), you don't need to specify a -signedjar + option:

+
    jarsigner bundle.jar jane 
+
+
+

Verifying a Signed JAR File

+
+

To verify a signed JAR file, that is, to verify that the signature is + valid and the JAR file has not been tampered with, use a command such as the + following:

+
    jarsigner -verify sbundle.jar 
+
+

If the verification is successful,

+
    jar verified.
+
+

is displayed. Otherwise, an error message appears.

+

You can get more information if you use the -verbose option. + A sample use of jarsigner with the -verbose option is + shown below, along with sample output:

+
    jarsigner -verify -verbose sbundle.jar
+
+           198 Fri Sep 26 16:14:06 PDT 1997 META-INF/MANIFEST.MF
+           199 Fri Sep 26 16:22:10 PDT 1997 META-INF/JANE.SF
+          1013 Fri Sep 26 16:22:10 PDT 1997 META-INF/JANE.DSA
+    smk   2752 Fri Sep 26 16:12:30 PDT 1997 AclEx.class
+    smk    849 Fri Sep 26 16:12:46 PDT 1997 test.class
+
+      s = signature was verified
+      m = entry is listed in manifest
+      k = at least one certificate was found in keystore
+
+    jar verified.
+
+

Verification with Certificate Information

+

If you specify the -certs option when verifying, along with + the -verify and -verbose options, the output + includes certificate information for each signer of the JAR file, including + the certificate type, the signer distinguished name information (iff it's an + X.509 certificate), and, in parentheses, the keystore alias for the signer + if the public key certificate in the JAR file matches that in a keystore + entry. For example,

+
    jarsigner -keystore /working/mystore -verify -verbose -certs myTest.jar
+
+           198 Fri Sep 26 16:14:06 PDT 1997 META-INF/MANIFEST.MF
+           199 Fri Sep 26 16:22:10 PDT 1997 META-INF/JANE.SF
+          1013 Fri Sep 26 16:22:10 PDT 1997 META-INF/JANE.DSA
+           208 Fri Sep 26 16:23:30 PDT 1997 META-INF/JAVATEST.SF
+          1087 Fri Sep 26 16:23:30 PDT 1997 META-INF/JAVATEST.DSA
+    smk   2752 Fri Sep 26 16:12:30 PDT 1997 Tst.class
+
+      X.509, CN=Test Group, OU= , O=Sun Microsystems, L=CUP, S=CA, C=US (javatest)
+      X.509, CN=Jane Smith, OU= , O=Sun, L=cup, S=ca, C=us (jane)
+
+      s = signature was verified
+      m = entry is listed in manifest
+      k = at least one certificate was found in keystore
+
+    jar verified.
+
+

If the certificate for a signer is not an X.509 certificate, there is no + distinguished name information. In that case, just the certificate type and + the alias are shown. For example, if the certificate is a PGP certificate, + and the alias is "bob", you'd get

+
      PGP, (bob)
+
+

Verification of a JAR File that Includes Identity Database Signers

+

If a JAR file has been signed using the JDK 1.1 javakey tool, and + thus the signer is an alias in an identity database, the verification output + includes an "i" symbol. If the JAR file has been signed by both an alias in + an identity database and an alias in a keystore, both "k" and "i" appear. +

+

When the -certs option is used, any identity database + aliases are shown in square brackets rather than the parentheses used for + keystore aliases. For example:

+
    jarsigner -keystore /working/mystore -verify -verbose -certs writeFile.jar
+
+           198 Fri Sep 26 16:14:06 PDT 1997 META-INF/MANIFEST.MF
+           199 Fri Sep 26 16:22:10 PDT 1997 META-INF/JANE.SF
+          1013 Fri Sep 26 16:22:10 PDT 1997 META-INF/JANE.DSA
+           199 Fri Sep 27 12:22:30 PDT 1997 META-INF/DUKE.SF
+          1013 Fri Sep 27 12:22:30 PDT 1997 META-INF/DUKE.DSA
+   smki   2752 Fri Sep 26 16:12:30 PDT 1997 writeFile.html
+
+      X.509, CN=Jane Smith, OU= , O=Sun, L=cup, S=ca, C=us (jane)
+      X.509, CN=Duke, OU= , O=Sun, L=cup, S=ca, C=us [duke]
+
+      s = signature was verified
+      m = entry is listed in manifest
+      k = at least one certificate was found in keystore
+      i = at least one certificate was found in identity scope
+
+    jar verified.
+
+

Note that the alias "duke" is in brackets to denote that it is an + identity database alias, not a keystore alias.

+
+
+

SEE ALSO

+
+ +
+
+ + + + + +
+ + Copyright © 1995, 2010 Oracle and/or its + affiliates. All rights reserved. + Sun +
+ + \ No newline at end of file diff --git a/help_offline/keytool.html b/help_offline/keytool.html new file mode 100644 index 0000000..abc5252 --- /dev/null +++ b/help_offline/keytool.html @@ -0,0 +1,1341 @@ + + + + +keytool-Key and Certificate Management Tool + + + +

keytool - Key and Certificate Management Tool

+
+

Manages a keystore (database) of private keys and their associated X.509 + certificate chains authenticating the corresponding public keys. Also manages + certificates from trusted entities.

+
+

SYNOPSIS

+
+
keytool [ commands ]
+
+
+

DESCRIPTION

+
+

keytool is a key and certificate management utility. It enables + users to administer their own public/private key pairs and associated + certificates for use in self-authentication (where the user authenticates + himself/herself to other users/services) or data integrity and authentication + services, using digital signatures. It also allows users to cache the public + keys (in the form of certificates) of their communicating peers.

+

A certificate is a digitally signed statement from one entity + (person, company, etc.), saying that the public key (and some other + information) of some other entity has a particular value. (See + + Certificates.) When data is digitally signed, the signature can be + verified to check the data integrity and authenticity. Integrity means + that the data has not been modified or tampered with, and authenticity + means the data indeed comes from whoever claims to have created and signed it. +

+

keytool stores the keys and certificates in a so-called keystore. + The default + + keystore implementation implements the keystore as a file. It protects + private keys with a password.

+

The + + jarsigner tool uses information from a keystore to generate or + verify digital signatures for Java ARchive (JAR) files. (A JAR file packages + class files, images, sounds, and/or other digital data in a single file). + jarsigner verifies the digital signature of a JAR file, using the + certificate that comes with it (it is included in the signature block file of + the JAR file), and then checks whether or not the public key of that + certificate is "trusted", i.e., is contained in the specified keystore.

+

Please note: the keytool and jarsigner tools completely + replace the javakey tool provided in JDK 1.1. These new tools provide + more features than javakey, including the ability to protect the + keystore and private keys with passwords, and the ability to verify signatures + in addition to generating them. The new keystore architecture replaces the + identity database that javakey created and managed. It is possible to + import the information from an identity database into a keystore, via the + -identitydb keytool command.

+

Keystore Entries

+
+

There are two different types of entries in a keystore:

+
    +
  1. key entries - each holds very sensitive cryptographic key + information, which is stored in a protected format to prevent unauthorized + access. Typically, a key stored in this type of entry is a secret key, or + a private key accompanied by the + + certificate "chain" for the corresponding public key. The keytool + and jarsigner tools only handle the latter type of entry, that is + private keys and their associated certificate chains.

     

  2. +
  3. trusted certificate entries - each contains a single public key + certificate belonging to another party. It is called a "trusted + certificate" because the keystore owner trusts that the public key in the + certificate indeed belongs to the identity identified by the "subject" + (owner) of the certificate. The issuer of the certificate vouches for + this, by signing the certificate.
  4. +
+
+

Keystore Aliases

+
+

All keystore entries (key and trusted certificate entries) are accessed + via unique aliases. Aliases are case-insensitive; the aliases + Hugo and hugo would refer to the same keystore entry. +

+

An alias is specified when you add an entity to the keystore using the + + -genkey command to generate a key pair (public and private key) or the + + -import command to add a certificate or certificate chain to the list of + trusted certificates. Subsequent keytool commands must use this same + alias to refer to the entity.

+

For example, suppose you use the alias duke to generate a new + public/private key pair and wrap the public key into a self-signed + certificate (see + + Certificate Chains) via the following command:

+
    keytool -genkey -alias duke -keypass dukekeypasswd
+
+

This specifies an inital password of "dukekeypasswd" required by + subsequent commands to access the private key assocated with the alias + duke. If you later want to change duke's private key password, you + use a command like the following:

+
    keytool -keypasswd -alias duke -keypass dukekeypasswd -new newpass
+
+

This changes the password from "dukekeypasswd" to "newpass".

+

Please note: A password should not actually be specified on a command + line or in a script unless it is for testing purposes, or you are on a + secure system. If you don't specify a required password option on a command + line, you will be prompted for it. When typing in a password at the password + prompt, the password is currently echoed (displayed exactly as typed), so be + careful not to type it in front of anyone.

+
+

Keystore Location

+
+

Each keytool command has a -keystore option for + specifying the name and location of the persistent keystore file for the + keystore managed by keytool. The keystore is by default stored in a + file named .keystore in the user's home directory, as determined by + the "user.home" system property. Given user name uName, the "user.home" + property value defaults to

+
C:\Winnt\Profiles\uName on multi-user Windows NT systems
+C:\Windows\Profiles\uName on multi-user Windows 95 systems
+C:\Windows on single-user Windows 95 systems
+
+

Thus, if the user name is "cathy", "user.home" defaults to

+
C:\Winnt\Profiles\cathy on multi-user Windows NT systems
+C:\Windows\Profiles\cathy on multi-user Windows 95 systems
+
+
+

Keystore Creation

+
+

A keystore is created whenever you use a + + -genkey, + + -import, or + + -identitydb command to add data to a keystore that doesn't yet exist. +

+

More specifically, if you specify, in the -keystore option, + a keystore that doesn't yet exist, that keystore will be created.

+

If you don't specify a -keystore option, the default + keystore is a file named .keystore in your home directory. If + that file does not yet exist, it will be created.

+
+

Keystore Implementation

+
+

The KeyStore class provided in the java.security + package supplies well-defined interfaces to access and modify the + information in a keystore. It is possible for there to be multiple different + concrete implementations, where each implementation is that for a particular + type of keystore.

+

Currently, two command-line tools (keytool and jarsigner) + and a GUI-based tool named Policy Tool make use of keystore + implementations. Since KeyStore is publicly available, JDK + users can write additional security applications that use it.

+

There is a built-in default implementation, provided by Sun Microsystems. + It implements the keystore as a file, utilizing a proprietary keystore type + (format) named "JKS". It protects each private key with its individual + password, and also protects the integrity of the entire keystore with a + (possibly different) password.

+

Keystore implementations are provider-based. More specifically, the + application interfaces supplied by KeyStore are implemented in + terms of a "Service Provider Interface" (SPI). That is, there is a + corresponding abstract KeystoreSpi class, also in the + java.security package, which defines the Service Provider Interface + methods that "providers" must implement. (The term "provider" refers to a + package or a set of packages that supply a concrete implementation of a + subset of services that can be accessed by the Java Security API.) Thus, to + provide a keystore implementation, clients must implement a "provider" and + supply a KeystoreSpi subclass implementation, as described in + + How to Implement a Provider for the Java Cryptography Architecture.

+

Applications can choose different types of keystore + implementations from different providers, using the "getInstance" factory + method supplied in the KeyStore class. A keystore type defines + the storage and data format of the keystore information, and the algorithms + used to protect private keys in the keystore and the integrity of the + keystore itself. Keystore implementations of different types are not + compatible.

+

keytool works on any file-based keystore implementation. (It + treats the keytore location that is passed to it at the command line as a + filename and converts it to a FileInputStream, from which it loads the + keystore information.) The jarsigner and policytool tools, on + the other hand, can read a keystore from any location that can be specified + using a URL.

+

For keytool and jarsigner, you can specify a keystore type + at the command line, via the -storetype option. For Policy Tool, + you can specify a keystore type via the "Change Keystore" command in the + Edit menu.

+

If you don't explicitly specify a keystore type, the tools choose a + keystore implementation based simply on the value of the keystore.type + property specified in the security properties file. The security properties + file is called java.security, and it resides in the JDK security + properties directory, java.home\lib\security, where + java.home is the runtime environment's directory (the jre + directory in the SDK or the top-level directory of the Java 2 Runtime + Environment).

+

Each tool gets the keystore.type value and then examines all + the currently-installed providers until it finds one that implements + keystores of that type. It then uses the keystore implementation from that + provider.

+

The KeyStore class defines a static method named + getDefaultType that lets applications and applets retrieve the value + of the keystore.type property. The following line of code + creates an instance of the default keystore type (as specified in the + keystore.type property):

+
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+
+

The default keystore type is "jks" (the proprietary type of the keystore + implementation provided by Sun). This is specified by the following line in + the security properties file:

+
    keystore.type=jks
+
+

To have the tools utilize a keystore implementation other than the + default, you can change that line to specify a different keystore type.

+

For example, if you have a provider package that supplies a keystore + implementation for a keystore type called "pkcs12", change the line to

+
    keystore.type=pkcs12
+
+

Note: case doesn't matter in keystore type designations. For example, "JKS" + would be considered the same as "jks".

+
+

Supported Algorithms and Key Sizes

+
+

keytool allows users to specify any key pair generation and + signature algorithm supplied by any of the registered cryptographic service + providers. That is, the keyalg and sigalg options for various + commands must be supported by a provider implementation. The default key + pair generation algorithm is "DSA". The signature algorithm is derived from + the algorithm of the underlying private key: If the underlying private key + is of type "DSA", the default signature algorithm is "SHA1withDSA", and if + the underlying private key is of type "RSA", the default signature algorithm + is "MD5withRSA".

+

When generating a DSA key pair, the key size must be in the range from + 512 to 1024 bits, and must be a multiple of 64. The default key size for any + algorithm is 1024 bits.

+
+

Certificates

+
+

A certificate (also known as a public-key certificate) is a + digitally signed statement from one entity (the issuer), saying that + the public key (and some other information) of another entity (the + subject) has some specific value.

+

Let us expand on some of the key terms used in this sentence:

+
+
Public Keys
+
These are numbers associated with a particular entity, and are + intended to be known to everyone who needs to have trusted interactions + with that entity. Public keys are used to verify signatures.
+
Digitally Signed
+
If some data is digitally signed it has been stored with the + "identity" of an entity, and a signature that proves that entity knows + about the data. The data is rendered unforgeable by signing with the + entity's private key.
+
Identity
+
A known way of addressing an entity. In some systems the identity is + the public key, in others it can be anything from a Unix UID to an Email + address to an X.509 Distinguished Name.
+
Signature
+
A signature is computed over some data using the private key of an + entity (the signer, which in the case of a certificate is also + known as the issuer).
+
Private Keys
+
These are numbers, each of which is supposed to be known only to the + particular entity whose private key it is (that is, it's supposed to be + kept secret). Private and public keys exist in pairs in all public key + cryptography systems (also referred to as "public key crypto systems"). In + a typical public key crypto system, such as DSA, a private key corresponds + to exactly one public key. Private keys are used to compute signatures. +
+
Entity
+
An entity is a person, organization, program, computer, business, + bank, or something else you are trusting to some degree.
+
+

Basically, public key cryptography requires access to users' public keys. + In a large-scale networked environment it is impossible to guarantee that + prior relationships between communicating entities have been established or + that a trusted repository exists with all used public keys. Certificates + were invented as a solution to this public key distribution problem. Now a + Certification Authority (CA) can act as a trusted third party. CAs + are entities (for example, businesses) that are trusted to sign (issue) + certificates for other entities. It is assumed that CAs will only create + valid and reliable certificates, as they are bound by legal agreements. + There are many public Certification Authorities, such as + VeriSign, + Thawte, + Entrust, and so on. You can also run your own Certification Authority + using products such as the Netscape/Microsoft Certificate Servers or the + Entrust CA product for your organization.

+

Using keytool, it is possible to display, import, and export + certificates. It is also possible to generate self-signed certificates.

+

keytool currently handles X.509 certificates.

+

X.509 Certificates

+
+

The X.509 standard defines what information can go into a certificate, + and describes how to write it down (the data format). All X.509 + certificates have the following data, in addition to the signature:

+
+
Version
+
This identifies which version of the X.509 standard applies to this + certificate, which affects what information can be specified in it. Thus + far, three versions are defined. keytool can import and export + v1, v2, and v3 certificates. It generates v1 certificates.
+
Serial Number
+
The entity that created the certificate is responsible for assigning + it a serial number to distinguish it from other certificates it issues. + This information is used in numerous ways, for example when a + certificate is revoked its serial number is placed in a Certificate + Revocation List (CRL).
+
Signature Algorithm Identifier
+
This identifies the algorithm used by the CA to sign the + certificate.
+
Issuer Name
+
The + + X.500 Distinguished Name of the entity that signed the certificate. + This is normally a CA. Using this certificate implies trusting the + entity that signed this certificate. (Note that in some cases, such as + root or top-level CA certificates, the issuer signs its own + certificate.)
+
Validity Period
+
Each certificate is valid only for a limited amount of time. This + period is described by a start date and time and an end date and time, + and can be as short as a few seconds or almost as long as a century. The + validity period chosen depends on a number of factors, such as the + strength of the private key used to sign the certificate or the amount + one is willing to pay for a certificate. This is the expected period + that entities can rely on the public value, if the associated private + key has not been compromised.
+
Subject Name
+
The name of the entity whose public key the certificate identifies. + This name uses the X.500 standard, so it is intended to be unique across + the Internet. This is the + + X.500 Distinguished Name (DN) of the entity, for example, +
    CN=Java Duke, OU=  Division, O=Sun Microsystems Inc, C=US
+
+

(These refer to the subject's Common Name, Organizational Unit, + Organization, and Country.)

+
Subject Public Key Information
+
This is the public key of the entity being named, together with an + algorithm identifier which specifies which public key crypto system this + key belongs to and any associated key parameters.
+
+

X.509 Version 1 has been available since 1988, is widely + deployed, and is the most generic.

+

X.509 Version 2 introduced the concept of subject and issuer + unique identifiers to handle the possibility of reuse of subject and/or + issuer names over time. Most certificate profile documents strongly + recommend that names not be reused, and that certificates should not make + use of unique identifiers. Version 2 certificates are not widely used.

+

X.509 Version 3 is the most recent (1996) and supports the + notion of extensions, whereby anyone can define an extension and include + it in the certificate. Some common extensions in use today are: + KeyUsage (limits the use of the keys to particular purposes such as + "signing-only") and AlternativeNames (allows other identities to + also be associated with this public key, e.g. DNS names, Email addresses, + IP addresses). Extensions can be marked critical to indicate that + the extension should be checked and enforced/used. For example, if a + certificate has the KeyUsage extension marked critical and set to "keyCertSign" + then if this certificate is presented during SSL communication, it should + be rejected, as the certificate extension indicates that the associated + private key should only be used for signing certificates and not for SSL + use.

+

All the data in a certificate is encoded using two related standards + called ASN.1/DER. Abstract Syntax Notation 1 describes data. The + Definite Encoding Rules describe a single way to store and transfer + that data.

+
+

X.500 Distinguished Names

+
+

X.500 Distinguished Names are used to identify entities, such as those + which are named by the subject and issuer + (signer) fields of X.509 certificates. keytool supports the + following subparts:

+
    +
  • commonName - common name of a person, e.g., "Susan Jones"

     

  • +
  • organizationUnit - small organization (e.g, department or + division) name, e.g., "Purchasing"

     

  • +
  • organizationName - large organization name, e.g., "ABCSystems, + Inc."

     

  • +
  • localityName - locality (city) name, e.g., "Palo Alto"

     

  • +
  • stateName - state or province name, e.g., "California"

     

  • +
  • country - two-letter country code, e.g., "CH"

     

  • +
+

When supplying a distinguished name string as the value of a -dname + option, as for the -genkey or -selfcert + commands, the string must be in the following format:

+
CN=cName, OU=orgUnit, O=org, L=city, S=state, C=countryCode
+
+

where all the italicized items represent actual values and the above + keywords are abbreviations for the following:

+
	CN=commonName
+	OU=organizationUnit
+	O=organizationName
+	L=localityName
+	S=stateName
+	C=country
+
+

A sample distinguished name string is

+
CN=Mark Smith, OU=JavaSoft, O=Sun, L=Cupertino, S=California, C=US
+
+

and a sample command using such a string is

+
keytool -genkey -dname "CN=Mark Smith, OU=JavaSoft, O=Sun, L=Cupertino, 
+S=California, C=US" -alias mark
+
+

Case does not matter for the keyword abbreviations. For example, "CN", + "cn", and "Cn" are all treated the same.

+

Order matters; each subcomponent must appear in the designated order. + However, it is not necessary to have all the subcomponents. You may use a + subset, for example:

+
CN=Steve Meier, OU=SunSoft, O=Sun, C=US
+
+

If a distinguished name string value contains a comma, the comma must + be escaped by a "\" character when you specify the string on a command + line, as in

+
   cn=peter schuster, o=Sun Microsystems\, Inc., o=sun, c=us
+
+

It is never necessary to specify a distinguished name string on a + command line. If it is needed for a command, but not supplied on the + command line, the user is prompted for each of the subcomponents. In this + case, a comma does not need to be escaped by a "\".

+
+

The Internet RFC 1421 Certificate Encoding + Standard

+
+

Certificates are often stored using the printable encoding format + defined by the Internet RFC 1421 standard, instead of their binary + encoding. This certificate format, also known as "Base 64 encoding", + facilitates exporting certificates to other applications by email or + through some other mechanism.

+

Certificates read by the -import and -printcert + commands can be in either this format or binary encoded.

+

The -export command by default outputs a certificate in + binary encoding, but will instead output a certificate in the printable + encoding format, if the -rfc option is specified.

+

The -list command by default prints the MD5 fingerprint of + a certificate. If the -v option is specified, the certificate + is printed in human-readable format, while if the -rfc option + is specified, the certificate is output in the printable encoding format. +

+

In its printable encoding format, the encoded certificate is bounded at + the beginning by

+
-----BEGIN CERTIFICATE-----
+
+

and at the end by

+
-----END CERTIFICATE-----
+
+
+

Certificate Chains

+
+

keytool can create and manage keystore "key" entries that each + contain a private key and an associated certificate "chain". The first + certificate in the chain contains the public key corresponding to the + private key.

+

When keys are first generated (see the + + -genkey command), the chain starts off containing a single element, a + self-signed certificate. A self-signed certificate is one for which + the issuer (signer) is the same as the subject (the entity whose public + key is being authenticated by the certificate). Whenever the -genkey + command is called to generate a new public/private key pair, it also wraps + the public key into a self-signed certificate.

+

Later, after a Certificate Signing Request (CSR) has been generated + (see the + + -certreq command) and sent to a Certification Authority (CA), the + response from the CA is imported (see + + -import), and the self-signed certificate is replaced by a chain of + certificates. At the bottom of the chain is the certificate (reply) issued + by the CA authenticating the subject's public key. The next certificate in + the chain is one that authenticates the CA's public key.

+

In many cases, this is a self-signed certificate (that is, a + certificate from the CA authenticating its own public key) and the last + certificate in the chain. In other cases, the CA may return a chain of + certificates. In this case, the bottom certificate in the chain is the + same (a certificate signed by the CA, authenticating the public key of the + key entry), but the second certificate in the chain is a certificate + signed by a different CA, authenticating the public key of the CA + you sent the CSR to. Then, the next certificate in the chain will be a + certificate authenticating the second CA's key, and so on, until a + self-signed "root" certificate is reached. Each certificate in the chain + (after the first) thus authenticates the public key of the signer of the + previous certificate in the chain.

+

Many CAs only return the issued certificate, with no supporting chain, + especially when there is a flat hierarchy (no intermediates CAs). In this + case, the certificate chain must be established from trusted certificate + information already stored in the keystore.

+

A different reply format (defined by the PKCS#7 standard) also includes + the supporting certificate chain, in addition to the issued certificate. + Both reply formats can be handled by keytool.

+

The top-level (root) CA certificate is self-signed. However, the trust + into the root's public key does not come from the root certificate itself + (anybody could generate a self-signed certificate with the distinguished + name of say, the VeriSign root CA!), but from other sources like a + newspaper. The root CA public key is widely known. The only reason it is + stored in a certificate is because this is the format understood by most + tools, so the certificate in this case is only used as a "vehicle" to + transport the root CA's public key. Before you add the root CA certificate + to your keystore, you should view it (using the -printcert + option) and compare the displayed fingerprint with the well-known + fingerprint (obtained from a newspaper, the root CA's webpage, etc.).

+
+

Importing Certificates

+
+

To import a certificate from a file, use the + + -import command, as in

+
    keytool -import -alias joe -file jcertfile.cer
+
+

This sample command imports the certificate(s) in the file + jcertfile.cer and stores it in the keystore entry identified by the + alias joe.

+

You import a certificate for two reasons:

+
    +
  1. to add it to the list of trusted certificates, or

     

  2. +
  3. to import a certificate reply received from a CA as the result of + submitting a Certificate Signing Request (see the + + -certreq command) to that CA.
  4. +
+

Which type of import is intended is indicated by the value of the + -alias option. If the alias exists in the database, and identifies + an entry with a private key, then it is assumed you want to import a + certificate reply. keytool checks whether the public key in the + certificate reply matches the public key stored with the alias, and exits + if they are different. If the alias identifies the other type of keystore + entry, the certificate will not be imported. If the alias does not exist, + then it will be created and associated with the imported certificate.

+

WARNING Regarding Importing Trusted + Certificates

+
+

IMPORTANT: Be sure to check a certificate very carefully before + importing it as a trusted certificate!

+

View it first (using the -printcert command, or the + -import command without the -noprompt option), + and make sure that the displayed certificate fingerprint(s) match the + expected ones. For example, suppose someone sends or emails you a + certificate, and you put it in a file named /tmp/cert. + Before you consider adding the certificate to your list of trusted + certificates, you can execute a -printcert command to view + its fingerprints, as in

+
  keytool -printcert -file /tmp/cert
+    Owner: CN=ll, OU=ll, O=ll, L=ll, S=ll, C=ll
+    Issuer: CN=ll, OU=ll, O=ll, L=ll, S=ll, C=ll
+    Serial Number: 59092b34
+    Valid from: Thu Sep 25 18:01:13 PDT 1997 until: Wed Dec 24 17:01:13 PST 1997
+    Certificate Fingerprints:
+         MD5:  11:81:AD:92:C8:E5:0E:A2:01:2E:D4:7A:D7:5F:07:6F
+         SHA1: 20:B6:17:FA:EF:E5:55:8A:D0:71:1F:E8:D6:9D:C0:37:13:0E:5E:FE
+
+

Then call or otherwise contact the person who sent the certificate, + and compare the fingerprint(s) that you see with the ones that they + show. Only if the fingerprints are equal is it guaranteed that the + certificate has not been replaced in transit with somebody else's (for + example, an attacker's) certificate. If such an attack took place, and + you did not check the certificate before you imported it, you would end + up trusting anything the attacker has signed (for example, a JAR file + with malicious class files inside).

+

Note: it is not required that you execute a -printcert + command prior to importing a certificate, since before adding a + certificate to the list of trusted certificates in the keystore, the + -import command prints out the certificate information and + prompts you to verify it. You then have the option of aborting the + import operation. Note, however, this is only the case if you invoke the + -import command without the -noprompt option. + If the -noprompt option is given, there is no interaction + with the user.

+
+
+

Exporting Certificates

+
+

To export a certificate to a file, use the + + -export command, as in

+
    keytool -export -alias jane -file janecertfile.cer
+
+

This sample command exports jane's certificate to the file + janecertfile.cer. That is, if jane is the alias for a + key entry, the command exports the certificate at the bottom of the + certificate chain in that keystore entry. This is the certificate that + authenticates jane's public key.

+

If, instead, jane is the alias for a trusted certificate + entry, then that trusted certificate is exported.

+
+

Displaying Certificates

+
+

To print out the contents of a keystore entry, use the + + -list command, as in

+
    keytool -list -alias joe
+
+

If you don't specify an alias, as in

+
    keytool -list
+
+

the contents of the entire keystore are printed.

+

To display the contents of a certificate stored in a file, use the + + -printcert command, as in

+
    keytool -printcert -file certfile.cer
+
+

This displays information about the certificate stored in the file + certfile.cer.

+

Note: This works independently of a keystore, i.e., you do not + need a keystore in order to display a certificate that's stored in a file. +

+
+

Generating a self-signed certificate

+
+

A self-signed certificate is one for which the issuer (signer) + is the same as the subject (the entity whose public key is being + authenticated by the certificate). Whenever the -genkey + command is called to generate a new public/private key pair, it also wraps + the public key into a self-signed certificate.

+

You may occasionally wish to generate a new self-signed certificate. + For example, you may want to use the same key pair under a different + identity (distinguished name). For example, suppose you change + departments. You can then:

+
    +
  1. copy (clone) the original key entry. See + + -keyclone.

     

  2. +
  3. generate a new self-signed certificate for the cloned entry, using + your new distinguished name. See below.

     

  4. +
  5. generate a Certificate Signing Requests for the cloned entry, and + import the reply certificate or certificate chain. See the + + -certreq and + + -import commands.

     

  6. +
  7. delete the original (now obsolete) entry. See + + -delete.

     

  8. +
+

To generate a self-signed certificate, use the + + -selfcert command, as in

+
    keytool -selfcert -alias dukeNew -keypass b92kqmp
+      -dname "cn=Duke Smith, ou=Purchasing, o=BlueSoft, c=US"
+
+

The generated certificate is stored as a single-element certificate + chain in the keystore entry identified by the specified alias (in this + case "dukeNew"), where it replaces the existing certificate chain.

+
+
+
+

COMMAND AND OPTION NOTES

+
+

The various commands and their options are listed and described + + below . Note:

+
    +
  • All command and option names are preceded by a minus sign (-).

     

  • +
  • The options for each command may be provided in any order.

     

  • +
  • All items not italicized or in braces or square brackets are required to + appear as is.

     

  • +
  • Braces surrounding an option generally signify that a + + default value will be used if the option is not specified on the command + line. Braces are also used around the -v, -rfc, + and -J options, which only have meaning if they appear on the + command line (that is, they don't have any "default" values other than not + existing).

     

  • +
  • Brackets surrounding an option signify that the user is prompted for the + value(s) if the option is not specified on the command line. (For a -keypass + option, if you do not specify the option on the command line, keytool + will first attempt to use the keystore password to recover the private key, + and if this fails, will then prompt you for the private key password.)

     

  • +
  • Items in italics (option values) represent the actual values that must + be supplied. For example, here is the format of the -printcert + command: +
      keytool -printcert {-file cert_file} {-v}
    +
    +

    When specifying a -printcert command, replace cert_file + with the actual file name, as in:

    +
      keytool -printcert -file VScert.cer
    +
    +

     

  • +
  • Option values must be quoted if they contain a blank (space).

     

  • +
  • The -help command is the default. Thus, the command line +
      keytool
    +
    +

    is equivalent to

    +
      keytool -help
    +
    +
  • +
+

Option Defaults

+
+

Below are the defaults for various option values.

+
-alias "mykey"
+
+-keyalg "DSA"
+
+-keysize 1024
+
+-validity 90
+
+-keystore the file named .keystore in the user's home directory
+
+-file stdin if reading, stdout if writing
+
+
+

The signature algorithm (-sigalg option) is derived from the + algorithm of the underlying private key: If the underlying private key is of + type "DSA", the -sigalg option defaults to "SHA1withDSA", and if the + underlying private key is of type "RSA", -sigalg defaults to + "MD5withRSA".

+
+

Options that Appear for Most Commands

+
+

The -v option can appear for all commands except -help. + If it appears, it signifies "verbose" mode; detailed certificate information + will be output.

+

There is also a -Jjavaoption option that may appear + for any command. If it appears, the specified javaoption string is + passed through directly to the Java interpreter. (keytool is actually + a "wrapper" around the interpreter.) This option should not contain any + spaces. It is useful for adjusting the execution environment or memory + usage. For a list of possible interpreter options, type java -h + or java -X at the command line.

+

These options may appear for all commands operating on a keystore:

+
+
-storetype storetype
+
This qualifier specifies the type of keystore to be instantiated. The + default keystore type is the one that is specified as the value of the "keystore.type" + property in the security properties file, which is returned by the static + getDefaultType method in java.security.KeyStore. +

 

+
-keystore keystore
+
The keystore (database file) location. Defaults to the file .keystore + in the user's home directory, as determined by the "user.home" system + property, whose value is described in + + Keystore Location. +

 

+
-storepass storepass
+
The password which is used to protect the integrity of the keystore. +

storepass must be at least 6 characters long. It must be + provided to all commands that access the keystore contents. For such + commands, if a -storepass option is not provided at the + command line, the user is prompted for it.

+

When retrieving information from the keystore, the password is + optional; if no password is given, the integrity of the retrieved + information cannot be checked and a warning is displayed.

+

Be careful with passwords - see + + Warning Regarding Passwords.

+

 

+
-provider provider-class-name
+
Used to specify the name of cryptographic service provider's master + class file when the service provider is not listed in the security + properties file.

 

+
+
+

Warning Regarding Passwords

+
+

Most commands operating on a keystore require the store password. Some + commands require a private key password.

+

Passwords can be specified on the command line (in the -storepass + and -keypass options, respectively). However, a password should + not be specified on a command line or in a script unless it is for testing + purposes, or you are on a secure system.

+

If you don't specify a required password option on a command line, you + will be prompted for it. When typing in a password at the password prompt, + the password is currently echoed (displayed exactly as typed), so be careful + not to type it in front of anyone.

+
+
+

COMMANDS

+
+

See also the + + Command and Option Notes.

+

Adding Data to the Keystore

+
+
+
-genkey + {-alias alias} {-keyalg keyalg} {-keysize keysize} {-sigalg + sigalg} [-dname dname] [-keypass keypass] {-validity + valDays} {-storetype storetype} {-keystore keystore} + [-storepass storepass] [-provider provider_class_name] {-v} + {-Jjavaoption}
+
Generates a key pair (a public key and associated private key). Wraps + the public key into an X.509 v1 self-signed certificate, which is stored + as a single-element certificate chain. This certificate chain and the + private key are stored in a new keystore entry identified by alias. +

keyalg specifies the algorithm to be used to generate the key + pair, and keysize specifies the size of each key to be generated. + sigalg specifies the algorithm that should be used to sign the + self-signed certificate; this algorithm must be compatible with keyalg. + See + + Supported Algorithms and Key Sizes.

+

dname specifies the + + X.500 Distinguished Name to be associated with alias, and is + used as the issuer and subject fields in the + self-signed certificate. If no distinguished name is provided at the + command line, the user will be prompted for one.

+

keypass is a password used to protect the private key of the + generated key pair. If no password is provided, the user is prompted for + it. If you press RETURN at the prompt, the key password is set to the same + password as that used for the keystore. keypass must be at least + 6 characters long. Be careful with passwords - see + + Warning Regarding Passwords.

+

valDays tells the number of days for which the certificate + should be considered valid.

+

 

+
-import + {-alias alias} {-file cert_file} [-keypass keypass] + {-noprompt} {-trustcacerts} {-storetype storetype} {-keystore + keystore} [-storepass storepass] [-provider + provider_class_name] {-v} {-Jjavaoption}
+
Reads the certificate or certificate chain (where the latter is + supplied in a PKCS#7 formatted reply) from the file cert_file, and + stores it in the keystore entry identified by alias. If no file is + given, the certificate or PKCS#7 reply is read from stdin. keytool + can import X.509 v1, v2, and v3 certificates, and PKCS#7 formatted + certificate chains consisting of certificates of that type. The data to be + imported must be provided either in binary encoding format, or in + printable encoding format (also known as Base64 encoding) as defined by + the + + Internet RFC 1421 standard. In the latter case, the encoding must be + bounded at the beginning by a string that starts with "-----BEGIN", and + bounded at the end by a string that starts with "-----END". +

When importing a new trusted certificate, alias must not + yet exist in the keystore. Before adding the certificate to the keystore, + keytool tries to verify it by attempting to construct a chain of + trust from that certificate to a self-signed certificate (belonging to a + root CA), using trusted certificates that are already available in the + keystore.

+

If the -trustcacerts option has been specified, additional + certificates are considered for the chain of trust, namely the + certificates in a file named "cacerts", which resides in the JDK + security properties directory, java.home\lib\security, + where java.home is the runtime environment's directory (the jre + directory in the SDK or the top-level directory of the Java 2 Runtime + Environment). The "cacerts" file represents a system-wide keystore with CA + certificates. System administrators can configure and manage that file + using keytool, specifying "jks" as the keystore type. The "cacerts" + keystore file ships with five VeriSign root CA certificates with the + following X.500 distinguished names:

+
1. OU=Class 1 Public Primary Certification Authority, O="VeriSign, Inc.",
+C=US
+
+2. OU=Class 2 Public Primary Certification Authority, O="VeriSign,
+Inc.", C=US
+
+3. OU=Class 3 Public Primary Certification Authority,
+O="VeriSign, Inc.", C=US
+
+4. OU=Class 4 Public Primary Certification
+Authority, O="VeriSign, Inc.", C=US
+
+5. OU=Secure Server Certification
+Authority, O="RSA Data Security, Inc.", C=US
+
+

The initial password of the "cacerts" keystore file is "changeit". + System administrators should change that password and the default access + permission of that file upon installing the JDK.

+

If keytool fails to establish a trust path from the certificate + to be imported up to a self-signed certificate (either from the keystore + or the "cacerts" file), the certificate information is printed out, and + the user is prompted to verify it, e.g., by comparing the displayed + certificate fingerprints with the fingerprints obtained from some other + (trusted) source of information, which might be the certificate owner + himself/herself. Be very careful to ensure the certificate is valid prior + to importing it as a "trusted" certificate! -- see + + WARNING Regarding Importing Trusted Certificates. The user then has + the option of aborting the import operation. If the -noprompt + option is given, however, there will be no interaction with the user.

+

When importing a certificate reply, the certificate reply is + validated using trusted certificates from the keystore, and optionally + using the certificates configured in the "cacerts" keystore file (if the + -trustcacerts option was specified).

+

If the reply is a single X.509 certificate, keytool attempts to + establish a trust chain, starting at the certificate reply and ending at a + self-signed certificate (belonging to a root CA). The certificate reply + and the hierarchy of certificates used to authenticate the certificate + reply form the new certificate chain of alias.

+

If the reply is a PKCS#7 formatted certificate chain, the chain is + first ordered (with the user certificate first and the self-signed root CA + certificate last), before keytool attempts to match the root CA + certificate provided in the reply with any of the trusted certificates in + the keystore or the "cacerts" keystore file (if the -trustcacerts + option was specified). If no match can be found, the information of the + root CA certificate is printed out, and the user is prompted to verify it, + e.g., by comparing the displayed certificate fingerprints with the + fingerprints obtained from some other (trusted) source of information, + which might be the root CA itself. The user then has the option of + aborting the import operation. If the -noprompt option is + given, however, there will be no interaction with the user.

+

The new certificate chain of alias replaces the old certificate + chain associated with this entry. The old chain can only be replaced if a + valid keypass, the password used to protect the private key of the + entry, is supplied. If no password is provided, and the private key + password is different from the keystore password, the user is prompted for + it. Be careful with passwords - see + + Warning Regarding Passwords.

+

 

+
-selfcert + {-alias alias} {-sigalg sigalg} {-dname dname} + {-validity valDays} [-keypass keypass] {-storetype + storetype} {-keystore keystore} [-storepass storepass] + [-provider provider_class_name] {-v} {-Jjavaoption} +
+
Generates an X.509 v1 self-signed certificate, using keystore + information including the private key and public key associated with + alias. If dname is supplied at the command line, it is used as + the + + X.500 Distinguished Name for both the issuer and + subject of the certificate. Otherwise, the X.500 Distinguished Name + associated with alias (at the bottom of its existing certificate + chain) is used. +

The generated certificate is stored as a single-element certificate + chain in the keystore entry identified by alias, where it + replaces the existing certificate chain.

+

sigalg specifies the algorithm that should be used to sign the + certificate. See + + Supported Algorithms and Key Sizes.

+

In order to access the private key, the appropriate password must be + provided, since private keys are protected in the keystore with a + password. If keypass is not provided at the command line, and is + different from the password used to protect the integrity of the keystore, + the user is prompted for it. Be careful with passwords - see + + Warning Regarding Passwords.

+

valDays tells the number of days for which the certificate + should be considered valid.

+

 

+
-identitydb + {-file idb_file} {-storetype storetype} {-keystore + keystore} [-storepass storepass] [-provider + provider_class_name] {-v} {-Jjavaoption}
+
Reads the JDK 1.1.x-style identity database from the file idb_file, + and adds its entries to the keystore. If no file is given, the identity + database is read from stdin. If a keystore does not exist, it is created. +

Only identity database entries ("identities") that were marked as + trusted will be imported in the keystore. All other identities will be + ignored. For each trusted identity, a keystore entry will be created. The + identity's name is used as the "alias" for the keystore entry.

+

The private keys from trusted identities will all be encrypted under + the same password, storepass. This is the same password that is + used to protect the keystore's integrity. Users can later assign + individual passwords to those private keys by using the "-keypasswd" + keytool command option.

+

An identity in an identity database may hold more than one certificate, + each certifying the same public key. But a keystore key entry for a + private key has that private key and a single "certificate chain" + (initially just a single certificate), where the first certificate in the + chain contains the public key corresponding to the private key. When + importing the information from an identity, only the first certificate of + the identity is stored in the keystore. This is because an identity's name + in an identity database is used as the alias for its corresponding + keystore entry, and alias names are unique within a keystore,

+
+

 

+
+

Exporting Data

+
+
+
-certreq + {-alias alias} {-sigalg sigalg} {-file certreq_file} + [-keypass keypass] {-storetype storetype} {-keystore + keystore} [-storepass storepass] [-provider + provider_class_name] {-v} {-Jjavaoption}
+
Generates a Certificate Signing Request (CSR), using the PKCS#10 + format. +

A CSR is intended to be sent to a certificate authority (CA). The CA + will authenticate the certificate requestor (usually off-line) and will + return a certificate or certificate chain, used to replace the existing + certificate chain (which initially consists of a self-signed certificate) + in the keystore.

+

The private key and X.500 Distinguished Name associated with alias + are used to create the PKCS#10 certificate request. In order to access the + private key, the appropriate password must be provided, since private keys + are protected in the keystore with a password. If keypass is not + provided at the command line, and is different from the password used to + protect the integrity of the keystore, the user is prompted for it.

+

Be careful with passwords - see + + Warning Regarding Passwords.

+

sigalg specifies the algorithm that should be used to sign the + CSR. See + + Supported Algorithms and Key Sizes.

+

The CSR is stored in the file certreq_file. If no file is + given, the CSR is output to stdout.

+

Use the import command to import the response from the CA.

+

 

+
-export + {-alias alias} {-file cert_file} {-storetype storetype} + {-keystore keystore} [-storepass storepass] [-provider + provider_class_name] {-rfc} {-v} {-Jjavaoption}
+
Reads (from the keystore) the certificate associated with alias, + and stores it in the file cert_file. +

If no file is given, the certificate is output to stdout.

+

The certificate is by default output in binary encoding, but will + instead be output in the printable encoding format, as defined by the + + Internet RFC 1421 standard, if the -rfc option is + specified.

+

If alias refers to a trusted certificate, that certificate is + output. Otherwise, alias refers to a key entry with an associated + certificate chain. In that case, the first certificate in the chain is + returned. This certificate authenticates the public key of the entity + addressed by alias.

+

 

+
+
+

Displaying Data

+
+
+
-list + {-alias alias} {-storetype storetype} {-keystore keystore} + [-storepass storepass] [-provider provider_class_name] {-v | + -rfc} {-Jjavaoption}
+
Prints (to stdout) the contents of the keystore entry identified by + alias. If no alias is specified, the contents of the entire keystore + are printed. +

This command by default prints the MD5 fingerprint of a certificate. If + the -v option is specified, the certificate is printed in + human-readable format, with additional information such as the owner, + issuer, and serial number. If the -rfc option is specified, + certificate contents are printed using the printable encoding format, as + defined by the + + Internet RFC 1421 standard

+

You cannot specify both -v and -rfc.

+

 

+
-printcert + {-file cert_file} {-v} {-Jjavaoption} +
+
Reads the certificate from the file + cert_file, and prints its contents in a human-readable format. If no + file is given, the certificate is read from stdin. +

The certificate may be either binary encoded or + in printable encoding format, as defined by the + + Internet RFC 1421 standard.

+

Note: This option can be used independently of a keystore.

+

 

+
+
+

Managing the Keystore

+
+
+
-keyclone + {-alias alias} [-dest dest_alias] [-keypass keypass] + [-new new_keypass] {-storetype storetype} {-keystore + keystore} [-storepass storepass] [-provider + provider_class_name] {-v} {-Jjavaoption}
+
Creates a new keystore entry, which has the same private key and + certificate chain as the original entry. +

The original entry is identified by alias (which defaults to "mykey" + if not provided). The new (destination) entry is identified by + dest_alias. If no destination alias is supplied at the command line, + the user is prompted for it.

+

If the private key password is different from the keystore password, + then the entry will only be cloned if a valid keypass is supplied. + This is the password used to protect the private key associated with + alias. If no key password is supplied at the command line, and the + private key password is different from the keystore password, the user is + prompted for it. The private key in the cloned entry may be protected with + a different password, if desired. If no -new option is + supplied at the command line, the user is prompted for the new entry's + password (and may choose to let it be the same as for the cloned entry's + private key).

+

Be careful with passwords - see + + Warning Regarding Passwords.

+

This command can be used to establish multiple certificate chains + corresponding to a given key pair, or for backup purposes.

+

 

+
-storepasswd [-new + new_storepass] {-storetype storetype} {-keystore keystore} + [-storepass storepass] [-provider provider_class_name] {-v} + {-Jjavaoption}
+
Changes the password used to protect the integrity of the keystore + contents. The new password is new_storepass, which must be at + least 6 characters long.

Be careful with passwords - see + + Warning Regarding Passwords.

+

 

+
-keypasswd {-alias alias} + [-keypass old_keypass] [-new new_keypass] {-storetype + storetype} {-keystore keystore} [-storepass storepass] + [-provider provider_class_name] {-v} {-Jjavaoption} +
+
Changes the password under which the private key identified by + alias is protected, from old_keypass to new_keypass. +

If the -keypass option is not provided at the command + line, and the private key password is different from the keystore + password, the user is prompted for it.

+

If the -new option is not provided at the command line, + the user is prompted for it.

+

 

+

Be careful with passwords - see + + Warning Regarding Passwords.

+

 

+
-delete + [-alias alias] {-storetype storetype} {-keystore keystore} + [-storepass storepass] [-provider provider_class_name] {-v} + {-Jjavaoption}
+
Deletes from the keystore the entry identified by alias. The + user is prompted for the alias, if no alias is provided at the command + line.

 

+
+
+

Getting Help

+
+
+
-help
+
Lists all the commands and their options.

 

+
+
+
+

EXAMPLES

+
+

Suppose you want to create a keystore for managing your public/private key + pair and certificates from entities you trust.

+

Generating Your Key Pair

+
+

The first thing you need to do is create a keystore and generate the key + pair. You could use a command such as the following:

+
    keytool -genkey -dname "cn=Mark Jones, ou=JavaSoft, o=Sun, c=US" 
+      -alias business -keypass kpi135 -keystore C:\working\mykeystore 
+      -storepass ab987c -validity 180
+
+

(Please note: This must be typed as a single line. Multiple lines are + used in the examples just for legibility purposes.)

+

This command creates the keystore named "mykeystore" in the "working" + directory on the C drive (assuming it doesn't already exist), and assigns it + the password "ab987c". It generates a public/private key pair for the entity + whose "distinguished name" has a common name of "Mark Jones", organizational + unit of "JavaSoft", organization of "Sun" and two-letter country code of + "US". It uses the default "DSA" key generation algorithm to create the keys, + both 1024 bits long.

+

It creates a self-signed certificate (using the default "SHA1withDSA" + signature algorithm) that includes the public key and the distinguished name + information. This certificate will be valid for 180 days, and is associated + with the private key in a keystore entry referred to by the alias + "business". The private key is assigned the password "kpi135".

+

The command could be significantly shorter if option defaults were + accepted. As a matter of fact, no options are required; defaults are used + for unspecified options that have default values, and you are prompted for + any required values. Thus, you could simply have the following:

+
    keytool -genkey 
+
+

In this case, a keystore entry with alias "mykey" is created, with a + newly-generated key pair and a certificate that is valid for 90 days. This + entry is placed in the keystore named ".keystore" in your home directory. + (The keystore is created if it doesn't already exist.) You will be prompted + for the distinguished name information, the keystore password, and the + private key password.

+

The rest of the examples assume you executed the -genkey + command without options specified, and that you responded to the prompts + with values equal to those given in the first -genkey command, + above (a private key password of "kpi135", etc.)

+
+

Requesting a Signed Certificate from a Certification Authority

+
+

So far all we've got is a self-signed certificate. A certificate is more + likely to be trusted by others if it is signed by a Certification Authority + (CA). To get such a signature, you first generate a Certificate Signing + Request (CSR), via the following:

+
    keytool -certreq -file MarkJ.csr
+
+

This creates a CSR (for the entity identified by the default alias "mykey") + and puts the request in the file named "MarkJ.csr". Submit this file to a + CA, such as VeriSign, Inc. The CA will authenticate you, the requestor + (usually off-line), and then will return a certificate, signed by them, + authenticating your public key. (In some cases, they will actually return a + chain of certificates, each one authenticating the public key of the signer + of the previous certificate in the chain.)

+
+

Importing a Certificate for the CA

+
+

You need to replace your self-signed certificate with a certificate + chain, where each certificate in the chain authenticates the public key of + the signer of the previous certificate in the chain, up to a "root" CA.

+

Before you import the certificate reply from a CA, you need one or more + "trusted certificates" in your keystore or in the cacerts + keystore file (which is described in + + import command):

+
    +
  • If the certificate reply is a certificate chain, you just need the top + certificate of the chain (that is, the "root" CA certificate + authenticating that CA's public key).

     

  • +
  • If the certificate reply is a single certificate, you need a + certificate for the issuing CA (the one that signed it), and if that + certificate is not self-signed, you need a certificate for its signer, and + so on, up to a self-signed "root" CA certificate.
  • +
+

The "cacerts" keystore file ships with five VeriSign root CA + certificates, so you probably won't need to import a VeriSign certificate as + a trusted certificate in your keystore. But if you request a signed + certificate from a different CA, and a certificate authenticating that CA's + public key hasn't been added to "cacerts", you will need to import a + certificate from the CA as a "trusted certificate".

+

A certificate from a CA is usually either self-signed, or signed by + another CA (in which case you also need a certificate authenticating that + CA's public key). Suppose company ABC, Inc., is a CA, and you obtain a file + named "ABCCA.cer" that is purportedly a self-signed certificate from ABC, + authenticating that CA's public key.

+

Be very careful to ensure the certificate is valid prior to importing it + as a "trusted" certificate! View it first (using the keytool -printcert + command, or the keytool -import command without the + -noprompt option), and make sure that the displayed certificate + fingerprint(s) match the expected ones. You can call the person who sent the + certificate, and compare the fingerprint(s) that you see with the ones that + they show (or that a secure public key repository shows). Only if the + fingerprints are equal is it guaranteed that the certificate has not been + replaced in transit with somebody else's (for example, an attacker's) + certificate. If such an attack took place, and you did not check the + certificate before you imported it, you would end up trusting anything the + attacker has signed.

+

If you trust that the certificate is valid, then you can add it to your + keystore via the following:

+
    keytool -import -alias abc -file ABCCA.cer
+
+

This creates a "trusted certificate" entry in the keystore, with the data + from the file "ABCCA.cer", and assigns the alias "abc" to the entry.

+
+

Importing the Certificate Reply from the CA

+
+

Once you've imported a certificate authenticating the public key of the + CA you submitted your certificate signing request to (or there's already + such a certificate in the "cacerts" file), you can import the certificate + reply and thereby replace your self-signed certificate with a certificate + chain. This chain is the one returned by the CA in response to your request + (if the CA reply is a chain), or one constructed (if the CA reply is a + single certificate) using the certificate reply and trusted certificates + that are already available in the keystore where you import the reply or in + the "cacerts" keystore file.

+

For example, suppose you sent your certificate signing request to + VeriSign. You can then import the reply via the following, which assumes the + returned certificate is named "VSMarkJ.cer":

+
    keytool -import -trustcacerts -file VSMarkJ.cer
+
+
+

Exporting a Certificate Authenticating Your Public Key

+
+

Suppose you have used the + + jarsigner tool to sign a Java ARchive (JAR) file. Clients that want to + use the file will want to authenticate your signature.

+

One way they can do this is by first importing your public key + certificate into their keystore as a "trusted" entry. You can export the + certificate and supply it to your clients. As an example, you can copy your + certificate to a file named MJ.cer via the following, assuming + the entry is aliased by "mykey":

+
    keytool -export -alias mykey -file MJ.cer
+
+

Given that certificate, and the signed JAR file, a client can use the + jarsigner tool to authenticate your signature.

+
+

Changing Your Distinguished Name but Keeping your Key Pair

+
+

Suppose your distinguished name changes, for example because you have + changed departments or moved to a different city. If desired, you may still + use the public/private key pair you've previously used, and yet update your + distinguished name. For example, suppose your name is Susan Miller, and you + created your initial key entry with the alias sMiller and the + distinguished name

+
  "cn=Susan Miller, ou=Finance Department, o=BlueSoft, c=us"
+
+

Suppose you change from the Finance Department to the Accounting + Department. You can still use the previously-generated public/private key + pair and yet update your distinguished name by doing the following. First, + copy (clone) your key entry:

+
    keytool -keyclone -alias sMiller -dest sMillerNew
+
+

(This prompts for the store password and for the initial and destination + private key passwords, since they aren't provided at the command line.) Now + you need to change the certificate chain associated with the copy, so that + the first certificate in the chain uses your different distinguished name. + Start by generating a self-signed certificate with the appropriate name:

+
    keytool -selfcert -alias sMillerNew
+      -dname "cn=Susan Miller, ou=Accounting Department, o=BlueSoft, c=us"
+
+

Then generate a Certificate Signing Request based on the information in + this new certificate:

+
    keytool -certreq -alias sMillerNew
+
+

When you get the CA certificate reply, import it:

+
    keytool -import -alias sMillerNew -file VSSMillerNew.cer
+
+

After importing the certificate reply, you may want to remove the initial + key entry that used your old distinguished name:

+
    keytool -delete -alias sMiller
+
+
+
+

SEE ALSO

+
+ +
+
+ + + + + +
+ + Copyright © 1995, 2010 Oracle and/or its + affiliates. All rights reserved. + Sun +
+ + \ No newline at end of file diff --git a/help_offline/require.html b/help_offline/require.html new file mode 100644 index 0000000..57a293a --- /dev/null +++ b/help_offline/require.html @@ -0,0 +1,88 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Requirements

+

All software applications have hardware requirements (and +sometimes have software requirements as well). BrowzOS is no different, except +that the software requirements for BrowzOS are very dynamic and, at times, may +differ depending on the way it is used. Even though this project has been +created and tested using a computer with 512 MB of memory and a 1.6 GHz +microprocessor, there is nothing that dictates the absolute minimum and absolute +maximum hardware requirements for any computer.

+

These are the basic requirements for any client computer user +who wishes to run BrowzOS from the internet:

+
  • Any computer with internet access and any client-side operating +system (i.e.: GNU/Linux, Windows, Mac OS X, etc.)
  • +
  • Any mainstream web browser with the ability to run web applets +and scripts (i.e.: Internet Explorer 6 and over, Mozilla 1.7 and over, Mozilla +Firefox, Safari, Netscape 6 and over, etc.)
  • +
  • Software drivers (i.e.: ActiveX controls and/or web browser +plug-ins) which enable mainstream web browsers to properly run certain web +applets and scripts (examples are Java, Flash, Shockwave, JavaScript, Visual +BASIC script, etc.)
  • +
+

These are the basic requirements for any computer user +who wishes to run BrowzOS from the hard drive:

+
  • Any computer with internet access and any client-side operating +system (i.e.: GNU/Linux, Windows, Mac OS X, etc.)
  • +
  • Any file archiving program or file compression program with (at +least) ZIP file read/write access
  • +
  • Any mainstream web browser with the ability to run web applets +and scripts (i.e.: Internet Explorer 6 and over, Mozilla 1.7 and over, Mozilla +Firefox, Safari, Netscape 6 and over, etc.)
  • +
  • Software drivers (i.e.: ActiveX controls and/or web browser +plug-ins) which enable mainstream web browsers to properly run certain web +applets and scripts (examples are Java, Flash, Shockwave, JavaScript, Visual +BASIC script, etc.)
  • +
  • Software drivers (i.e.: programming language run-time files) +which enable mainstream operating systems to properly run certain web applets +and scripts (such as ASP, .NET framework, PHP, SQL, etc.)
  • +
+

These are the basic requirements for web designers who wish to run BrowzOS +from a server:

+
  • Any client computer with internet access and any client-side operating +system (i.e.: GNU/Linux, Windows, Mac OS X, etc.)
  • +
  • Any file archiving program or file compression program with (at +least) ZIP file read/write access
  • +
  • Any SSH and/or FTP client program to access a server across the +internet
  • +
  • Any text editor, rich text editor or "what-you-see-is-what +you-get" editor (such as Frontpage, SciTe, Notepad, Vi, Microsoft Word, etc.)
  • +
  • Any server computer running server software (like Apache, IIS or +similar)  and any mainstream server operating system (i.e.: FreeBSD, +Windows Server 2003, etc.)
  • +
  • Any mainstream web browser with the ability to run web applets +and scripts (i.e.: Internet Explorer 6 and over, Mozilla 1.7 and over, Mozilla +Firefox, Safari, Netscape 6 and over, etc.)
  • +
  • Software drivers (i.e.: ActiveX controls and/or web browser +plug-ins) for both client computers and servers which enable mainstream web browsers to properly run certain web +applets and scripts (examples are Java, Flash, Shockwave, JavaScript, Visual +BASIC script, etc.)
  • +
  • Software drivers (i.e.: programming language run-time files) +which enable both mainstream server and client operating systems to properly run +certain web applets and scripts (like ASP, .NET framework, PHP, SQL, etc.)
  • +
+


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help_offline/run.html b/help_offline/run.html new file mode 100644 index 0000000..f82fc7b --- /dev/null +++ b/help_offline/run.html @@ -0,0 +1,55 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Installing, Running And Using "Programs" In BrowzOS

+

Installing "applications" into BrowzOS is quite easy if the +package has been extracted to a client computer's hard drive; it is simply a +matter of editing the package's web pages and scripts by adding/removing icons +and applets. The contents of the BrowzOS package is extremely modular, since each important applet +and script has its own directory and index file. So, installing an "application" +isn't too complicated, considering that a fair amount of HTML programming is required. In simple terms, +installing "applications" requires the creation of a folder in the main directory (to store +the needed applet, index and all other needed files,) as well as the editing of the Dock and Desktop scripts +in the main package.

+

Sadly, installing "programs" in BrowzOS while accessing it from a web +browser has not been implemented and is, therefore, impossible at the moment (not +to say that it's outright impossible, though). Theoretically, a web designer can have all +the needed applets installed on a server as (otherwise hidden) available +"installation" packages, then run a script which can customise the Desktop and +Dock's icons to unhide the hidden applets, thus "installing a program" into +BrowzOS through a web browser.

+

Running each "program" from BrowzOS (regardless of the package being installed on a client or server computer) is as simple as opening and using any other program on any mainstream operating system. As stated previously, an "application" can be executed by clicking on the Desktop icon's name or on the Dock icon's image with the mouse cursor. The "program" will then open in its own pop-up window. Closing the "program" is a matter of closing aid window.

+

The fact that these "applications" are so similar to the +programs available on any mainstream operating system means using each individual +"application" does not require users to have special training. However, as each +"program" is different and unique, instructions and tutorials for each +individual applet have been included inside their respective folders in the +BrowzOS package (this is especially critical for web designers). Please refer to +the included tutorials and instructions before use and modification (they're either found in text files +or in the form of comments within the index file's HTML code). If instructions for any particular +applet cannot be found, please consult the Credits page of this manual for a +list of applet/script creators and their sites. If said sites are unreachable, please consult this website's forum.

+


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help_offline/sunlogo64x30.gif b/help_offline/sunlogo64x30.gif new file mode 100644 index 0000000..98e0350 Binary files /dev/null and b/help_offline/sunlogo64x30.gif differ diff --git a/help_offline/technic.html b/help_offline/technic.html new file mode 100644 index 0000000..66c602c --- /dev/null +++ b/help_offline/technic.html @@ -0,0 +1,127 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Technical Information (For Web Designers And Applet Programmers)

+

As mentioned previously, these are the basic requirements for web designers who wish to run BrowzOS +from a server:

+
  • Any client computer with internet access and any client-side operating +system (i.e.: GNU/Linux, Windows, Mac OS X, etc.)
  • +
  • Any file archiving program or file compression program with ZIP +file read/write access
  • +
  • Any SSH and/or FTP client program to access a server across the +internet
  • +
  • Any text editor, rich text editor or "what-you-see-is-what +you-get" editor (such as Frontpage, SciTe, Notepad, Vi, Microsoft Word, etc.)
  • +
  • Any server computer running server software (like Apache, IIS or +similar)  and any mainstream server operating system (i.e.: FreeBSD, +Windows Server 2003, etc.)
  • +
  • Any mainstream web browser with the ability to run web applets +and scripts (i.e.: Internet Explorer 6 and over, Mozilla 1.7 and over, Mozilla +Firefox, Safari, Netscape 6 and over, etc.)
  • +
  • Software drivers (i.e.: ActiveX controls and/or web browser +plug-ins) for both client computers and servers which enable mainstream web browsers to properly run certain web +applets and scripts (examples are Java, Flash, Shockwave, JavaScript, Visual +BASIC script, etc.)
  • +
  • Software drivers (i.e.: programming language run-time files) +which enable both mainstream server and client operating systems to properly run +certain web applets and scripts (like ASP, .NET framework, PHP, SQL, etc.)
  • +
+

Also, there are 3 steps for web designers to get started with +BrowzOS:

+
  1. Download the package (it is + available in multiple file formats, including ZIP, 7zip, RAR, etc.)
  2. +
  3. Extract the package and make any necessary changes to the + scripts, web pages and applets in the package (this would also include adding + & removing various applets and scripts as needed)
  4. +
  5. Upload the files to any directory on the server in use
  6. +
+

Edit the web pages as needed. This includes adding/removing +scripts and applets as needed, editing scripts as needed, changing the file +names of the HTML pages as needed and editing the contents of the HTML pages +as needed (i.e.: adding/removing links, icons, etc.) +

If an applet or script requires directory/file access on a web server, log into the +server (using either an FTP or SSH client) and change the file attributes of the +directory to allow full read/write/execute permissions to the Owner, Group and +Public user groups. In an FTP client, this is done by right-clicking on the folder, +selecting File Attributes and either selecting all the check boxes or changing +the numeric value to "0777" or "777." In an SSH client, log into the UNIX shell +and type either of the following commands:

+
    +
  • chmod a+rwx foldername
  • +
  • chmod 0777 foldername
  • +
  • chmod 777 foldername
  • +
+

Where "foldername" is the name of the folder whose attributes need to be changed. This can +also be done to individual files that require full access and modification such as text files and database files; +simply substitute "foldername" with the name of the file whose attributes need to be changed.

+

If the extracted/edited package is uploaded into a website's +folder on a server (i.e.: if the package has been uploaded to + +ftp://www.yourdomain.com/yourdomain.com/ or + +ftp://yoursubdomain.domain.com/yoursubdomain.domain.com/,) open the +web browser, type the web address and the index page is loaded (which can be +changed as needed). If the extracted/edited package is uploaded into a folder +residing inside an actual domain or sub-domain's main website folder, changes +are need to the already-existing index page in order to allow navigation to the BrowzOS +index page (again, anyone can change the BrowzOS index page as needed).

+

When designing applets that require direct file access on a +client computer from a server, the best programming language to use for creating +the applet is Java. Typical Java applets do not have file access on a client +computer for security reasons. However, Java applets may be signed with a +certificate, which will give them the ability to access the client computer's +hard drive. Usually, web designers who work for professional organisations and +corporations can apply for a certificate from a Certificate Authority (CA,) +which can cost $150-$400 per year. For other web designers (especially hobbyists +and open-source web designers) who do not have the money to spend for a +certificate from a CA, Java applets can be self-signed (at no monetary cost) +with a home-made test certificate.

+

Even though home-made test certificates are free when compared to CA +certificates, the only true difference between them is the +fact that, when self-signed, the web browser will not recognise the applet as +having been created by a trustworthy source; it will be identified as +trustworthy only when signed using a CA certificate. Because of this, a client +computer user must grant permission for the applet to run and install the test +certificate. In any case, whether or not it is signed using a CA or +test certificate, the Java applet will still retain the unrestricted +functionality of direct file access on a client computer's hard drive.

+

When using Sun's Java SDK, the simplest way to self-sign a Java +applet is by completing the following commands in a command prompt:

+
  1. keytool -genkey -dname "cn=Creator" -validity 365 -storepass StorePassword -keypass KeyPassword
  2. +
  3. keytool -selfcert
  4. +
  5. jarsigner java_applet.jar -keystore StorePassword -keypass KeyPassword mykey
+

Where "StorePassword" and "KeyPassword" are the desired keystore +and key passwords for the certificate, "Creator" is the name of the applet's +creator, "365" forces the certificate to be valid for 365 days (use any value if desired) and "mykey" is +the default keystore alias (a.k.a.: the default name of the generated +certificate).

+

Please refer to the following guides for the applet-signing +tools available in Sun's Java SDK:

+ +


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/help_offline/usage.html b/help_offline/usage.html new file mode 100644 index 0000000..18c01c7 --- /dev/null +++ b/help_offline/usage.html @@ -0,0 +1,47 @@ + + + +Help + + + + + +
BrowzOS +

The Web Browser's Operating System

+
+

+
+

Using The Environment

+

Using BrowzOS is as easy as using any other mainstream operating +system. As a matter of fact, it has never been easier to navigate the +environment and complete different tasks. When BrowzOS starts, the web browser's +window will transform into an eye-pleasing Desktop, +complete with dynamic icons, an icon Dock and the ability to execute different functions.

+

On the Desktop, there are a number of icons, each representing +an executable "application." To open and run the "application," click on the +icon's name with the mouse cursor. To drag and relocate the icon to a different +part of the Desktop, click and hold the mouse cursor on top of the icon's image +and simply drag it to the desired position (in much the same manner as the +desktop icons of Windows, Mac OS X and other operating systems).

+

The Dock is just as dynamic as the Desktop; when the mouse +cursor is moved over each icon in the Dock, the icons enlarge and they're +names appear above them (in much the same manner as the dock in +Mac OS X). Once the mouse cursor is hovering over it, the icon's image can be clicked +on and the "application" will be executed.

+

Regardless of using icons from the Dock or the Desktop to run an +"application," the "application." will open in its own pop-up window. Using "applications" in BrowzOS is just as easy +as using regular applications from within any mainstream operating system. Once completed with the "application," simply click on the "Back" button of the web browser with the mouse cursor to close it and return back to the Desktop. even though the Desktop area has been occupied by an "application," the Dock will remain in full view at all times (in the case a user chooses to close one "application" only to run another in a seamless fashion).

+


+

Return to the table of contents

+ + \ No newline at end of file diff --git a/instmess/AUTHORS.txt b/instmess/AUTHORS.txt new file mode 100644 index 0000000..42f46d7 --- /dev/null +++ b/instmess/AUTHORS.txt @@ -0,0 +1 @@ +Stephane Gully diff --git a/instmess/COPYING.txt b/instmess/COPYING.txt new file mode 100644 index 0000000..0f1f743 --- /dev/null +++ b/instmess/COPYING.txt @@ -0,0 +1,17 @@ +phpFreeChat a simple, fast, and customizable chat server. +Copyright © 2006 Stephane Gully + +This library 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 2.1 of the License, or (at your option) any later version. + +This library 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. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the +Free Software Foundation, 51 Franklin St, Fifth Floor, +Boston, MA 02110-1301 USA \ No newline at end of file diff --git a/instmess/checkmd5.php b/instmess/checkmd5.php new file mode 100644 index 0000000..e758c07 --- /dev/null +++ b/instmess/checkmd5.php @@ -0,0 +1,3318 @@ +ok - ./version.txt +"; +else + $files_ko[] = "corrupted - ./version.txt (please replace this file by a correct one) +"; +if (md5(file_get_contents("./COPYING.txt")) == "6316f2b9af0cab4497de71b53e26a01f") + $files_ok[] = "ok - ./COPYING.txt +"; +else + $files_ko[] = "corrupted - ./COPYING.txt (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/doc-archi1.svg")) == "3c2b6ce5c03537846b6ec11eb8aae96e") + $files_ok[] = "ok - ./misc/doc-archi1.svg +"; +else + $files_ko[] = "corrupted - ./misc/doc-archi1.svg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/login.svg")) == "c89ef14d3f40ec6b555f76722ab140c5") + $files_ok[] = "ok - ./misc/login.svg +"; +else + $files_ko[] = "corrupted - ./misc/login.svg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/tabs.svg")) == "02dc973dc9cf45246f3cef1963cc84e2") + $files_ok[] = "ok - ./misc/tabs.svg +"; +else + $files_ko[] = "corrupted - ./misc/tabs.svg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/clock-off.png")) == "16a6384b230479c258125da0eced5f4c") + $files_ok[] = "ok - ./misc/clock-off.png +"; +else + $files_ko[] = "corrupted - ./misc/clock-off.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/color-on.png")) == "5a15495292999a129ef760c8ace8b8b7") + $files_ok[] = "ok - ./misc/color-on.png +"; +else + $files_ko[] = "corrupted - ./misc/color-on.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/clock-on.png")) == "993a86ff21548368bd912da8c381ee9c") + $files_ok[] = "ok - ./misc/clock-on.png +"; +else + $files_ko[] = "corrupted - ./misc/clock-on.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/png2gif.sh")) == "51c749d6e30da2150572587de04b0f9c") + $files_ok[] = "ok - ./misc/png2gif.sh +"; +else + $files_ko[] = "corrupted - ./misc/png2gif.sh (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/logo_88x31.png")) == "847b945b0951a0f97524815c83f0be8a") + $files_ok[] = "ok - ./misc/logo_88x31.png +"; +else + $files_ko[] = "corrupted - ./misc/logo_88x31.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/createwebinstaller.php")) == "683488cff52c87df69fae3c342db4483") + $files_ok[] = "ok - ./misc/createwebinstaller.php +"; +else + $files_ko[] = "corrupted - ./misc/createwebinstaller.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/logout.png")) == "9e56228db0820eeaee172674d5a28330") + $files_ok[] = "ok - ./misc/logout.png +"; +else + $files_ko[] = "corrupted - ./misc/logout.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/color-off.png")) == "e6c7a4168c9b9348417c49d21dc9aa63") + $files_ok[] = "ok - ./misc/color-off.png +"; +else + $files_ko[] = "corrupted - ./misc/color-off.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/login.png")) == "9982dea5d71d32f409117c06bc629c96") + $files_ok[] = "ok - ./misc/login.png +"; +else + $files_ko[] = "corrupted - ./misc/login.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/tarSource")) == "a208a7e2951eb68394d2209b9c90e09b") + $files_ok[] = "ok - ./misc/tarSource +"; +else + $files_ko[] = "corrupted - ./misc/tarSource (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/generate-doc.inc.php")) == "9a978bba9adfac00b5ed7aa0de5bd395") + $files_ok[] = "ok - ./misc/generate-doc.inc.php +"; +else + $files_ko[] = "corrupted - ./misc/generate-doc.inc.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/logo_80x15.gif")) == "a310104e2c7140b178973dba5bbab72f") + $files_ok[] = "ok - ./misc/logo_80x15.gif +"; +else + $files_ko[] = "corrupted - ./misc/logo_80x15.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/checkmd5")) == "8d9338e6c52283a3ef5b44c284fdaef9") + $files_ok[] = "ok - ./misc/checkmd5 +"; +else + $files_ko[] = "corrupted - ./misc/checkmd5 (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/bulle.png")) == "1306c813ef5bbad78d1ef6eb95dfd533") + $files_ok[] = "ok - ./misc/bulle.png +"; +else + $files_ko[] = "corrupted - ./misc/bulle.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/logo_88x31.gif")) == "e6a73f260f19df15f50224edf9d1430e") + $files_ok[] = "ok - ./misc/logo_88x31.gif +"; +else + $files_ko[] = "corrupted - ./misc/logo_88x31.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/logout.svg")) == "3083e2b038ff7fb87130dde8ccef3c9d") + $files_ok[] = "ok - ./misc/logout.svg +"; +else + $files_ko[] = "corrupted - ./misc/logout.svg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/logo.svg")) == "65ec511e2d42b0784dc7aadec1de28ab") + $files_ok[] = "ok - ./misc/logo.svg +"; +else + $files_ko[] = "corrupted - ./misc/logo.svg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/sendSource")) == "5aa8879e92df9a8ac7081f116a6fa9e6") + $files_ok[] = "ok - ./misc/sendSource +"; +else + $files_ko[] = "corrupted - ./misc/sendSource (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/logo_80x15.png")) == "f3227835145eff30023d1160297eb976") + $files_ok[] = "ok - ./misc/logo_80x15.png +"; +else + $files_ko[] = "corrupted - ./misc/logo_80x15.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./misc/i18n_update.php")) == "52c1e06f226c231e38cbeb209c0af8a4") + $files_ok[] = "ok - ./misc/i18n_update.php +"; +else + $files_ko[] = "corrupted - ./misc/i18n_update.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/cookie.js")) == "1a4f8571119a724915eff57425424f7c") + $files_ok[] = "ok - ./data/public/js/cookie.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/cookie.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/regex.js")) == "d58fd695b962fb7e55a2337932ed649e") + $files_ok[] = "ok - ./data/public/js/regex.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/regex.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/myprototype.js")) == "74211953cf62957dd33e638e903b7bb8") + $files_ok[] = "ok - ./data/public/js/myprototype.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/myprototype.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/pfcprompt.js")) == "494f2df1a9fdafc89a98ef1eab47fb93") + $files_ok[] = "ok - ./data/public/js/pfcprompt.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/pfcprompt.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/xajax.js")) == "fd8530b6b462bcf5992f804f71e7c3bb") + $files_ok[] = "ok - ./data/public/js/xajax.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/xajax.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/image_preloader.js")) == "17c2707db87834cc753a107674de859b") + $files_ok[] = "ok - ./data/public/js/image_preloader.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/image_preloader.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/pfcgui.js")) == "eb1ef97ef9b849f80191c7a73b282679") + $files_ok[] = "ok - ./data/public/js/pfcgui.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/pfcgui.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/activity.js")) == "5def443d09c43502d559328259e8b20c") + $files_ok[] = "ok - ./data/public/js/activity.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/activity.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/sprintf2.js")) == "4a69c732882320631af945a58095c220") + $files_ok[] = "ok - ./data/public/js/sprintf2.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/sprintf2.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/prototype.js")) == "d3a5b20d5368c1bcabe655b57b52d097") + $files_ok[] = "ok - ./data/public/js/prototype.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/prototype.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/compat.js")) == "508ed0c512375a88688846f248a3902f") + $files_ok[] = "ok - ./data/public/js/compat.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/compat.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/pfcresource.js")) == "7067768a5dc2fa7fa85905a528a487e9") + $files_ok[] = "ok - ./data/public/js/pfcresource.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/pfcresource.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/utf8.js")) == "231234f094dab73b1136f77e333290ca") + $files_ok[] = "ok - ./data/public/js/utf8.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/utf8.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/md5.js")) == "c440f84bf6693e366fa2a56735d6667c") + $files_ok[] = "ok - ./data/public/js/md5.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/md5.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/mousepos.js")) == "3d71c5acfdefdf84b593411546595d33") + $files_ok[] = "ok - ./data/public/js/mousepos.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/mousepos.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/pfcclient.js")) == "742b6122ebe7b07e58fe26832351b18c") + $files_ok[] = "ok - ./data/public/js/pfcclient.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/pfcclient.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/public/js/createstylerule.js")) == "cf9955cc05c571183c942a740acb7e86") + $files_ok[] = "ok - ./data/public/js/createstylerule.js +"; +else + $files_ko[] = "corrupted - ./data/public/js/createstylerule.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./data/private/.htaccess")) == "55e3fab406c794358a55ecd986e4c3ef") + $files_ok[] = "ok - ./data/private/.htaccess +"; +else + $files_ko[] = "corrupted - ./data/private/.htaccess (please replace this file by a correct one) +"; +if (md5(file_get_contents("./style/valid-css.png")) == "b5938c9e5e648c723b1497e5d69966a5") + $files_ok[] = "ok - ./style/valid-css.png +"; +else + $files_ko[] = "corrupted - ./style/valid-css.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./style/header.css")) == "b01f3034ce4f01d0bec9ab9fb0b38477") + $files_ok[] = "ok - ./style/header.css +"; +else + $files_ko[] = "corrupted - ./style/header.css (please replace this file by a correct one) +"; +if (md5(file_get_contents("./style/valid-xhtml.png")) == "0b778cd2933aca8d9301836ffb30a515") + $files_ok[] = "ok - ./style/valid-xhtml.png +"; +else + $files_ko[] = "corrupted - ./style/valid-xhtml.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./style/generic.css")) == "bde40b3db3ce695768205bad3cbde2c5") + $files_ok[] = "ok - ./style/generic.css +"; +else + $files_ko[] = "corrupted - ./style/generic.css (please replace this file by a correct one) +"; +if (md5(file_get_contents("./style/menu.css")) == "95c5939e16a0fdf8edf08d83e3e18591") + $files_ok[] = "ok - ./style/menu.css +"; +else + $files_ko[] = "corrupted - ./style/menu.css (please replace this file by a correct one) +"; +if (md5(file_get_contents("./style/content.css")) == "c3b6f1cbab0da6a197ff23383c4abbe8") + $files_ok[] = "ok - ./style/content.css +"; +else + $files_ko[] = "corrupted - ./style/content.css (please replace this file by a correct one) +"; +if (md5(file_get_contents("./style/check_on.png")) == "3565962cde4d541c51409d64db1d2b04") + $files_ok[] = "ok - ./style/check_on.png +"; +else + $files_ko[] = "corrupted - ./style/check_on.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./style/check_off.png")) == "3f62eed2a47fdc9457870c7b93300035") + $files_ok[] = "ok - ./style/check_off.png +"; +else + $files_ko[] = "corrupted - ./style/check_off.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./style/footer.css")) == "a5e55b1d48ee2ad5c986435be9459b1a") + $files_ok[] = "ok - ./style/footer.css +"; +else + $files_ko[] = "corrupted - ./style/footer.css (please replace this file by a correct one) +"; +if (md5(file_get_contents("./style/logo_88x31.gif")) == "31849457de6b49f143990b8bf80999bb") + $files_ok[] = "ok - ./style/logo_88x31.gif +"; +else + $files_ko[] = "corrupted - ./style/logo_88x31.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./style/show.js")) == "68fde759190b3d58950b452f2a2cd85c") + $files_ok[] = "ok - ./style/show.js +"; +else + $files_ko[] = "corrupted - ./style/show.js (please replace this file by a correct one) +"; +if (md5(file_get_contents("./style/logo.gif")) == "7ecaaf133a1f3f7a434936e2bae9e9c2") + $files_ok[] = "ok - ./style/logo.gif +"; +else + $files_ko[] = "corrupted - ./style/logo.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./testcase/parsecommand.php")) == "a977e31e61bc7d612b7aef03023857ca") + $files_ok[] = "ok - ./testcase/parsecommand.php +"; +else + $files_ko[] = "corrupted - ./testcase/parsecommand.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./testcase/container_mysql.php")) == "2c49ff1b4a06ad7c7680f8f2415ad423") + $files_ok[] = "ok - ./testcase/container_mysql.php +"; +else + $files_ko[] = "corrupted - ./testcase/container_mysql.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./testcase/ctype.php")) == "96d603e437c71a28b779b6c924ad092d") + $files_ok[] = "ok - ./testcase/ctype.php +"; +else + $files_ko[] = "corrupted - ./testcase/ctype.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./testcase/container_file.php")) == "fe425c872a6ac57180331783757188fe") + $files_ok[] = "ok - ./testcase/container_file.php +"; +else + $files_ko[] = "corrupted - ./testcase/container_file.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./testcase/container_generic.php")) == "14ace2ef38bfbf54d5c2db3a084df70f") + $files_ok[] = "ok - ./testcase/container_generic.php +"; +else + $files_ko[] = "corrupted - ./testcase/container_generic.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./testcase/filemtime.php")) == "376a44ded7611fb575645212a91c621b") + $files_ok[] = "ok - ./testcase/filemtime.php +"; +else + $files_ko[] = "corrupted - ./testcase/filemtime.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./AUTHORS.txt")) == "9f685cd7629840fe020328307ffa07b2") + $files_ok[] = "ok - ./AUTHORS.txt +"; +else + $files_ko[] = "corrupted - ./AUTHORS.txt (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/json/JSON.php")) == "3ce5a187c2869122f7cbcd14eec99447") + $files_ok[] = "ok - ./lib/json/JSON.php +"; +else + $files_ko[] = "corrupted - ./lib/json/JSON.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/json/LICENSE")) == "f572694efc44fa58b7ca7adf100fea7d") + $files_ok[] = "ok - ./lib/json/LICENSE +"; +else + $files_ko[] = "corrupted - ./lib/json/LICENSE (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/pearrc")) == "29c4f3f77a779177c7f6337b1112acf2") + $files_ok[] = "ok - ./lib/pear/pearrc +"; +else + $files_ko[] = "corrupted - ./lib/pear/pearrc (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/RepeatedTest.php")) == "ee280b97a8b06d6292767244a910e56c") + $files_ok[] = "ok - ./lib/pear/PHPUnit/RepeatedTest.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/RepeatedTest.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/GUI/HTML.php")) == "6b48059f47b545903725bda0076cab4a") + $files_ok[] = "ok - ./lib/pear/PHPUnit/GUI/HTML.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/GUI/HTML.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/GUI/Gtk.php")) == "2ca785505f2508f059993d9203edd166") + $files_ok[] = "ok - ./lib/pear/PHPUnit/GUI/Gtk.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/GUI/Gtk.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/GUI/HTML.tpl")) == "222a8fef6537f8fb1653f68f1b1d6a28") + $files_ok[] = "ok - ./lib/pear/PHPUnit/GUI/HTML.tpl +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/GUI/HTML.tpl (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/GUI/SetupDecorator.php")) == "39a29415cdb5191f57529b70e1ca65d8") + $files_ok[] = "ok - ./lib/pear/PHPUnit/GUI/SetupDecorator.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/GUI/SetupDecorator.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/TestCase.php")) == "9b1d78d9ad5c9f6aa0fc3ae6bf180b20") + $files_ok[] = "ok - ./lib/pear/PHPUnit/TestCase.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/TestCase.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/TestSuite.php")) == "13df054bceb10f7604a5796cac4361bc") + $files_ok[] = "ok - ./lib/pear/PHPUnit/TestSuite.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/TestSuite.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/TestFailure.php")) == "8331637fa097f2d518b4fbd918e3e7b9") + $files_ok[] = "ok - ./lib/pear/PHPUnit/TestFailure.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/TestFailure.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/Skeleton.php")) == "1d9b6092a307cebc59a4633260475c06") + $files_ok[] = "ok - ./lib/pear/PHPUnit/Skeleton.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/Skeleton.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/TestListener.php")) == "3ed518d5db2870b98198981b0b508e13") + $files_ok[] = "ok - ./lib/pear/PHPUnit/TestListener.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/TestListener.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/Assert.php")) == "02f09cb0b94b8450edfbd2536e02e39c") + $files_ok[] = "ok - ./lib/pear/PHPUnit/Assert.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/Assert.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/TestResult.php")) == "a0c025e0ca5f1922d781db6ccb29f2b9") + $files_ok[] = "ok - ./lib/pear/PHPUnit/TestResult.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/TestResult.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit/TestDecorator.php")) == "a0be8efea0c4416fe8e2b3099253a0d9") + $files_ok[] = "ok - ./lib/pear/PHPUnit/TestDecorator.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit/TestDecorator.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/pear.sh")) == "c18d0b61320cc48124806b12e3b61146") + $files_ok[] = "ok - ./lib/pear/pear.sh +"; +else + $files_ko[] = "corrupted - ./lib/pear/pear.sh (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/pear/PHPUnit.php")) == "333af11bab6eb201a1c941cded5bc9ed") + $files_ok[] = "ok - ./lib/pear/PHPUnit.php +"; +else + $files_ko[] = "corrupted - ./lib/pear/PHPUnit.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/lang.inc.php")) == "0ac3405b6b2df58470f1e88ddc2ae376") + $files_ok[] = "ok - ./lib/csstidy-1.2/lang.inc.php +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/lang.inc.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/template1.tpl")) == "a36d1408995bdc517171e06fc8d3d9b5") + $files_ok[] = "ok - ./lib/csstidy-1.2/template1.tpl +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/template1.tpl (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/css_optimiser.php")) == "3ea08cbc26770eb6014af9955759d1a5") + $files_ok[] = "ok - ./lib/csstidy-1.2/css_optimiser.php +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/css_optimiser.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/template2.tpl")) == "59041b0f200dab257f1240f2e8b7d24c") + $files_ok[] = "ok - ./lib/csstidy-1.2/template2.tpl +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/template2.tpl (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/template3.tpl")) == "d8b57d1eefa45c588389465d9cf08518") + $files_ok[] = "ok - ./lib/csstidy-1.2/template3.tpl +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/template3.tpl (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/README")) == "3a3e2ddf61de1689e042291b821262b0") + $files_ok[] = "ok - ./lib/csstidy-1.2/README +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/README (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/cssparse.css")) == "23575d87c17f20364a84e481a46d8040") + $files_ok[] = "ok - ./lib/csstidy-1.2/cssparse.css +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/cssparse.css (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/class.csstidy_print.php")) == "b8dda68989a41554e4d39402d42b2b10") + $files_ok[] = "ok - ./lib/csstidy-1.2/class.csstidy_print.php +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/class.csstidy_print.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/template.tpl")) == "d6f6aefec8ca602e51f1b13628cdea61") + $files_ok[] = "ok - ./lib/csstidy-1.2/template.tpl +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/template.tpl (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/class.csstidy_optimise.php")) == "cacf732709a79a47e86bdc530d551b98") + $files_ok[] = "ok - ./lib/csstidy-1.2/class.csstidy_optimise.php +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/class.csstidy_optimise.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/COPYING")) == "eb723b61539feef013de476e68b5c50a") + $files_ok[] = "ok - ./lib/csstidy-1.2/COPYING +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/COPYING (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/class.csstidy.php")) == "86d417902179fb54e041a9646e599e63") + $files_ok[] = "ok - ./lib/csstidy-1.2/class.csstidy.php +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/class.csstidy.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/csstidy-1.2/data.inc.php")) == "2982e57b47a025880f4fd8f34289ad5b") + $files_ok[] = "ok - ./lib/csstidy-1.2/data.inc.php +"; +else + $files_ko[] = "corrupted - ./lib/csstidy-1.2/data.inc.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/utf8/utf8_substr.php")) == "058728815cb337c7b804f1992da3a979") + $files_ok[] = "ok - ./lib/utf8/utf8_substr.php +"; +else + $files_ko[] = "corrupted - ./lib/utf8/utf8_substr.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/utf8/utf8_strlen.php")) == "dfd702b035ed705b390ccfec70826a43") + $files_ok[] = "ok - ./lib/utf8/utf8_strlen.php +"; +else + $files_ko[] = "corrupted - ./lib/utf8/utf8_strlen.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/utf8/utf8_char2byte_pos.php")) == "5a97e5ee045f76307f7f8e7692906cd9") + $files_ok[] = "ok - ./lib/utf8/utf8_char2byte_pos.php +"; +else + $files_ko[] = "corrupted - ./lib/utf8/utf8_char2byte_pos.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./lib/ctype/ctype.php")) == "ac37165b979f6f0f4069587943260f93") + $files_ok[] = "ok - ./lib/ctype/ctype.php +"; +else + $files_ko[] = "corrupted - ./lib/ctype/ctype.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/question.gif")) == "6d4a76eacdb033da1a03ddf674244e9a") + $files_ok[] = "ok - ./themes/phoenity/smileys/question.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/question.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/rolleyes.gif")) == "9df2cb8541fef0b3e4e3587de2112e6e") + $files_ok[] = "ok - ./themes/phoenity/smileys/rolleyes.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/rolleyes.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/mrgreen.gif")) == "c473e4af346804fa9424c8f0828da63a") + $files_ok[] = "ok - ./themes/phoenity/smileys/mrgreen.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/mrgreen.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/razz.gif")) == "95e00ee6fba980dc067733cd9d01b3dc") + $files_ok[] = "ok - ./themes/phoenity/smileys/razz.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/razz.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/dizzy.gif")) == "6a6f73439034d275cd9d0f1ac7a69fe7") + $files_ok[] = "ok - ./themes/phoenity/smileys/dizzy.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/dizzy.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/normal.gif")) == "2dc7fa75980fe39c6cecf4c5818dbd73") + $files_ok[] = "ok - ./themes/phoenity/smileys/normal.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/normal.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/eek.gif")) == "8f0ec25612278ebb8147d55ae5f20e8b") + $files_ok[] = "ok - ./themes/phoenity/smileys/eek.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/eek.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/laugh.gif")) == "12ed99bbd1f005d032d8ccf72cd6f32f") + $files_ok[] = "ok - ./themes/phoenity/smileys/laugh.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/laugh.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/cry.gif")) == "36215fb000e42ce42ce0decf787f9c26") + $files_ok[] = "ok - ./themes/phoenity/smileys/cry.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/cry.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/theme.txt")) == "9020b706831b00b0e3ea5610036262ec") + $files_ok[] = "ok - ./themes/phoenity/smileys/theme.txt +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/theme.txt (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/wink.gif")) == "c4faa92f831c3154ead2381d57c07279") + $files_ok[] = "ok - ./themes/phoenity/smileys/wink.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/wink.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/idea.gif")) == "59cb7ca1114595e01f7f86a4dbedbd8e") + $files_ok[] = "ok - ./themes/phoenity/smileys/idea.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/idea.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/alien.gif")) == "041346982a5ab7caf90ebaaa677cd39a") + $files_ok[] = "ok - ./themes/phoenity/smileys/alien.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/alien.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/redface.gif")) == "3a7a7500543b11a012caf1db52494ad9") + $files_ok[] = "ok - ./themes/phoenity/smileys/redface.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/redface.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/author.txt")) == "debe661cf6cde044944b5df151299f37") + $files_ok[] = "ok - ./themes/phoenity/smileys/author.txt +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/author.txt (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/exclam.gif")) == "eff97cbebe3ebaa8c7fb170b0e021ecc") + $files_ok[] = "ok - ./themes/phoenity/smileys/exclam.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/exclam.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/arrow.gif")) == "29b2a1340d0aa879ee05a2ee1ff87902") + $files_ok[] = "ok - ./themes/phoenity/smileys/arrow.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/arrow.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/smile.gif")) == "100c0286316a4d19b42f5ccd565d6064") + $files_ok[] = "ok - ./themes/phoenity/smileys/smile.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/smile.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/angry.gif")) == "51e8ddd378707afe08960b445f12ec11") + $files_ok[] = "ok - ./themes/phoenity/smileys/angry.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/angry.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/cool.gif")) == "8584627443f4ddadcf42fc1635da9c4a") + $files_ok[] = "ok - ./themes/phoenity/smileys/cool.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/cool.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/surprised.gif")) == "9dd0bae8ba4f092dcb69fae6d30f3c9d") + $files_ok[] = "ok - ./themes/phoenity/smileys/surprised.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/surprised.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/lol.gif")) == "ba84335aa5cbd179443e96bae8947f7e") + $files_ok[] = "ok - ./themes/phoenity/smileys/lol.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/lol.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/confused.gif")) == "b6985c61fea3d04687d15bf411152496") + $files_ok[] = "ok - ./themes/phoenity/smileys/confused.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/confused.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/sad.gif")) == "750285e6074e9ea7c1057e23bfa4d24f") + $files_ok[] = "ok - ./themes/phoenity/smileys/sad.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/sad.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phoenity/smileys/evil.gif")) == "dd431e18a98d8e9d54a345590ae32377") + $files_ok[] = "ok - ./themes/phoenity/smileys/evil.gif +"; +else + $files_ko[] = "corrupted - ./themes/phoenity/smileys/evil.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/info.php")) == "74a782006acc5d1f7d5035c8f2a2b4c3") + $files_ok[] = "ok - ./themes/cerutti/info.php +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/info.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/sad.png")) == "dc7e856a342a6179addfcf079f16112b") + $files_ok[] = "ok - ./themes/cerutti/smileys/sad.png +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/sad.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/tongue.png")) == "0d8be3cfef845b5b7c082ff5952adc9d") + $files_ok[] = "ok - ./themes/cerutti/smileys/tongue.png +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/tongue.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/smile.png")) == "c80d66c3ad960b512cd1dbd96edebb89") + $files_ok[] = "ok - ./themes/cerutti/smileys/smile.png +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/smile.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/cry.png")) == "e1209e55027107d428eb608be5a45303") + $files_ok[] = "ok - ./themes/cerutti/smileys/cry.png +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/cry.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/wink.png")) == "da1a6609b507a3b63e66f3e3da683157") + $files_ok[] = "ok - ./themes/cerutti/smileys/wink.png +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/wink.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/neutral.png")) == "37b38d73fb55923816cb524328935170") + $files_ok[] = "ok - ./themes/cerutti/smileys/neutral.png +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/neutral.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/theme.txt")) == "cef0e97e06b0e7655e7869754e489055") + $files_ok[] = "ok - ./themes/cerutti/smileys/theme.txt +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/theme.txt (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/dizzy.png")) == "6c4fed5689adaedd6a27844316fc9bca") + $files_ok[] = "ok - ./themes/cerutti/smileys/dizzy.png +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/dizzy.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/caca.png")) == "70d7e6aabce7bdd60f201eefe3525737") + $files_ok[] = "ok - ./themes/cerutti/smileys/caca.png +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/caca.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/lol.png")) == "bd5321c4efe6532827371632a331aac9") + $files_ok[] = "ok - ./themes/cerutti/smileys/lol.png +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/lol.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/confused.png")) == "0a8cb5ae4cf3ab97b4553874237360a9") + $files_ok[] = "ok - ./themes/cerutti/smileys/confused.png +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/confused.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/happy.png")) == "ba79b8655ff133ce8ca92ae60c73c1f9") + $files_ok[] = "ok - ./themes/cerutti/smileys/happy.png +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/happy.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/cerutti/smileys/omg.png")) == "7e46b23ed7b42e04409de85b9dbab4b3") + $files_ok[] = "ok - ./themes/cerutti/smileys/omg.png +"; +else + $files_ko[] = "corrupted - ./themes/cerutti/smileys/omg.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/online-on.gif")) == "4a03a397c30f93f4236e5e0d68026930") + $files_ok[] = "ok - ./themes/default/images/online-on.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/online-on.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/sound-off.gif")) == "6bca72a591b2b4a85669fc75d9e98b91") + $files_ok[] = "ok - ./themes/default/images/sound-off.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/sound-off.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/minimize.gif")) == "04974148218dbe7284983646bf72ac5d") + $files_ok[] = "ok - ./themes/default/images/minimize.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/minimize.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/newmsg.gif")) == "47bdd9133eef9ae200c86b706383d4dd") + $files_ok[] = "ok - ./themes/default/images/newmsg.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/newmsg.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/online-separator.gif")) == "60d59675ec62bec9f31ef28d27c389c8") + $files_ok[] = "ok - ./themes/default/images/online-separator.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/online-separator.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/bt_em.gif")) == "2364167a9fc1dd1fd376e88451e0ff8d") + $files_ok[] = "ok - ./themes/default/images/bt_em.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/bt_em.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/pv.gif")) == "44631a4b74df80854e3edd8b1da6bea9") + $files_ok[] = "ok - ./themes/default/images/pv.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/pv.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/smiley-off.gif")) == "3881a1b15d1fd06d589e43594f38b6da") + $files_ok[] = "ok - ./themes/default/images/smiley-off.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/smiley-off.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/maximize.gif")) == "a4148fb4c8e0a4e5d90c2fefb815c051") + $files_ok[] = "ok - ./themes/default/images/maximize.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/maximize.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/oldmsg.gif")) == "ae0d2ffd77ae2c56e7613a02fe9097c0") + $files_ok[] = "ok - ./themes/default/images/oldmsg.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/oldmsg.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/bt_strong.gif")) == "e1aaaf2554f0923b06dc71540b79c097") + $files_ok[] = "ok - ./themes/default/images/bt_strong.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/bt_strong.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/color-on.gif")) == "da3386e8fc6fded6551efcb0491d1533") + $files_ok[] = "ok - ./themes/default/images/color-on.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/color-on.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/color_transparent.gif")) == "d16660b200a240936a5074e2d3615323") + $files_ok[] = "ok - ./themes/default/images/color_transparent.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/color_transparent.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/openpv.gif")) == "d2db7f3ba13898e7ea780a9d7ef9c253") + $files_ok[] = "ok - ./themes/default/images/openpv.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/openpv.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/blank.gif")) == "56398e76be6355ad5999b262208a17c9") + $files_ok[] = "ok - ./themes/default/images/blank.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/blank.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/user-me.gif")) == "edd28039088e757c6da5dc8740dc768b") + $files_ok[] = "ok - ./themes/default/images/user-me.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/user-me.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/tab_remove.gif")) == "e0ff46455a64eebd74d3ddb276931f62") + $files_ok[] = "ok - ./themes/default/images/tab_remove.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/tab_remove.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/online-off.gif")) == "1522801a97107bf25d3ea83084be4766") + $files_ok[] = "ok - ./themes/default/images/online-off.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/online-off.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/smiley-on.gif")) == "4633bd4a9624021b11d55b81c6f4f568") + $files_ok[] = "ok - ./themes/default/images/smiley-on.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/smiley-on.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/user.gif")) == "58e4ec775869cd5dca578f1c71660190") + $files_ok[] = "ok - ./themes/default/images/user.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/user.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/ch-active.gif")) == "3b967759fddccb24519d3a5d8a16adda") + $files_ok[] = "ok - ./themes/default/images/ch-active.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/ch-active.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/login.gif")) == "3ddc466237777d88338b096b9ca8b168") + $files_ok[] = "ok - ./themes/default/images/login.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/login.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/close-whoisbox.gif")) == "26a743e635a56b2f3012705bcf620780") + $files_ok[] = "ok - ./themes/default/images/close-whoisbox.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/close-whoisbox.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/ch.gif")) == "d63c31b681cbebc794c57f1e5e48f8a4") + $files_ok[] = "ok - ./themes/default/images/ch.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/ch.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/pv-active.gif")) == "c60815ec64ae55b2a0bb906b699f0dd7") + $files_ok[] = "ok - ./themes/default/images/pv-active.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/pv-active.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/user_female-me.gif")) == "68958bf29993ab364faad86c5e8faf28") + $files_ok[] = "ok - ./themes/default/images/user_female-me.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/user_female-me.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/user-admin.gif")) == "26976f1650f3e09514fd514f5b45321e") + $files_ok[] = "ok - ./themes/default/images/user-admin.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/user-admin.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/bt_mail.gif")) == "2ac51570f2310600dbccde996d8900a4") + $files_ok[] = "ok - ./themes/default/images/bt_mail.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/bt_mail.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/clock-on.gif")) == "cebf0d8c08804c222a97850ff556d6f7") + $files_ok[] = "ok - ./themes/default/images/clock-on.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/clock-on.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/bt_pre.gif")) == "10e65e27c5d7f93133574a8148f20f40") + $files_ok[] = "ok - ./themes/default/images/bt_pre.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/bt_pre.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/clock-off.gif")) == "c951f92532352210637de1779034efa5") + $files_ok[] = "ok - ./themes/default/images/clock-off.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/clock-off.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/user_female.gif")) == "59018c160126b2344d0111b20e516d0f") + $files_ok[] = "ok - ./themes/default/images/user_female.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/user_female.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/sound-on.gif")) == "3dfde355962e0b26846b5a5712e06a8b") + $files_ok[] = "ok - ./themes/default/images/sound-on.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/sound-on.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/bt_del.gif")) == "0a1925de3cab1c6d50cad8056da0854d") + $files_ok[] = "ok - ./themes/default/images/bt_del.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/bt_del.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/color-off.gif")) == "c300cb4add51a7a51a5ea8255ff1215e") + $files_ok[] = "ok - ./themes/default/images/color-off.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/color-off.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/logout.gif")) == "03eec524ec44c60cb102f93ad28c3e74") + $files_ok[] = "ok - ./themes/default/images/logout.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/logout.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/background.gif")) == "ed8ef21e3e13d132be4e83b83e9f5713") + $files_ok[] = "ok - ./themes/default/images/background.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/background.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/bt_color.gif")) == "855a08ec0053e2cd6dc979b4d782a310") + $files_ok[] = "ok - ./themes/default/images/bt_color.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/bt_color.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/images/bt_ins.gif")) == "05007e708b7f96c284f65d9f2f64136f") + $files_ok[] = "ok - ./themes/default/images/bt_ins.gif +"; +else + $files_ko[] = "corrupted - ./themes/default/images/bt_ins.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/customize.js.php")) == "04d48d318d65503d3ee7f7694a7670d9") + $files_ok[] = "ok - ./themes/default/customize.js.php +"; +else + $files_ko[] = "corrupted - ./themes/default/customize.js.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/chat.js.tpl.php")) == "e3e083e587a98f7fb111aac34201cebf") + $files_ok[] = "ok - ./themes/default/chat.js.tpl.php +"; +else + $files_ko[] = "corrupted - ./themes/default/chat.js.tpl.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/style.css.php")) == "f3f141842eee25ed13be7e888ef254cf") + $files_ok[] = "ok - ./themes/default/style.css.php +"; +else + $files_ko[] = "corrupted - ./themes/default/style.css.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/chat.html.tpl.php")) == "7f842197b18a205deefca9c1f0c90cc9") + $files_ok[] = "ok - ./themes/default/chat.html.tpl.php +"; +else + $files_ko[] = "corrupted - ./themes/default/chat.html.tpl.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/info.php")) == "c1b928cf2e0a869020e268b9e66ed9f2") + $files_ok[] = "ok - ./themes/default/info.php +"; +else + $files_ko[] = "corrupted - ./themes/default/info.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/weather_clouds.png")) == "ebdaf00099570da61b98413456a28c84") + $files_ok[] = "ok - ./themes/default/smileys/weather_clouds.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/weather_clouds.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/emoticon_surprised.png")) == "fc2ea4521b77852675709abc9af1e756") + $files_ok[] = "ok - ./themes/default/smileys/emoticon_surprised.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/emoticon_surprised.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/emoticon_tongue.png")) == "88989f31f0a419767dee866c1aed125f") + $files_ok[] = "ok - ./themes/default/smileys/emoticon_tongue.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/emoticon_tongue.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/weather_sun.png")) == "6455dfc18e75d64d04af04e20dea6064") + $files_ok[] = "ok - ./themes/default/smileys/weather_sun.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/weather_sun.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/emoticon_wink.png")) == "82faf3af26d63cda8d06f693a275ed0e") + $files_ok[] = "ok - ./themes/default/smileys/emoticon_wink.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/emoticon_wink.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/emoticon_evilgrin.png")) == "ab804d23dfab2319fa42d0aabbb0c8d8") + $files_ok[] = "ok - ./themes/default/smileys/emoticon_evilgrin.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/emoticon_evilgrin.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/weather_cloudy.png")) == "d31dc9c75a8fc9cafbc62792e30e4267") + $files_ok[] = "ok - ./themes/default/smileys/weather_cloudy.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/weather_cloudy.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/emoticon_happy.png")) == "8859b6a1cff8d81261933be315fec53a") + $files_ok[] = "ok - ./themes/default/smileys/emoticon_happy.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/emoticon_happy.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/lightbulb.png")) == "5b11a0aec4a3f0f16e5931255b1a94fe") + $files_ok[] = "ok - ./themes/default/smileys/lightbulb.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/lightbulb.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/emoticon_grin.png")) == "cc48443affa11c0325e13da04ca0f4ef") + $files_ok[] = "ok - ./themes/default/smileys/emoticon_grin.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/emoticon_grin.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/theme.txt")) == "fe9c0667a0d98e95475e5c9a6def9819") + $files_ok[] = "ok - ./themes/default/smileys/theme.txt +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/theme.txt (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/emoticon_unhappy.png")) == "bae7c117bd214ebcc63cfc64d7559977") + $files_ok[] = "ok - ./themes/default/smileys/emoticon_unhappy.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/emoticon_unhappy.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/emoticon_smile.png")) == "476ec4e14fe9a402a596dfd1c1675228") + $files_ok[] = "ok - ./themes/default/smileys/emoticon_smile.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/emoticon_smile.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/emoticon_waii.png")) == "d47c83e12a92d8d640dd29f508c16ea4") + $files_ok[] = "ok - ./themes/default/smileys/emoticon_waii.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/emoticon_waii.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/weather_rain.png")) == "8be60d731745d311efcbe3cda15c30d6") + $files_ok[] = "ok - ./themes/default/smileys/weather_rain.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/weather_rain.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/exclamation.png")) == "48e1f2aec2d8495c544c3f103ed73b74") + $files_ok[] = "ok - ./themes/default/smileys/exclamation.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/exclamation.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/weather_lightning.png")) == "49b6f51a4ac3a44eb3c7de3beeb6de76") + $files_ok[] = "ok - ./themes/default/smileys/weather_lightning.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/weather_lightning.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/arrow_right.png")) == "a73e6ecdd4d3cf98ffc43342f3880d79") + $files_ok[] = "ok - ./themes/default/smileys/arrow_right.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/arrow_right.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/arrow_left.png")) == "cadca7e476b3355c5bbad957c71741a1") + $files_ok[] = "ok - ./themes/default/smileys/arrow_left.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/arrow_left.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/smileys/weather_snow.png")) == "dc0acec7ef2f2c7413dd2c31fea7e543") + $files_ok[] = "ok - ./themes/default/smileys/weather_snow.png +"; +else + $files_ko[] = "corrupted - ./themes/default/smileys/weather_snow.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/sound.swf")) == "028c63d4ecbf31143a63104337075a93") + $files_ok[] = "ok - ./themes/default/sound.swf +"; +else + $files_ko[] = "corrupted - ./themes/default/sound.swf (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/default/iepngfix.htc")) == "2a44fd1a1147042abb6b980296dd05e6") + $files_ok[] = "ok - ./themes/default/iepngfix.htc +"; +else + $files_ko[] = "corrupted - ./themes/default/iepngfix.htc (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/newmsg.png")) == "b2be5793be8f7956239c029b5535c48e") + $files_ok[] = "ok - ./themes/zilveer/images/newmsg.png +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/newmsg.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/tab_off.png")) == "f727dbdfa853f8961507523aa1164530") + $files_ok[] = "ok - ./themes/zilveer/images/tab_off.png +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/tab_off.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/user-me.gif")) == "380fee477d72cda55d565dbe60da5eb6") + $files_ok[] = "ok - ./themes/zilveer/images/user-me.gif +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/user-me.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/tab_remove.gif")) == "b7b94f7f28427d07ab12335d31e8b2c5") + $files_ok[] = "ok - ./themes/zilveer/images/tab_remove.gif +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/tab_remove.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/pfc_send.png")) == "9c362ce36768a95262b37188cc293f23") + $files_ok[] = "ok - ./themes/zilveer/images/pfc_send.png +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/pfc_send.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/user.gif")) == "7990ac9304cde47aa2fbb4b756a63948") + $files_ok[] = "ok - ./themes/zilveer/images/user.gif +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/user.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/pfc_words.png")) == "f20c325919c8696b813a99e63c726622") + $files_ok[] = "ok - ./themes/zilveer/images/pfc_words.png +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/pfc_words.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/pfc_message2.png")) == "d273de0b3621b0b15beca65e6dc606b0") + $files_ok[] = "ok - ./themes/zilveer/images/pfc_message2.png +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/pfc_message2.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/pfc_online.png")) == "553c5d9a83b91e3183e95007584afa72") + $files_ok[] = "ok - ./themes/zilveer/images/pfc_online.png +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/pfc_online.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/oldmsg.png")) == "c49bb4de07f29a5a640d71d48be79eaf") + $files_ok[] = "ok - ./themes/zilveer/images/oldmsg.png +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/oldmsg.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/pfc_message1.png")) == "9da3b43623f9b19638e81fb13c700050") + $files_ok[] = "ok - ./themes/zilveer/images/pfc_message1.png +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/pfc_message1.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/channels_content_bg.png")) == "cc0a3058c4814df9717b4ff255dc9468") + $files_ok[] = "ok - ./themes/zilveer/images/channels_content_bg.png +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/channels_content_bg.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/images/tab_on.png")) == "86a8aabbf294f6f9107fe3454b093283") + $files_ok[] = "ok - ./themes/zilveer/images/tab_on.png +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/images/tab_on.png (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/style.css.php")) == "e04f2206ee560a7f3e0829140dd7c684") + $files_ok[] = "ok - ./themes/zilveer/style.css.php +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/style.css.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/zilveer/info.php")) == "29daa1a30f32336276cf1ca2818d381d") + $files_ok[] = "ok - ./themes/zilveer/info.php +"; +else + $files_ko[] = "corrupted - ./themes/zilveer/info.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_cool.gif")) == "25c83ea511f206e88f214719dad9c88c") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_cool.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_cool.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_shifty.gif")) == "b485a5374343e8aa45fcd25e919b8aec") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_shifty.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_shifty.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_idea.gif")) == "aaebc9c048367118ba65e1da46bc3e08") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_idea.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_idea.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_naughty.gif")) == "97977aebbdc02eb3bedb03dc302d089a") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_naughty.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_naughty.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_twisted.gif")) == "c9c3d12da1e9da699e490b86d24eee85") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_twisted.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_twisted.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_razz.gif")) == "7aec68426aa06f01e2b1ac250e5aee62") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_razz.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_razz.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_question.gif")) == "0518596a4eb94c32a2b2ed898bdc3549") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_question.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_question.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_wink.gif")) == "f058206bb8ff732dbe8e7aa10d74c9cd") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_wink.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_wink.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_confused.gif")) == "4affed1b55e5f73c9f0675ae7d0ad823") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_confused.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_confused.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_cry.gif")) == "7605eca95aaeda46e641745ef6f0e0b0") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_cry.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_cry.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_snooty.gif")) == "e108015689c8fd501c1463ece170b88c") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_snooty.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_snooty.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_redface.gif")) == "d7e9d095432cbcf09375ffc782c30c23") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_redface.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_redface.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_smile.gif")) == "9ee646ffab71107d1a11407be52f33a5") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_smile.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_smile.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_drool.gif")) == "b206469a2c4afdcba57d56fbd0871439") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_drool.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_drool.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_mrgreen.gif")) == "54e8505227edae1e583cf2f9554abc3a") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_mrgreen.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_mrgreen.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_frown.gif")) == "5a50535a06def9d01076772e5e9d235b") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_frown.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_frown.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_whistle.gif")) == "8b1565b7109ab4510d2cd7edefebc463") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_whistle.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_whistle.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_mad.gif")) == "e4355c00894da1bd78341a6b54d20b56") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_mad.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_mad.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_sad.gif")) == "5a50535a06def9d01076772e5e9d235b") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_sad.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_sad.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_dance.gif")) == "b7e11d832f670c0409d23cafaa8ebd8e") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_dance.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_dance.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_sick.gif")) == "d998a746f60e61d1ea67a18df5688c19") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_sick.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_sick.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_exclaim.gif")) == "da86bbf377f97d06047aa781a582c52f") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_exclaim.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_exclaim.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/theme.txt")) == "e657ba19b831d1d2c019f95f348e19ec") + $files_ok[] = "ok - ./themes/phpbb2/smileys/theme.txt +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/theme.txt (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_doh.gif")) == "98c85ed88ddf132d1844764745f623b5") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_doh.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_doh.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_clap.gif")) == "2c89896da3b4caf1ef92889ad0672b9e") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_clap.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_clap.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_wall.gif")) == "6cbafdf6a297dd1005981d0d3a71633e") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_wall.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_wall.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_arrow.gif")) == "394bffa679f650b7d2f22aa263cc06ba") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_arrow.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_arrow.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_hand.gif")) == "fc90b256cb4d643acf1701a5655efb42") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_hand.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_hand.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_lol.gif")) == "b76e7729d43c4a49182d020741285bef") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_lol.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_lol.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_pray.gif")) == "195772090f39a559afde011d948cdfb2") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_pray.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_pray.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_silenced.gif")) == "f0d041ecd9aeec95dd41a517f31ce7b1") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_silenced.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_silenced.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_rolleyes.gif")) == "19071b1af987946e96dcef6ce0611c6b") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_rolleyes.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_rolleyes.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_neutral.gif")) == "4e8b7a51c7f60a2362a4f67fbbc937e7") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_neutral.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_neutral.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_shhh.gif")) == "ee53d3e76c849a14bada50465c45619d") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_shhh.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_shhh.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_surprised.gif")) == "ae735b5dd659dc4b3b0f249ce59bef79") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_surprised.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_surprised.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_biggrin.gif")) == "f970a6591668c625e4b9dbd3b7a450d7") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_biggrin.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_biggrin.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_boohoo.gif")) == "801e99c1fa246dc76beca2b0edf981bb") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_boohoo.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_boohoo.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_eek.gif")) == "52e43743e38a67d5d28845a104ca8c7d") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_eek.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_eek.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/icon_evil.gif")) == "178255bb3fe2c3aa790c1f8ec8738504") + $files_ok[] = "ok - ./themes/phpbb2/smileys/icon_evil.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/icon_evil.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_angel.gif")) == "ab86302adb7a6e13d0750dd1740e5c81") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_angel.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_angel.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_think.gif")) == "532437bda0de45a9802e46edb9297ecc") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_think.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_think.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_liar.gif")) == "c20ec555384f78001f2a31ae30d08df1") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_liar.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_liar.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/phpbb2/smileys/eusa_eh.gif")) == "ebabf045c8a5175713bd0df70f171337") + $files_ok[] = "ok - ./themes/phpbb2/smileys/eusa_eh.gif +"; +else + $files_ko[] = "corrupted - ./themes/phpbb2/smileys/eusa_eh.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/blune/images/online-on.gif")) == "b0d3ad5555be67098ad4744fa287587b") + $files_ok[] = "ok - ./themes/blune/images/online-on.gif +"; +else + $files_ko[] = "corrupted - ./themes/blune/images/online-on.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/blune/images/smiley-off.gif")) == "842cbed27d0c58f5266fd78571ec892b") + $files_ok[] = "ok - ./themes/blune/images/smiley-off.gif +"; +else + $files_ko[] = "corrupted - ./themes/blune/images/smiley-off.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/blune/images/online-off.gif")) == "2a674af5e4c079ffddd29636c32d4567") + $files_ok[] = "ok - ./themes/blune/images/online-off.gif +"; +else + $files_ko[] = "corrupted - ./themes/blune/images/online-off.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/blune/images/shade.gif")) == "f698bb62d97653ffa67194f15ca6dc67") + $files_ok[] = "ok - ./themes/blune/images/shade.gif +"; +else + $files_ko[] = "corrupted - ./themes/blune/images/shade.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/blune/images/smiley-on.gif")) == "d179fcd72ac088d1581293768995408f") + $files_ok[] = "ok - ./themes/blune/images/smiley-on.gif +"; +else + $files_ko[] = "corrupted - ./themes/blune/images/smiley-on.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/blune/style.css.php")) == "fa5b8509d821eae281339ca6a96642b7") + $files_ok[] = "ok - ./themes/blune/style.css.php +"; +else + $files_ko[] = "corrupted - ./themes/blune/style.css.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/blune/info.php")) == "7c8ae17657410ec3383415e51cde7346") + $files_ok[] = "ok - ./themes/blune/info.php +"; +else + $files_ko[] = "corrupted - ./themes/blune/info.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/green/images/pv.gif")) == "a2aa2c317784441d98fe6fa4fb12b40a") + $files_ok[] = "ok - ./themes/green/images/pv.gif +"; +else + $files_ko[] = "corrupted - ./themes/green/images/pv.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/green/images/shade.gif")) == "ab7a16562a524d5b013897a255e82a14") + $files_ok[] = "ok - ./themes/green/images/shade.gif +"; +else + $files_ko[] = "corrupted - ./themes/green/images/shade.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/green/images/ch-active.gif")) == "3149768414696468fc05265847317051") + $files_ok[] = "ok - ./themes/green/images/ch-active.gif +"; +else + $files_ko[] = "corrupted - ./themes/green/images/ch-active.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/green/images/ch.gif")) == "81da3989457825bf584c5d1afdfa3106") + $files_ok[] = "ok - ./themes/green/images/ch.gif +"; +else + $files_ko[] = "corrupted - ./themes/green/images/ch.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/green/images/pv-active.gif")) == "e16d305795a4b70c200d160abaf2b9e8") + $files_ok[] = "ok - ./themes/green/images/pv-active.gif +"; +else + $files_ko[] = "corrupted - ./themes/green/images/pv-active.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/green/style.css.php")) == "077af8a583bfb0cd33e26a4178aac01f") + $files_ok[] = "ok - ./themes/green/style.css.php +"; +else + $files_ko[] = "corrupted - ./themes/green/style.css.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/green/info.php")) == "c1b928cf2e0a869020e268b9e66ed9f2") + $files_ok[] = "ok - ./themes/green/info.php +"; +else + $files_ko[] = "corrupted - ./themes/green/info.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_devil.gif")) == "f0c8e3e78f63bc6efb22cbc5c865a51d") + $files_ok[] = "ok - ./themes/msn/smileys/msn_devil.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_devil.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_thumbdown.gif")) == "0bdd0133c44b68410c8aafd97317d145") + $files_ok[] = "ok - ./themes/msn/smileys/msn_thumbdown.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_thumbdown.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_think.gif")) == "9b686d115a52031f69c9560d6329e2b2") + $files_ok[] = "ok - ./themes/msn/smileys/msn_think.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_think.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_donttell.gif")) == "9e8b0728342d828f0b44032d95942c03") + $files_ok[] = "ok - ./themes/msn/smileys/msn_donttell.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_donttell.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_soccer.gif")) == "3c7dda7ffc32c2a9f16017d2fb7998fb") + $files_ok[] = "ok - ./themes/msn/smileys/msn_soccer.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_soccer.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_embarrassed.gif")) == "1167cab2b4f41b27d93ac38ea3fac5db") + $files_ok[] = "ok - ./themes/msn/smileys/msn_embarrassed.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_embarrassed.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/gnu.gif")) == "c017971d158684c9c8c90465d463aad2") + $files_ok[] = "ok - ./themes/msn/smileys/gnu.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/gnu.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_beer.gif")) == "3d87073da754666de1ae484520067e68") + $files_ok[] = "ok - ./themes/msn/smileys/msn_beer.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_beer.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_idea.gif")) == "90c7d0fd141ef81f0691b05af121d012") + $files_ok[] = "ok - ./themes/msn/smileys/msn_idea.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_idea.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_rainbow.gif")) == "a92a5f2a6255f0de1e2cb92e753e6cc9") + $files_ok[] = "ok - ./themes/msn/smileys/msn_rainbow.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_rainbow.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_plane.gif")) == "cb6454c37f97ebcd104d9f48f9580c77") + $files_ok[] = "ok - ./themes/msn/smileys/msn_plane.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_plane.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_cry.gif")) == "24a7e85607b77df2ca351b9070455614") + $files_ok[] = "ok - ./themes/msn/smileys/msn_cry.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_cry.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_smiley.gif")) == "1ee7380f552a1e8aa2dd3d79d42a1fc8") + $files_ok[] = "ok - ./themes/msn/smileys/msn_smiley.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_smiley.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/tux1.gif")) == "7daca5cbc2ed09d38745ae4455ec69fa") + $files_ok[] = "ok - ./themes/msn/smileys/tux1.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/tux1.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_phone.gif")) == "93683094f4d09077a5f36b70fcc473e4") + $files_ok[] = "ok - ./themes/msn/smileys/msn_phone.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_phone.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_sad.gif")) == "65de771d331642697b10f4078c04fc74") + $files_ok[] = "ok - ./themes/msn/smileys/msn_sad.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_sad.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_film.gif")) == "3b75f3cb6f6729db7fe4f13c0f850769") + $files_ok[] = "ok - ./themes/msn/smileys/msn_film.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_film.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_coffee.gif")) == "8e2ede77d072f9557ee09bac03709bf5") + $files_ok[] = "ok - ./themes/msn/smileys/msn_coffee.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_coffee.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_angel.gif")) == "761927ceda5306fb4f555558b2d4e77e") + $files_ok[] = "ok - ./themes/msn/smileys/msn_angel.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_angel.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_secret.gif")) == "71c843a5c1d2fa6f5d3b2eb610733886") + $files_ok[] = "ok - ./themes/msn/smileys/msn_secret.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_secret.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_cat.gif")) == "debe5495ac32c1ec84249e075e64576c") + $files_ok[] = "ok - ./themes/msn/smileys/msn_cat.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_cat.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/tux2.gif")) == "a243f306eb4b248130e32a016fc8e8bf") + $files_ok[] = "ok - ./themes/msn/smileys/tux2.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/tux2.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_cigarette.gif")) == "136b67c5f06c2ce2fca562b0ebd14ac0") + $files_ok[] = "ok - ./themes/msn/smileys/msn_cigarette.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_cigarette.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_clock.gif")) == "8c33386946ac0cdcd6b2d7f7e5c79eb3") + $files_ok[] = "ok - ./themes/msn/smileys/msn_clock.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_clock.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_thumbup.gif")) == "d78bf1b8d73a4dbfb8f183f392cad6bd") + $files_ok[] = "ok - ./themes/msn/smileys/msn_thumbup.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_thumbup.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_eyeroll.gif")) == "e34f303e2a6897a9039da2ff9d5deef9") + $files_ok[] = "ok - ./themes/msn/smileys/msn_eyeroll.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_eyeroll.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_neutral.gif")) == "314b193857a72951f6e13757002e4135") + $files_ok[] = "ok - ./themes/msn/smileys/msn_neutral.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_neutral.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_photo.gif")) == "1a69b2eb42989bd855101a96909663c8") + $files_ok[] = "ok - ./themes/msn/smileys/msn_photo.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_photo.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_sick.gif")) == "6c4b3703caac139fb4e38cb7a6823063") + $files_ok[] = "ok - ./themes/msn/smileys/msn_sick.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_sick.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_question.gif")) == "7a4f023c40389bbef08113d92384af51") + $files_ok[] = "ok - ./themes/msn/smileys/msn_question.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_question.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_island.gif")) == "688852a51f9296c19002119ba1502296") + $files_ok[] = "ok - ./themes/msn/smileys/msn_island.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_island.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_coins.gif")) == "9accfd4cf71ec99ba33d3ed686dfeed4") + $files_ok[] = "ok - ./themes/msn/smileys/msn_coins.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_coins.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_umbrella.gif")) == "583547b8b39ca01ed3eeb213978744f8") + $files_ok[] = "ok - ./themes/msn/smileys/msn_umbrella.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_umbrella.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_star.gif")) == "90eecfdf2c786fc4d9d6a30989f2039b") + $files_ok[] = "ok - ./themes/msn/smileys/msn_star.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_star.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_sunglasses.gif")) == "59933cc704a8ca4ad93db092ad20b02d") + $files_ok[] = "ok - ./themes/msn/smileys/msn_sunglasses.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_sunglasses.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_stormy.gif")) == "5d18d4dd2ce4bd87f10e28c9fb41f873") + $files_ok[] = "ok - ./themes/msn/smileys/msn_stormy.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_stormy.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_dontknow.gif")) == "79f66d387ef7be6e90d6514e4ad3c1d5") + $files_ok[] = "ok - ./themes/msn/smileys/msn_dontknow.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_dontknow.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_weird.gif")) == "f6a1caf92eec504b6c97faa9ef229040") + $files_ok[] = "ok - ./themes/msn/smileys/msn_weird.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_weird.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_drink.gif")) == "f592cca6c2c5fa68b5dbdfc55fd9cd92") + $files_ok[] = "ok - ./themes/msn/smileys/msn_drink.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_drink.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_girl.gif")) == "8404882aadbae0e7c2e32efafd6dcc0f") + $files_ok[] = "ok - ./themes/msn/smileys/msn_girl.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_girl.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_sun.gif")) == "e284ffdf854b6144619d16fb7b88cfd2") + $files_ok[] = "ok - ./themes/msn/smileys/msn_sun.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_sun.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_highfive.gif")) == "b059e1ba589cbdeccf2bbeb27ad19553") + $files_ok[] = "ok - ./themes/msn/smileys/msn_highfive.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_highfive.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_note.gif")) == "574a48a41eed6f7ba30e9dc1d0e5bcca") + $files_ok[] = "ok - ./themes/msn/smileys/msn_note.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_note.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_brb.gif")) == "069e799ebe2059e10f9afec6cdc53c90") + $files_ok[] = "ok - ./themes/msn/smileys/msn_brb.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_brb.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_cellphone.gif")) == "bfeba2b52116654f3b1ebd1d65d5e794") + $files_ok[] = "ok - ./themes/msn/smileys/msn_cellphone.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_cellphone.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_flower.gif")) == "d4486b8b37e5e04e2af5edd56e87e4b2") + $files_ok[] = "ok - ./themes/msn/smileys/msn_flower.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_flower.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_away.gif")) == "ba0f5bee90980daebc947f1b8f4a05b2") + $files_ok[] = "ok - ./themes/msn/smileys/msn_away.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_away.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_kiss.gif")) == "8155230f268dbc70200f9521bd97e077") + $files_ok[] = "ok - ./themes/msn/smileys/msn_kiss.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_kiss.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/theme.txt")) == "9416aa435b29822261452ba722280a54") + $files_ok[] = "ok - ./themes/msn/smileys/theme.txt +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/theme.txt (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_lightning.gif")) == "ff6bd1245c5976c8a5f8d1cd96dfa8c3") + $files_ok[] = "ok - ./themes/msn/smileys/msn_lightning.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_lightning.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_tongue.gif")) == "aaa70458bf6cd69e70d5d0e36b6f22af") + $files_ok[] = "ok - ./themes/msn/smileys/msn_tongue.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_tongue.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_sleep.gif")) == "5de060131577d41faa7b9905fd48fdfd") + $files_ok[] = "ok - ./themes/msn/smileys/msn_sleep.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_sleep.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_online.gif")) == "1db28f6ef55a6abd924d6bd956711ad1") + $files_ok[] = "ok - ./themes/msn/smileys/msn_online.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_online.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_pizza.gif")) == "3eec29dabde7af45ad119d33dd621eec") + $files_ok[] = "ok - ./themes/msn/smileys/msn_pizza.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_pizza.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_teeth.gif")) == "62355df280ea829874a3a2d0c2fd3e2c") + $files_ok[] = "ok - ./themes/msn/smileys/msn_teeth.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_teeth.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_turtle.gif")) == "f9fa8300b8d46806e1a91a7205a78fb3") + $files_ok[] = "ok - ./themes/msn/smileys/msn_turtle.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_turtle.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_laugh.gif")) == "fa4df10bc0f59f9cd7fb3d17d9666809") + $files_ok[] = "ok - ./themes/msn/smileys/msn_laugh.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_laugh.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_gift.gif")) == "bdc2b2e4a5c5705d397c8814322c6d71") + $files_ok[] = "ok - ./themes/msn/smileys/msn_gift.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_gift.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_brheart.gif")) == "637327f448a391921608263b7f9690ab") + $files_ok[] = "ok - ./themes/msn/smileys/msn_brheart.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_brheart.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_car.gif")) == "1be632c228e3f7234f55f19c3357a7e5") + $files_ok[] = "ok - ./themes/msn/smileys/msn_car.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_car.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_xbox.gif")) == "ee1b2f5691108851dca8c8ff44ea1302") + $files_ok[] = "ok - ./themes/msn/smileys/msn_xbox.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_xbox.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_boy.gif")) == "4d4e94744010373fdd74b2fe88bc21b6") + $files_ok[] = "ok - ./themes/msn/smileys/msn_boy.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_boy.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_heart.gif")) == "b88cb76e3415cca92a1397d3f0759968") + $files_ok[] = "ok - ./themes/msn/smileys/msn_heart.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_heart.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_plate.gif")) == "c605dae169de7175febf26c108b4442b") + $files_ok[] = "ok - ./themes/msn/smileys/msn_plate.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_plate.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_ooooh.gif")) == "229770690af97f704f306c37beb1c4ff") + $files_ok[] = "ok - ./themes/msn/smileys/msn_ooooh.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_ooooh.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_icon.gif")) == "531b3814aeedadb6ea298ab157d9bd74") + $files_ok[] = "ok - ./themes/msn/smileys/msn_icon.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_icon.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_fingerscrossed.gif")) == "060ecdb116be2852a87396b707e6d93e") + $files_ok[] = "ok - ./themes/msn/smileys/msn_fingerscrossed.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_fingerscrossed.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_wink.gif")) == "bb48a7ad6bc3eb337dd477aa1e199b51") + $files_ok[] = "ok - ./themes/msn/smileys/msn_wink.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_wink.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_occ.gif")) == "07cbf6ea5cb737eece58ba23a9a4714e") + $files_ok[] = "ok - ./themes/msn/smileys/msn_occ.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_occ.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_sleepy.gif")) == "92fa97a6e8ff890d6870c5fd10088102") + $files_ok[] = "ok - ./themes/msn/smileys/msn_sleepy.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_sleepy.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_handcuffs.gif")) == "af1b4a62da5932578b43f6c0e62e2d81") + $files_ok[] = "ok - ./themes/msn/smileys/msn_handcuffs.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_handcuffs.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_computer.gif")) == "681ab1613f1c16260af0551d6748e566") + $files_ok[] = "ok - ./themes/msn/smileys/msn_computer.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_computer.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_sheep.gif")) == "c067d767a4ce62d653feb5a1b1317ef1") + $files_ok[] = "ok - ./themes/msn/smileys/msn_sheep.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_sheep.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_email.gif")) == "e797693d1a564930d4ae5eeb832d4c94") + $files_ok[] = "ok - ./themes/msn/smileys/msn_email.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_email.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_run.gif")) == "082c8ce4c434c47408bdce9b1f4a6a21") + $files_ok[] = "ok - ./themes/msn/smileys/msn_run.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_run.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_hot.gif")) == "5016c1b9bffa889822ff9a25df0e8f04") + $files_ok[] = "ok - ./themes/msn/smileys/msn_hot.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_hot.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_cake.gif")) == "f97eb0ab0f6cd85b308c3d450453c184") + $files_ok[] = "ok - ./themes/msn/smileys/msn_cake.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_cake.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_snail.gif")) == "f3b0a85f1b7025d9e1f763f94018ff19") + $files_ok[] = "ok - ./themes/msn/smileys/msn_snail.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_snail.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_bowl.gif")) == "0cb273f38ed21dfef3ba6b3edc4edc4a") + $files_ok[] = "ok - ./themes/msn/smileys/msn_bowl.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_bowl.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_angry.gif")) == "de7d8369a1bdc21e90b12ecaceb3be95") + $files_ok[] = "ok - ./themes/msn/smileys/msn_angry.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_angry.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_deadflower.gif")) == "6c3f5436a58ec2354778d1e3287fc7f4") + $files_ok[] = "ok - ./themes/msn/smileys/msn_deadflower.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_deadflower.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_bat.gif")) == "68ebb31be298bcfaf416b230e8d4cc36") + $files_ok[] = "ok - ./themes/msn/smileys/msn_bat.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_bat.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_dog.gif")) == "016da97e9b4807ee74ea2243c582f4ae") + $files_ok[] = "ok - ./themes/msn/smileys/msn_dog.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_dog.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_sarcastic.gif")) == "b7ab1a6f9dcdcc3a0f420aa44aad1d44") + $files_ok[] = "ok - ./themes/msn/smileys/msn_sarcastic.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_sarcastic.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_nerd.gif")) == "c536cd997d2f943643b6d1285712f4c8") + $files_ok[] = "ok - ./themes/msn/smileys/msn_nerd.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_nerd.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_runback.gif")) == "0a12e8849b43091a7994e7cb444bc8bf") + $files_ok[] = "ok - ./themes/msn/smileys/msn_runback.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_runback.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./themes/msn/smileys/msn_party.gif")) == "312e8faf0d264ef3520c9873a8c01cdf") + $files_ok[] = "ok - ./themes/msn/smileys/msn_party.gif +"; +else + $files_ko[] = "corrupted - ./themes/msn/smileys/msn_party.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/pl_PL/main.php")) == "16a1211f7e55157e67775228d20a681a") + $files_ok[] = "ok - ./i18n/pl_PL/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/pl_PL/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/sv_SE/main.php")) == "cabfe1e9bfb71fc9d420650a8c1a0a71") + $files_ok[] = "ok - ./i18n/sv_SE/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/sv_SE/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/nb_NO/main.php")) == "8e018225d8648f16ba3b733982caf853") + $files_ok[] = "ok - ./i18n/nb_NO/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/nb_NO/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/uk_RO/main.php")) == "fcd882e3b153b241124816ab9fab17f7") + $files_ok[] = "ok - ./i18n/uk_RO/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/uk_RO/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/nl_BE/main.php")) == "b0f0087c305b44a4a1e57b042ffd42b4") + $files_ok[] = "ok - ./i18n/nl_BE/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/nl_BE/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/fr_FR/main.php")) == "e6750a013022143fab98901ad01a0436") + $files_ok[] = "ok - ./i18n/fr_FR/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/fr_FR/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/pt_BR/main.php")) == "5a3d6e70791908ac62e8b06183545a15") + $files_ok[] = "ok - ./i18n/pt_BR/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/pt_BR/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/bg_BG/main.php")) == "de9bb201ba35e8a09d7c97f6d73875c5") + $files_ok[] = "ok - ./i18n/bg_BG/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/bg_BG/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/ru_RU/main.php")) == "3bfc1ff64718bd4adca5a3c6f252ca2c") + $files_ok[] = "ok - ./i18n/ru_RU/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/ru_RU/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/hy_AM/main.php")) == "6faffdbfcd04731b98bdef78c1804f5f") + $files_ok[] = "ok - ./i18n/hy_AM/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/hy_AM/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/ba_BA/main.php")) == "04ef440a32fdf113f7f1db4631922638") + $files_ok[] = "ok - ./i18n/ba_BA/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/ba_BA/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/zh_TW/main.php")) == "3ff9d3825704578c9477c6108c5343eb") + $files_ok[] = "ok - ./i18n/zh_TW/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/zh_TW/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/de_DE-informal/main.php")) == "8d71ca20566ad5f13a6646c30a37af49") + $files_ok[] = "ok - ./i18n/de_DE-informal/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/de_DE-informal/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/de_DE-formal/main.php")) == "c6c11bfdc7bf24a0b17fb1d78064b208") + $files_ok[] = "ok - ./i18n/de_DE-formal/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/de_DE-formal/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/tr_TR/main.php")) == "7ad1a6cfbca14cdca9e40b79051c7d3f") + $files_ok[] = "ok - ./i18n/tr_TR/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/tr_TR/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/el_GR/main.php")) == "1877b453859585df1b1a1add0c0b97ed") + $files_ok[] = "ok - ./i18n/el_GR/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/el_GR/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/sk_SK/main.php")) == "3ad251cc3cbbfd732b1bf1b18aef8234") + $files_ok[] = "ok - ./i18n/sk_SK/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/sk_SK/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/pt_PT/main.php")) == "c60949a9016570f4e5a77139246df3c8") + $files_ok[] = "ok - ./i18n/pt_PT/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/pt_PT/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/ar_LB/main.php")) == "d60399d5f730dab4cfb63b77cde0d331") + $files_ok[] = "ok - ./i18n/ar_LB/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/ar_LB/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/eo/main.php")) == "21067405706fcf03557e2c019a7a480c") + $files_ok[] = "ok - ./i18n/eo/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/eo/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/ca_CAT/main.php")) == "4b522bfbee5eccbe5715c3c23412fb54") + $files_ok[] = "ok - ./i18n/ca_CAT/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/ca_CAT/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/ja_JP/main.php")) == "3b23f32a917249f1d36de8d5dbcca3fc") + $files_ok[] = "ok - ./i18n/ja_JP/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/ja_JP/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/oc_FR/main.php")) == "02e5a2c72f934aef2ad1a24d676f2a2d") + $files_ok[] = "ok - ./i18n/oc_FR/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/oc_FR/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/es_ES/main.php")) == "b9aa34d080a75d95e251de7ae76cba8d") + $files_ok[] = "ok - ./i18n/es_ES/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/es_ES/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/zh_CN/main.php")) == "619031f8d43732e661789f1754a041ba") + $files_ok[] = "ok - ./i18n/zh_CN/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/zh_CN/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/da_DK/main.php")) == "62629eebfc43a4ea1bdbe20de7e3a9b4") + $files_ok[] = "ok - ./i18n/da_DK/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/da_DK/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/uk_UA/main.php")) == "c4c3df8be94d22b1cbc5a24ddf1517fa") + $files_ok[] = "ok - ./i18n/uk_UA/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/uk_UA/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/vi_VN/main.php")) == "8098c3bffc699bf1fdf5f866fcebb679") + $files_ok[] = "ok - ./i18n/vi_VN/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/vi_VN/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/hu_HU/main.php")) == "e4f3028730b229e7a1a047e562095414") + $files_ok[] = "ok - ./i18n/hu_HU/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/hu_HU/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/bn_BD/main.php")) == "fa68613c5c18b37a815f5e29eb5e13f9") + $files_ok[] = "ok - ./i18n/bn_BD/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/bn_BD/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/id_ID/main.php")) == "0c18907029e973c59ee6b64d0a545cc3") + $files_ok[] = "ok - ./i18n/id_ID/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/id_ID/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/cs_CZ/main.php")) == "bd74b9c78cab557304db9a1ca53ad05b") + $files_ok[] = "ok - ./i18n/cs_CZ/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/cs_CZ/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/sr_CS/main.php")) == "a8e9bf8b4bfdbe12a171b061a2c9807b") + $files_ok[] = "ok - ./i18n/sr_CS/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/sr_CS/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/nn_NO/main.php")) == "b2e0479d4b6854afeb4a38307bfe4664") + $files_ok[] = "ok - ./i18n/nn_NO/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/nn_NO/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/it_IT/main.php")) == "26f756e03084d15ca58ce4cd583a9440") + $files_ok[] = "ok - ./i18n/it_IT/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/it_IT/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/en_US/main.php")) == "4d70311a211061651ed58cafdcd03abb") + $files_ok[] = "ok - ./i18n/en_US/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/en_US/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/th_TH/main.php")) == "365e794be31b5dfb3eb682d558a64d0b") + $files_ok[] = "ok - ./i18n/th_TH/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/th_TH/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/gl_ES/main.php")) == "b9fea56a94374c5762b37b24b14824f5") + $files_ok[] = "ok - ./i18n/gl_ES/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/gl_ES/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/ko_KR/main.php")) == "d1e94defc466b9d4ef6a9f3199b011bf") + $files_ok[] = "ok - ./i18n/ko_KR/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/ko_KR/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/mk_MK/main.php")) == "36bb02ca69fd797e5eac17d09157c6d2") + $files_ok[] = "ok - ./i18n/mk_MK/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/mk_MK/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/nl_NL/main.php")) == "596c5038f8b08908b858dfe2cbbf0b02") + $files_ok[] = "ok - ./i18n/nl_NL/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/nl_NL/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./i18n/hr_HR/main.php")) == "3367529dfafd3f7a3107cc8bf1ec3532") + $files_ok[] = "ok - ./i18n/hr_HR/main.php +"; +else + $files_ko[] = "corrupted - ./i18n/hr_HR/main.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo33_in_portuguese_from_portugal.php")) == "111ab2ec143365ce47801ce7e8e3a912") + $files_ok[] = "ok - ./demo/demo33_in_portuguese_from_portugal.php +"; +else + $files_ko[] = "corrupted - ./demo/demo33_in_portuguese_from_portugal.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo43_change_the_nicknames_colors.php")) == "e772c5027d6b635888f9896ff8b22727") + $files_ok[] = "ok - ./demo/demo43_change_the_nicknames_colors.php +"; +else + $files_ko[] = "corrupted - ./demo/demo43_change_the_nicknames_colors.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo40_in_turkish.php")) == "1d08d39f0f373c929207402108f760f9") + $files_ok[] = "ok - ./demo/demo40_in_turkish.php +"; +else + $files_ko[] = "corrupted - ./demo/demo40_in_turkish.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo53_in_armenian.php")) == "4a824e6b72a5121931d0d8b7709ba746") + $files_ok[] = "ok - ./demo/demo53_in_armenian.php +"; +else + $files_ko[] = "corrupted - ./demo/demo53_in_armenian.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo24_in_spanish.php")) == "05fa6ce774c1c0f83482fc31294aad26") + $files_ok[] = "ok - ./demo/demo24_in_spanish.php +"; +else + $files_ko[] = "corrupted - ./demo/demo24_in_spanish.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo65_metadata_and_html.php")) == "3a016914110dd3f94f51e1349e21871c") + $files_ok[] = "ok - ./demo/demo65_metadata_and_html.php +"; +else + $files_ko[] = "corrupted - ./demo/demo65_metadata_and_html.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo62_in_belgian_dutch.php")) == "fca924fa4cb8fcdd7d2347283545c554") + $files_ok[] = "ok - ./demo/demo62_in_belgian_dutch.php +"; +else + $files_ko[] = "corrupted - ./demo/demo62_in_belgian_dutch.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/index.php")) == "8e97a6a2aad0bce5bd4dea95af3ad489") + $files_ok[] = "ok - ./demo/index.php +"; +else + $files_ko[] = "corrupted - ./demo/index.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo19_in_japanese.php")) == "96e1af245c9b0ec07eac075f4cd78966") + $files_ok[] = "ok - ./demo/demo19_in_japanese.php +"; +else + $files_ko[] = "corrupted - ./demo/demo19_in_japanese.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo50_customized_usermetadata.php")) == "4efb7ea1bdf5abbea19c0008279c1798") + $files_ok[] = "ok - ./demo/demo50_customized_usermetadata.php +"; +else + $files_ko[] = "corrupted - ./demo/demo50_customized_usermetadata.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo4_simulate_slow_server.php")) == "74d20f58ef20fbbf2147b376b19eb84a") + $files_ok[] = "ok - ./demo/demo4_simulate_slow_server.php +"; +else + $files_ko[] = "corrupted - ./demo/demo4_simulate_slow_server.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo16_in_arabic.php")) == "fd0960100b59bcf705900d7bad450442") + $files_ok[] = "ok - ./demo/demo16_in_arabic.php +"; +else + $files_ko[] = "corrupted - ./demo/demo16_in_arabic.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo42_in_chinese_from_taiwan.php")) == "77bdae5f74fa654ff8741c4efe8b7ccc") + $files_ok[] = "ok - ./demo/demo42_in_chinese_from_taiwan.php +"; +else + $files_ko[] = "corrupted - ./demo/demo42_in_chinese_from_taiwan.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo44_green_theme.php")) == "3b9670fd07c36c5ed7084564185463a0") + $files_ok[] = "ok - ./demo/demo44_green_theme.php +"; +else + $files_ko[] = "corrupted - ./demo/demo44_green_theme.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo5_customized_style_data/mytheme/images/newmsg.gif")) == "c1148ffba533cf181ca98b0d779330a1") + $files_ok[] = "ok - ./demo/demo5_customized_style_data/mytheme/images/newmsg.gif +"; +else + $files_ko[] = "corrupted - ./demo/demo5_customized_style_data/mytheme/images/newmsg.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo5_customized_style_data/mytheme/images/oldmsg.gif")) == "33eb60245c48c250ce1fbd3870e5ccd7") + $files_ok[] = "ok - ./demo/demo5_customized_style_data/mytheme/images/oldmsg.gif +"; +else + $files_ko[] = "corrupted - ./demo/demo5_customized_style_data/mytheme/images/oldmsg.gif (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo5_customized_style_data/mytheme/images/brick.jpg")) == "113df3c3b797b22ffd682c9fc2357c81") + $files_ok[] = "ok - ./demo/demo5_customized_style_data/mytheme/images/brick.jpg +"; +else + $files_ko[] = "corrupted - ./demo/demo5_customized_style_data/mytheme/images/brick.jpg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo5_customized_style_data/mytheme/style.css.php")) == "b456d205618f090481f8763f3512e9da") + $files_ok[] = "ok - ./demo/demo5_customized_style_data/mytheme/style.css.php +"; +else + $files_ko[] = "corrupted - ./demo/demo5_customized_style_data/mytheme/style.css.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo58_in_danish.php")) == "4a9e22a2f24208e8b24ca8155cb3e069") + $files_ok[] = "ok - ./demo/demo58_in_danish.php +"; +else + $files_ko[] = "corrupted - ./demo/demo58_in_danish.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo5_customized_style.php")) == "1de480a513097d03b586c3fc62f57bb8") + $files_ok[] = "ok - ./demo/demo5_customized_style.php +"; +else + $files_ko[] = "corrupted - ./demo/demo5_customized_style.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo51_zilveer_theme.php")) == "51fb864e9ec2fcc99e71b54dda1f7bd4") + $files_ok[] = "ok - ./demo/demo51_zilveer_theme.php +"; +else + $files_ko[] = "corrupted - ./demo/demo51_zilveer_theme.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo13_in_german_informal_language.php")) == "1b8f100c417dce97e33f04f7693f64b1") + $files_ok[] = "ok - ./demo/demo13_in_german_informal_language.php +"; +else + $files_ko[] = "corrupted - ./demo/demo13_in_german_informal_language.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo28_blune_theme.php")) == "b4c7072ea53862d10115f1e25262377d") + $files_ok[] = "ok - ./demo/demo28_blune_theme.php +"; +else + $files_ko[] = "corrupted - ./demo/demo28_blune_theme.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo63_in_thai.php")) == "f92172c631f8cf1e62675f716cb3d17d") + $files_ok[] = "ok - ./demo/demo63_in_thai.php +"; +else + $files_ko[] = "corrupted - ./demo/demo63_in_thai.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo7_in_english.php")) == "9c75e76a8f0cbcee4fbd4d0aab67573f") + $files_ok[] = "ok - ./demo/demo7_in_english.php +"; +else + $files_ko[] = "corrupted - ./demo/demo7_in_english.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo50_data/avatar6.jpg")) == "d2c4152dad072c5901e788d8208793e0") + $files_ok[] = "ok - ./demo/demo50_data/avatar6.jpg +"; +else + $files_ko[] = "corrupted - ./demo/demo50_data/avatar6.jpg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo50_data/avatar4.jpg")) == "fb11c521913c09945b62208748b5ea48") + $files_ok[] = "ok - ./demo/demo50_data/avatar4.jpg +"; +else + $files_ko[] = "corrupted - ./demo/demo50_data/avatar4.jpg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo50_data/avatar8.jpg")) == "4f1d3dab54e822397ebaa017cdb442d9") + $files_ok[] = "ok - ./demo/demo50_data/avatar8.jpg +"; +else + $files_ko[] = "corrupted - ./demo/demo50_data/avatar8.jpg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo50_data/avatar3.jpg")) == "84aa2dcd8559c37ca0aa749cb7b84122") + $files_ok[] = "ok - ./demo/demo50_data/avatar3.jpg +"; +else + $files_ko[] = "corrupted - ./demo/demo50_data/avatar3.jpg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo50_data/avatar5.jpg")) == "fb9ab84220704278ccef13757b494e9a") + $files_ok[] = "ok - ./demo/demo50_data/avatar5.jpg +"; +else + $files_ko[] = "corrupted - ./demo/demo50_data/avatar5.jpg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo50_data/avatar2.jpg")) == "2dc0dc886216c1c47ce7623a881df5d6") + $files_ok[] = "ok - ./demo/demo50_data/avatar2.jpg +"; +else + $files_ko[] = "corrupted - ./demo/demo50_data/avatar2.jpg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo50_data/avatar1.jpg")) == "496590f29defff6282a34e450d8fbe52") + $files_ok[] = "ok - ./demo/demo50_data/avatar1.jpg +"; +else + $files_ko[] = "corrupted - ./demo/demo50_data/avatar1.jpg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo50_data/avatar7.jpg")) == "06d4ebc78ff034e0780cb777f5f37867") + $files_ok[] = "ok - ./demo/demo50_data/avatar7.jpg +"; +else + $files_ko[] = "corrupted - ./demo/demo50_data/avatar7.jpg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo50_data/mytheme/customize.js.php")) == "ff193fcdd1d9a17f6e280fd91d9eaef5") + $files_ok[] = "ok - ./demo/demo50_data/mytheme/customize.js.php +"; +else + $files_ko[] = "corrupted - ./demo/demo50_data/mytheme/customize.js.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo50_data/mytheme/style.css.php")) == "16191d93c942322d7c95289426ba2bbd") + $files_ok[] = "ok - ./demo/demo50_data/mytheme/style.css.php +"; +else + $files_ko[] = "corrupted - ./demo/demo50_data/mytheme/style.css.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo50_data/avatar9.jpg")) == "2d95a0d6104eb6e0cf13b75c9b547c29") + $files_ok[] = "ok - ./demo/demo50_data/avatar9.jpg +"; +else + $files_ko[] = "corrupted - ./demo/demo50_data/avatar9.jpg (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo31_show_who_is_online-whoisonline.php")) == "385c3c05f345fe5a6780b9b99904d1ae") + $files_ok[] = "ok - ./demo/demo31_show_who_is_online-whoisonline.php +"; +else + $files_ko[] = "corrupted - ./demo/demo31_show_who_is_online-whoisonline.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo56_in_romanian.php")) == "5a3ad282cfd54a8f4e6ebac780cb91c9") + $files_ok[] = "ok - ./demo/demo56_in_romanian.php +"; +else + $files_ko[] = "corrupted - ./demo/demo56_in_romanian.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo32_show_last_messages-config.php")) == "347d00d282b0b690f156ece051cbdd0b") + $files_ok[] = "ok - ./demo/demo32_show_last_messages-config.php +"; +else + $files_ko[] = "corrupted - ./demo/demo32_show_last_messages-config.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo23_in_italian.php")) == "2308d6864ef8759528be194da1b58b57") + $files_ok[] = "ok - ./demo/demo23_in_italian.php +"; +else + $files_ko[] = "corrupted - ./demo/demo23_in_italian.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo12_phoenity_smiley_theme.php")) == "f27f8eac7bf12caf3de5b1aa41c6382b") + $files_ok[] = "ok - ./demo/demo12_phoenity_smiley_theme.php +"; +else + $files_ko[] = "corrupted - ./demo/demo12_phoenity_smiley_theme.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo1_simple.php")) == "0d0812eddb012c4d7c80f021105e2622") + $files_ok[] = "ok - ./demo/demo1_simple.php +"; +else + $files_ko[] = "corrupted - ./demo/demo1_simple.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo41_in_greek.php")) == "e6525eaaaf0633e64b4f3a51edd329bb") + $files_ok[] = "ok - ./demo/demo41_in_greek.php +"; +else + $files_ko[] = "corrupted - ./demo/demo41_in_greek.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo2_simple_with_params.php")) == "dc8ed5868e6531af4befc121495b2d42") + $files_ok[] = "ok - ./demo/demo2_simple_with_params.php +"; +else + $files_ko[] = "corrupted - ./demo/demo2_simple_with_params.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo9_with_a_utf8_encoded_nickname.php")) == "a786133e52e57301a3791c2fd9e35c8e") + $files_ok[] = "ok - ./demo/demo9_with_a_utf8_encoded_nickname.php +"; +else + $files_ko[] = "corrupted - ./demo/demo9_with_a_utf8_encoded_nickname.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo28_mini_blune_theme.php")) == "fd4379a544921ce3c777fcb4cfce5700") + $files_ok[] = "ok - ./demo/demo28_mini_blune_theme.php +"; +else + $files_ko[] = "corrupted - ./demo/demo28_mini_blune_theme.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo48_custom_proxy/myproxy.class.php")) == "9e3cce087415c80fa1f6f72c8bb101e8") + $files_ok[] = "ok - ./demo/demo48_custom_proxy/myproxy.class.php +"; +else + $files_ko[] = "corrupted - ./demo/demo48_custom_proxy/myproxy.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo54_in_esperanto.php")) == "85c1b4ad0a002c6c92778480e7813cb6") + $files_ok[] = "ok - ./demo/demo54_in_esperanto.php +"; +else + $files_ko[] = "corrupted - ./demo/demo54_in_esperanto.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo3_client.php")) == "071e7f19dbaab0290f44dba1224bf8e1") + $files_ok[] = "ok - ./demo/demo3_client.php +"; +else + $files_ko[] = "corrupted - ./demo/demo3_client.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo55_mysql_container.php")) == "4c366abab53c31fe812b24334694a6b9") + $files_ok[] = "ok - ./demo/demo55_mysql_container.php +"; +else + $files_ko[] = "corrupted - ./demo/demo55_mysql_container.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo14_in_german_formal_language.php")) == "4af0267b4dde60d8928e63c7fbb8b68c") + $files_ok[] = "ok - ./demo/demo14_in_german_formal_language.php +"; +else + $files_ko[] = "corrupted - ./demo/demo14_in_german_formal_language.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo31_show_who_is_online-chat.php")) == "07225259dc282974d36cb633647b4c4a") + $files_ok[] = "ok - ./demo/demo31_show_who_is_online-chat.php +"; +else + $files_ko[] = "corrupted - ./demo/demo31_show_who_is_online-chat.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo47_in_polish.php")) == "6cb186e1c56095a3bc81bf8b65b232c7") + $files_ok[] = "ok - ./demo/demo47_in_polish.php +"; +else + $files_ko[] = "corrupted - ./demo/demo47_in_polish.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo10_in_chinese.php")) == "e371d7c1b17dbd726d356c7f9d8435a0") + $files_ok[] = "ok - ./demo/demo10_in_chinese.php +"; +else + $files_ko[] = "corrupted - ./demo/demo10_in_chinese.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo8_with_a_iso-8859-1_encoded_page.php")) == "9dd16df569c9f9be24cd5fd2eaa632b6") + $files_ok[] = "ok - ./demo/demo8_with_a_iso-8859-1_encoded_page.php +"; +else + $files_ko[] = "corrupted - ./demo/demo8_with_a_iso-8859-1_encoded_page.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo57_in_korean.php")) == "0c2c31e09d644da3373e72c4a09290a1") + $files_ok[] = "ok - ./demo/demo57_in_korean.php +"; +else + $files_ko[] = "corrupted - ./demo/demo57_in_korean.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo27_dice.class.php")) == "ff2767708426a5cbe02ea7e42da074dd") + $files_ok[] = "ok - ./demo/demo27_dice.class.php +"; +else + $files_ko[] = "corrupted - ./demo/demo27_dice.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo34_add_a_link_on_nicknames.php")) == "f4520e6e998efd7820c8381b9e2c41dd") + $files_ok[] = "ok - ./demo/demo34_add_a_link_on_nicknames.php +"; +else + $files_ko[] = "corrupted - ./demo/demo34_add_a_link_on_nicknames.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo32_show_last_messages-chat.php")) == "49e651b89129264c33335b6765b40286") + $files_ok[] = "ok - ./demo/demo32_show_last_messages-chat.php +"; +else + $files_ko[] = "corrupted - ./demo/demo32_show_last_messages-chat.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo66_in_occitan.php")) == "2fdb9df74688e852c64b9c84abfdcc77") + $files_ok[] = "ok - ./demo/demo66_in_occitan.php +"; +else + $files_ko[] = "corrupted - ./demo/demo66_in_occitan.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo37_in_dutch_from_netherlands.php")) == "78a1da944b2656e3ac42baac44068d73") + $files_ok[] = "ok - ./demo/demo37_in_dutch_from_netherlands.php +"; +else + $files_ko[] = "corrupted - ./demo/demo37_in_dutch_from_netherlands.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo27_customized_command.php")) == "d4b03cfd654bbafa2111f321deb6229a") + $files_ok[] = "ok - ./demo/demo27_customized_command.php +"; +else + $files_ko[] = "corrupted - ./demo/demo27_customized_command.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo65_chat_popup.php")) == "f35cefeda07143b0857e89d44c28a0d7") + $files_ok[] = "ok - ./demo/demo65_chat_popup.php +"; +else + $files_ko[] = "corrupted - ./demo/demo65_chat_popup.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo34_add_a_link_on_nicknames/mytheme/customize.js.php")) == "3889acabffd488200af1c617e4dddf4d") + $files_ok[] = "ok - ./demo/demo34_add_a_link_on_nicknames/mytheme/customize.js.php +"; +else + $files_ko[] = "corrupted - ./demo/demo34_add_a_link_on_nicknames/mytheme/customize.js.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo21_with_hardcoded_urls.php")) == "90dd0d7f70a1552cd5873653a45fb69a") + $files_ok[] = "ok - ./demo/demo21_with_hardcoded_urls.php +"; +else + $files_ko[] = "corrupted - ./demo/demo21_with_hardcoded_urls.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo48_custom_proxy.php")) == "754ab358b0d5aa0a26bef918c39337a4") + $files_ok[] = "ok - ./demo/demo48_custom_proxy.php +"; +else + $files_ko[] = "corrupted - ./demo/demo48_custom_proxy.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo59_in_norwegian_nynorsk.php")) == "afc9aa787ed0eeff20c56e32e6e3e9a4") + $files_ok[] = "ok - ./demo/demo59_in_norwegian_nynorsk.php +"; +else + $files_ko[] = "corrupted - ./demo/demo59_in_norwegian_nynorsk.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo29_in_russian.php")) == "0e9f5e85b4f55b58349fca9b53b49a36") + $files_ok[] = "ok - ./demo/demo29_in_russian.php +"; +else + $files_ko[] = "corrupted - ./demo/demo29_in_russian.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo15_multiple_channel.php")) == "c58e73c3bb2f49d4e1cf32ef29d1d0a7") + $files_ok[] = "ok - ./demo/demo15_multiple_channel.php +"; +else + $files_ko[] = "corrupted - ./demo/demo15_multiple_channel.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo46_in_hungarian.php")) == "4e14835a935b5d76a12de3e218c7e778") + $files_ok[] = "ok - ./demo/demo46_in_hungarian.php +"; +else + $files_ko[] = "corrupted - ./demo/demo46_in_hungarian.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo32_show_last_messages-showlastmsg.php")) == "01754bddc19bee490d09d2a9c7179004") + $files_ok[] = "ok - ./demo/demo32_show_last_messages-showlastmsg.php +"; +else + $files_ko[] = "corrupted - ./demo/demo32_show_last_messages-showlastmsg.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo3_server.php")) == "2556330e0b99d525fdfa7ce69e94b9d9") + $files_ok[] = "ok - ./demo/demo3_server.php +"; +else + $files_ko[] = "corrupted - ./demo/demo3_server.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo64_in_galician.php")) == "8c95ea4803f346d176f04cfef758d7cb") + $files_ok[] = "ok - ./demo/demo64_in_galician.php +"; +else + $files_ko[] = "corrupted - ./demo/demo64_in_galician.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo18_phpbb2_smiley_theme.php")) == "84b8b67d271bb58a905f54ed22abc6ee") + $files_ok[] = "ok - ./demo/demo18_phpbb2_smiley_theme.php +"; +else + $files_ko[] = "corrupted - ./demo/demo18_phpbb2_smiley_theme.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo3_config.php")) == "2fab383629390a4825a0b393380d9195") + $files_ok[] = "ok - ./demo/demo3_config.php +"; +else + $files_ko[] = "corrupted - ./demo/demo3_config.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo17_cerutti_smiley_theme.php")) == "be46cc8d9c69d110b2678d356bf3d604") + $files_ok[] = "ok - ./demo/demo17_cerutti_smiley_theme.php +"; +else + $files_ko[] = "corrupted - ./demo/demo17_cerutti_smiley_theme.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo25_in_indonesian.php")) == "933c8b54f2a29dd397bac1aba24ac58a") + $files_ok[] = "ok - ./demo/demo25_in_indonesian.php +"; +else + $files_ko[] = "corrupted - ./demo/demo25_in_indonesian.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo61_in_croatian.php")) == "6c48669968198ec10f9b41e1519773cb") + $files_ok[] = "ok - ./demo/demo61_in_croatian.php +"; +else + $files_ko[] = "corrupted - ./demo/demo61_in_croatian.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo60_in_vietnamese.php")) == "72750f35d37ad75f0b695bd654784676") + $files_ok[] = "ok - ./demo/demo60_in_vietnamese.php +"; +else + $files_ko[] = "corrupted - ./demo/demo60_in_vietnamese.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo22_in_serbian_croatian.php")) == "6062456ce7bfccaaecd0a441ff58bc96") + $files_ok[] = "ok - ./demo/demo22_in_serbian_croatian.php +"; +else + $files_ko[] = "corrupted - ./demo/demo22_in_serbian_croatian.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo36_in_ukrainian.php")) == "543353b9a7ea2b37837f7f262a36e6a2") + $files_ok[] = "ok - ./demo/demo36_in_ukrainian.php +"; +else + $files_ko[] = "corrupted - ./demo/demo36_in_ukrainian.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo26_in_swedish.php")) == "c7379cf2d100b6fb59536e26ca268e9b") + $files_ok[] = "ok - ./demo/demo26_in_swedish.php +"; +else + $files_ko[] = "corrupted - ./demo/demo26_in_swedish.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo45_in_bulgarian.php")) == "4ed3e3b2e134b1d6e0ed435a4f583e24") + $files_ok[] = "ok - ./demo/demo45_in_bulgarian.php +"; +else + $files_ko[] = "corrupted - ./demo/demo45_in_bulgarian.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo6_in_french.php")) == "b295120ea85ce1d59a3d1c775a496059") + $files_ok[] = "ok - ./demo/demo6_in_french.php +"; +else + $files_ko[] = "corrupted - ./demo/demo6_in_french.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo49_msn_smiley_theme.php")) == "e9993743a7ced60c18dd2909fb96db7a") + $files_ok[] = "ok - ./demo/demo49_msn_smiley_theme.php +"; +else + $files_ko[] = "corrupted - ./demo/demo49_msn_smiley_theme.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo52_in_bangla.php")) == "76bd8a9fab4165154a53b9da91654669") + $files_ok[] = "ok - ./demo/demo52_in_bangla.php +"; +else + $files_ko[] = "corrupted - ./demo/demo52_in_bangla.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo38_in_norwegian_bokmal.php")) == "80af65c8fd034c491f1bd110976a6f4a") + $files_ok[] = "ok - ./demo/demo38_in_norwegian_bokmal.php +"; +else + $files_ko[] = "corrupted - ./demo/demo38_in_norwegian_bokmal.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo20_in_brazilian_portuguese.php")) == "754f8d9bed2c4f58523e038af320d760") + $files_ok[] = "ok - ./demo/demo20_in_brazilian_portuguese.php +"; +else + $files_ko[] = "corrupted - ./demo/demo20_in_brazilian_portuguese.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./demo/demo39_in_bosnian.php")) == "3fd69a43736598bb321f3867479a56b2") + $files_ok[] = "ok - ./demo/demo39_in_bosnian.php +"; +else + $files_ko[] = "corrupted - ./demo/demo39_in_bosnian.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfccontainerinterface.class.php")) == "af726eb66353397dabb1e4747fd1af5a") + $files_ok[] = "ok - ./src/pfccontainerinterface.class.php +"; +else + $files_ko[] = "corrupted - ./src/pfccontainerinterface.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfcresponse.class.php")) == "ef6bc29f44e2a04c79aec5d1f9a51298") + $files_ok[] = "ok - ./src/pfcresponse.class.php +"; +else + $files_ko[] = "corrupted - ./src/pfcresponse.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfcinfo.class.php")) == "62521d559e6e4c040f5bb260b3bc0f71") + $files_ok[] = "ok - ./src/pfcinfo.class.php +"; +else + $files_ko[] = "corrupted - ./src/pfcinfo.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/proxies/noflood.class.php")) == "ee707277b11b2c6d9309b6443a5df242") + $files_ok[] = "ok - ./src/proxies/noflood.class.php +"; +else + $files_ko[] = "corrupted - ./src/proxies/noflood.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/proxies/checknickchange.class.php")) == "35ebe77dd80bcc9e249069da024c405b") + $files_ok[] = "ok - ./src/proxies/checknickchange.class.php +"; +else + $files_ko[] = "corrupted - ./src/proxies/checknickchange.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/proxies/censor.class.php")) == "4a2eabddc47cdafec3495212539cda41") + $files_ok[] = "ok - ./src/proxies/censor.class.php +"; +else + $files_ko[] = "corrupted - ./src/proxies/censor.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/proxies/log.class.php")) == "e7480faf2c5f1f680908966a3daaa416") + $files_ok[] = "ok - ./src/proxies/log.class.php +"; +else + $files_ko[] = "corrupted - ./src/proxies/log.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/proxies/lock.class.php")) == "cabf8e57346f217a55793acb4834c699") + $files_ok[] = "ok - ./src/proxies/lock.class.php +"; +else + $files_ko[] = "corrupted - ./src/proxies/lock.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/proxies/checktimeout.class.php")) == "1b0c6272e963808351ff001d99968848") + $files_ok[] = "ok - ./src/proxies/checktimeout.class.php +"; +else + $files_ko[] = "corrupted - ./src/proxies/checktimeout.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/proxies/auth.class.php")) == "98bb07eb64c80cc9333c90d923648b91") + $files_ok[] = "ok - ./src/proxies/auth.class.php +"; +else + $files_ko[] = "corrupted - ./src/proxies/auth.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfci18n.class.php")) == "d35c60628f451c9bb370da8b573241f1") + $files_ok[] = "ok - ./src/pfci18n.class.php +"; +else + $files_ko[] = "corrupted - ./src/pfci18n.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfcurlprocessing.php")) == "c472ae27b7512d153d348b95dd293854") + $files_ok[] = "ok - ./src/pfcurlprocessing.php +"; +else + $files_ko[] = "corrupted - ./src/pfcurlprocessing.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfctools.php")) == "e28d289f68ab07ed0461352d4a0522dd") + $files_ok[] = "ok - ./src/pfctools.php +"; +else + $files_ko[] = "corrupted - ./src/pfctools.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfcjson.class.php")) == "77ad36b2c83155a3aa70732deb012e28") + $files_ok[] = "ok - ./src/pfcjson.class.php +"; +else + $files_ko[] = "corrupted - ./src/pfcjson.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfccommand.class.php")) == "97c756e835a03d6dffffd9cb8f000871") + $files_ok[] = "ok - ./src/pfccommand.class.php +"; +else + $files_ko[] = "corrupted - ./src/pfccommand.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/whois.class.php")) == "065119e27815a9a95d8f835b137d8183") + $files_ok[] = "ok - ./src/commands/whois.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/whois.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/kick.class.php")) == "e045d0bcef8ae9c54a5c80c9397b3fd2") + $files_ok[] = "ok - ./src/commands/kick.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/kick.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/invite.class.php")) == "2f0da284de519cdf5495eb1da85e06c4") + $files_ok[] = "ok - ./src/commands/invite.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/invite.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/redirect.class.php")) == "856d3f878923e4e8d0fba11c51fdba32") + $files_ok[] = "ok - ./src/commands/redirect.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/redirect.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/debug.class.php")) == "7b0127fa7c7b0c460067b3d64eb7ed93") + $files_ok[] = "ok - ./src/commands/debug.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/debug.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/quit.class.php")) == "570bd237eb45cda22731c58fb32f5d63") + $files_ok[] = "ok - ./src/commands/quit.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/quit.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/connect.class.php")) == "09aa6c00c547d7bec7920afffe33e8ac") + $files_ok[] = "ok - ./src/commands/connect.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/connect.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/unban.class.php")) == "2f26811a2439916e80b428284bdd48a0") + $files_ok[] = "ok - ./src/commands/unban.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/unban.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/send.class.php")) == "39a3aa897c5dfad28831d948c09b420e") + $files_ok[] = "ok - ./src/commands/send.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/send.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/who.class.php")) == "324951677b8b3a67f04c8978706faa59") + $files_ok[] = "ok - ./src/commands/who.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/who.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/ban.class.php")) == "e810df59bfc7763e96732f3cbcd70aa9") + $files_ok[] = "ok - ./src/commands/ban.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/ban.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/asknick.class.php")) == "8222c743d1eae6912a113657ed8efce7") + $files_ok[] = "ok - ./src/commands/asknick.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/asknick.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/version.class.php")) == "0c5e4832ee58d525e6703582be077d28") + $files_ok[] = "ok - ./src/commands/version.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/version.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/whois2.class.php")) == "3d4dd01c3589bd5ac95ee76648768fc5") + $files_ok[] = "ok - ./src/commands/whois2.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/whois2.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/me.class.php")) == "f51928e1b3da796cca81ab42bd580077") + $files_ok[] = "ok - ./src/commands/me.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/me.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/init.class.php")) == "f7e037f6a117b8b7442da646fba03675") + $files_ok[] = "ok - ./src/commands/init.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/init.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/nick.class.php")) == "3ef83588e816d6446e1bde6aad780e21") + $files_ok[] = "ok - ./src/commands/nick.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/nick.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/banlist.class.php")) == "72c130c27d181992188fbf31b2292860") + $files_ok[] = "ok - ./src/commands/banlist.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/banlist.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/update.class.php")) == "d4918a5519a286f98346d9672070587b") + $files_ok[] = "ok - ./src/commands/update.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/update.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/join.class.php")) == "e42afc6bbd6731c06fcbd7a2b9e48817") + $files_ok[] = "ok - ./src/commands/join.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/join.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/clear.class.php")) == "1f3c17be656d47132992cdf49e9fbd8b") + $files_ok[] = "ok - ./src/commands/clear.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/clear.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/notice.class.php")) == "c4639b70411c8047950d4dad0c011373") + $files_ok[] = "ok - ./src/commands/notice.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/notice.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/error.class.php")) == "c47f0cd75c857107c52918e199e0790c") + $files_ok[] = "ok - ./src/commands/error.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/error.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/leave.class.php")) == "2365e61980e6a5d28e3d5b522132bea0") + $files_ok[] = "ok - ./src/commands/leave.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/leave.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/who2.class.php")) == "519bb3fd46481198fd9193edecbc8467") + $files_ok[] = "ok - ./src/commands/who2.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/who2.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/privmsg.class.php")) == "dbc4c676a1bc6d950d2d2e8bf6ce8f72") + $files_ok[] = "ok - ./src/commands/privmsg.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/privmsg.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/privmsg2.class.php")) == "0a75dd01c65628768f3d287c2b31eb17") + $files_ok[] = "ok - ./src/commands/privmsg2.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/privmsg2.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/deop.class.php")) == "47aae56ed3e6040d84382398a41e4c7c") + $files_ok[] = "ok - ./src/commands/deop.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/deop.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/help.class.php")) == "c0c49466e980292e8b16beaea299ac57") + $files_ok[] = "ok - ./src/commands/help.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/help.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/identify.class.php")) == "8ece06787a64b179cfe9aca5d1302772") + $files_ok[] = "ok - ./src/commands/identify.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/identify.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/getnewmsg.class.php")) == "8852416adef7f4c2329aaaa12a5c8fb8") + $files_ok[] = "ok - ./src/commands/getnewmsg.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/getnewmsg.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/op.class.php")) == "bcc1edc239ef361242f2821c47bbcae2") + $files_ok[] = "ok - ./src/commands/op.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/op.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/updatemynick.class.php")) == "3124d9378be8cc85764bfc30848fba29") + $files_ok[] = "ok - ./src/commands/updatemynick.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/updatemynick.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/join2.class.php")) == "971365bb2c2146c5be0c777f61ac97e2") + $files_ok[] = "ok - ./src/commands/join2.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/join2.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/commands/rehash.class.php")) == "c30ece2addfd30d2e0c6937bcf090cf5") + $files_ok[] = "ok - ./src/commands/rehash.class.php +"; +else + $files_ko[] = "corrupted - ./src/commands/rehash.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfccontainer.class.php")) == "939f7eea39d6f5b5cce1163b6ba7f2bc") + $files_ok[] = "ok - ./src/pfccontainer.class.php +"; +else + $files_ko[] = "corrupted - ./src/pfccontainer.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfcproxycommand.class.php")) == "495f179ea06acd0f193fde0ed507c096") + $files_ok[] = "ok - ./src/pfcproxycommand.class.php +"; +else + $files_ko[] = "corrupted - ./src/pfcproxycommand.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/phpfreechat.class.php")) == "9f69f474e2000fedcd6fc4fe09bd523e") + $files_ok[] = "ok - ./src/phpfreechat.class.php +"; +else + $files_ko[] = "corrupted - ./src/phpfreechat.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfctemplate.class.php")) == "403fd8bcd8ee81754a418a4186a464b5") + $files_ok[] = "ok - ./src/pfctemplate.class.php +"; +else + $files_ko[] = "corrupted - ./src/pfctemplate.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfcuserconfig.class.php")) == "ecc11a59dd7b7ad04a4367ab77f8531b") + $files_ok[] = "ok - ./src/pfcuserconfig.class.php +"; +else + $files_ko[] = "corrupted - ./src/pfcuserconfig.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/containers/file.class.php")) == "cc65a3996b53ba785e30000e6c806b68") + $files_ok[] = "ok - ./src/containers/file.class.php +"; +else + $files_ko[] = "corrupted - ./src/containers/file.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/containers/oracle.class.php")) == "808989868dbfc511ccdbd500b3f8a4b2") + $files_ok[] = "ok - ./src/containers/oracle.class.php +"; +else + $files_ko[] = "corrupted - ./src/containers/oracle.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/containers/mysql.class.php")) == "34c3f6fe46fddca98b8933876d77ee03") + $files_ok[] = "ok - ./src/containers/mysql.class.php +"; +else + $files_ko[] = "corrupted - ./src/containers/mysql.class.php (please replace this file by a correct one) +"; +if (md5(file_get_contents("./src/pfcglobalconfig.class.php")) == "319ddd0e1fa20650d5a60266458f0a7d") + $files_ok[] = "ok - ./src/pfcglobalconfig.class.php +"; +else + $files_ko[] = "corrupted - ./src/pfcglobalconfig.class.php (please replace this file by a correct one) +"; +echo "

Checking phpfreechat files validity

"; +echo "
+";
+$arr = array_merge($files_ko,$files_ok);
+foreach($arr as $file)
+  echo $file;
+echo "
+"; +?> diff --git a/instmess/data/private/.htaccess b/instmess/data/private/.htaccess new file mode 100644 index 0000000..a53e0d3 --- /dev/null +++ b/instmess/data/private/.htaccess @@ -0,0 +1,3 @@ +order deny,allow +deny from all +allow from localhost \ No newline at end of file diff --git a/instmess/data/public/js/activity.js b/instmess/data/public/js/activity.js new file mode 100644 index 0000000..973b8dc --- /dev/null +++ b/instmess/data/public/js/activity.js @@ -0,0 +1,67 @@ +var DetectActivity = Class.create(); +DetectActivity.prototype = { + initialize: function(subject) + { + this.onunactivate = function() {}; + this.onactivate = function() {}; + this.subject = subject; + this.isactive = true; + Event.observe(subject, 'mousemove', this._OnFocus.bindAsEventListener(this), false); + Event.observe(subject, 'mouseout', this._OnBlur.bindAsEventListener(this), false); + }, + _OnFocus: function(e) + { + this.isactive = true; + if (this.onactivate) this.onactivate(); + }, + _OnBlur: function(e) + { + this.isactive = false; + if (this.onunactivate) this.onunactivate(); + }, + isActive: function() + { + return this.isactive; + } +} + + + +/* +// Unused code, by usefull for further auto idle features + + _launchTimeout: function(myself) + { +var oldisactive = this.isactive; + if (this.oldposx == this.posx && + this.oldposy == this.posy) + this.isactive = false; + else + this.isactive = true; +this.oldposx = this.posx; +this.oldposy = this.posy; +if (oldisactive != this.isactive) alert("switch"); + setTimeout(function() { myself._launchTimeout(myself); }, 1000); + }, + + _OnMouseMove: function(e) + { + var posx = 0; + var posy = 0; + if (!e) var e = window.event; + if (e.pageX || e.pageY) + { + posx = e.pageX; + posy = e.pageY; + } + else if (e.clientX || e.clientY) + { + posx = e.clientX + document.body.scrollLeft + + document.documentElement.scrollLeft; + posy = e.clientY + document.body.scrollTop + + document.documentElement.scrollTop; + } + this.posx = posx; + this.posy = posy; + }, +*/ diff --git a/instmess/data/public/js/compat.js b/instmess/data/public/js/compat.js new file mode 100644 index 0000000..77f4940 --- /dev/null +++ b/instmess/data/public/js/compat.js @@ -0,0 +1,95 @@ + // Cookie handling + var Cookie = + { + read: function (name) + { + // Work around for Firefox when 'HTTP only' cookies are in use + if (typeof(document.cookie) != "string" && navigator.product == "Gecko") delete HTMLDocument.prototype.cookie; + + var arrCookies = document.cookie.split ('; '); + for (var i=0; i 0) workingDate.setTime(workingDate.getTime() - skew); + return workingDate; +} + + +// Test for cookie support +function supportsCookies(rootPath) { + setCookie('checking_for_cookie_support', 'testing123', '', (rootPath != null ? rootPath : '')); + if (getCookie('checking_for_cookie_support')) return true; + else return false; +} \ No newline at end of file diff --git a/instmess/data/public/js/createstylerule.js b/instmess/data/public/js/createstylerule.js new file mode 100644 index 0000000..199659e --- /dev/null +++ b/instmess/data/public/js/createstylerule.js @@ -0,0 +1,39 @@ +// from http://www.bobbyvandersluis.com/articles/dynamicCSS.php +var pfcCSS = Class.create(); +pfcCSS.prototype = { + initialize: function() + { + if (!document.getElementsByTagName || + !(document.createElement || document.createElementNS)) return; + var agt = navigator.userAgent.toLowerCase(); + this.is_ie = ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)); + this.is_iewin = (is_ie && (agt.indexOf("win") != -1)); + this.is_iemac = (is_ie && (agt.indexOf("mac") != -1)); + if (this.is_iemac) return; // script doesn't work properly in IE/Mac + + var head = document.getElementsByTagName("head")[0]; + this.style = (typeof document.createElementNS != "undefined") ? + document.createElementNS("http://www.w3.org/1999/xhtml", "style") : + document.createElement("style"); + this.style.setAttribute("type", "text/css"); + this.style.setAttribute("media", "screen"); + head.appendChild(this.style); + + this.lastStyle = document.styleSheets[document.styleSheets.length - 1]; + }, + + applyRule: function(selector, declaration) + { + selector = selector.split(','); + for ( var i = 0; i < selector.length; i++) + { + if (!this.is_iewin) { + var styleRule = document.createTextNode(selector[i] + " {" + declaration + "}"); + this.style.appendChild(styleRule); // bugs in IE/Win + } + if (this.is_iewin && document.styleSheets && document.styleSheets.length > 0) { + this.lastStyle.addRule(selector[i], declaration); + } + } + } +} \ No newline at end of file diff --git a/instmess/data/public/js/image_preloader.js b/instmess/data/public/js/image_preloader.js new file mode 100644 index 0000000..1568f53 --- /dev/null +++ b/instmess/data/public/js/image_preloader.js @@ -0,0 +1,13 @@ +// Image Preloader v1.0.1 +// documentation: http://www.dithered.com/javascript/image_preloader/index.html +// license: http://creativecommons.org/licenses/by/1.0/ +// code by Chris Nott (chris[at]dithered[dot]com) + + +function preloadImages() { + if (document.images) { + for (var i = 0; i < preloadImages.arguments.length; i++) { + (new Image()).src = preloadImages.arguments[i]; + } + } +} \ No newline at end of file diff --git a/instmess/data/public/js/md5.js b/instmess/data/public/js/md5.js new file mode 100644 index 0000000..c59d559 --- /dev/null +++ b/instmess/data/public/js/md5.js @@ -0,0 +1,327 @@ +/** +* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message +* Digest Algorithm, as defined in RFC 1321. +* +* Extends string prototype with the following method: +* md5 +* +* This extensions doesn't depend on any other code or overwrite existing methods. +* +* +* The Initial Developer of the Original Code is +* Paul Johnston +* Version 2.1 Copyright (C) Paul Johnston 1999 - 2002. +* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet +* +* Distributed under the BSD License +* See http://pajhome.org.uk/crypt/md5 for more info. +* +* +* Contributor(s): +* Harald Hanek +* +* Copyright (c) 2007 Harald Hanek (http://js-methods.googlecode.com) +* +* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) +* and GPL (http://www.gnu.org/licenses/gpl.html) licenses. +* +* @author Harald Hanek +* @version 0.9 +* @lastchangeddate 10. October 2007 18:01:32 +* @revision 876 +*/ + +(function(){ + + var md5 = + { + hexcase : 0, /* hex output format. 0 - lowercase; 1 - uppercase */ + b64pad : "", /* base-64 pad character. "=" for strict RFC compliance */ + chrsz : 8, /* bits per input character. 8 - ASCII; 16 - Unicode */ + + /** + * These are the functions you'll usually want to call + * They take string arguments and return either hex or base-64 encoded strings + */ + 'hex_md5' : function(s) + { + return this.binl2hex(this.core_md5(this.str2binl(s), s.length * this.chrsz)); + }, + + 'b64_md5' : function(s) + { + return this.binl2b64(this.core_md5(this.str2binl(s), s.length * this.chrsz)); + }, + + 'str_md5' : function(s) + { + return this.binl2str(this.core_md5(this.str2binl(s), s.length * this.chrsz)); + }, + + 'hex_hmac_md5' : function(key, data) + { + return this.binl2hex(this.core_hmac_md5(key, data)); + }, + + 'b64_hmac_md5' : function(key, data) + { + return this.binl2b64(this.core_hmac_md5(key, data)); + }, + + 'str_hmac_md5' : function(key, data) + { + return this.binl2str(this.core_hmac_md5(key, data)); + }, + + /** + * Calculate the MD5 of an array of little-endian words, and a bit length. + * + */ + 'core_md5' : function(x, len) + { + x[len >> 5] |= 0x80 << ((len) % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; + + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + + for(var i = 0; i < x.length; i += 16) + { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + + a = this.md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); + d = this.md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); + c = this.md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); + b = this.md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); + a = this.md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); + d = this.md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); + c = this.md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); + b = this.md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); + a = this.md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); + d = this.md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); + c = this.md5_ff(c, d, a, b, x[i+10], 17, -42063); + b = this.md5_ff(b, c, d, a, x[i+11], 22, -1990404162); + a = this.md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); + d = this.md5_ff(d, a, b, c, x[i+13], 12, -40341101); + c = this.md5_ff(c, d, a, b, x[i+14], 17, -1502002290); + b = this.md5_ff(b, c, d, a, x[i+15], 22, 1236535329); + + a = this.md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); + d = this.md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); + c = this.md5_gg(c, d, a, b, x[i+11], 14, 643717713); + b = this.md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); + a = this.md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); + d = this.md5_gg(d, a, b, c, x[i+10], 9 , 38016083); + c = this.md5_gg(c, d, a, b, x[i+15], 14, -660478335); + b = this.md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); + a = this.md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); + d = this.md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); + c = this.md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); + b = this.md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); + a = this.md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); + d = this.md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); + c = this.md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); + b = this.md5_gg(b, c, d, a, x[i+12], 20, -1926607734); + + a = this.md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); + d = this.md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); + c = this.md5_hh(c, d, a, b, x[i+11], 16, 1839030562); + b = this.md5_hh(b, c, d, a, x[i+14], 23, -35309556); + a = this.md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); + d = this.md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); + c = this.md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); + b = this.md5_hh(b, c, d, a, x[i+10], 23, -1094730640); + a = this.md5_hh(a, b, c, d, x[i+13], 4 , 681279174); + d = this.md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); + c = this.md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); + b = this.md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); + a = this.md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); + d = this.md5_hh(d, a, b, c, x[i+12], 11, -421815835); + c = this.md5_hh(c, d, a, b, x[i+15], 16, 530742520); + b = this.md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); + + a = this.md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); + d = this.md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); + c = this.md5_ii(c, d, a, b, x[i+14], 15, -1416354905); + b = this.md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); + a = this.md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); + d = this.md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); + c = this.md5_ii(c, d, a, b, x[i+10], 15, -1051523); + b = this.md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); + a = this.md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); + d = this.md5_ii(d, a, b, c, x[i+15], 10, -30611744); + c = this.md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); + b = this.md5_ii(b, c, d, a, x[i+13], 21, 1309151649); + a = this.md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); + d = this.md5_ii(d, a, b, c, x[i+11], 10, -1120210379); + c = this.md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); + b = this.md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); + + a = this.safe_add(a, olda); + b = this.safe_add(b, oldb); + c = this.safe_add(c, oldc); + d = this.safe_add(d, oldd); + } + return Array(a, b, c, d); + }, + + /** + * These functions implement the four basic operations the algorithm uses. + * + */ + 'md5_cmn' : function(q, a, b, x, s, t) + { + return this.safe_add(this.bit_rol(this.safe_add(this.safe_add(a, q), this.safe_add(x, t)), s),b); + }, + + 'md5_ff' : function(a, b, c, d, x, s, t) + { + return this.md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); + }, + + 'md5_gg' : function(a, b, c, d, x, s, t) + { + return this.md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); + }, + + 'md5_hh' : function(a, b, c, d, x, s, t) + { + return this.md5_cmn(b ^ c ^ d, a, b, x, s, t); + }, + + 'md5_ii' : function(a, b, c, d, x, s, t) + { + return this.md5_cmn(c ^ (b | (~d)), a, b, x, s, t); + }, + + /** + * Calculate the HMAC-MD5, of a key and some data. + * + */ + 'core_hmac_md5' : function(key, data) + { + var bkey = this.str2binl(key); + if(bkey.length > 16) + bkey = this.core_md5(bkey, key.length * this.chrsz); + + var ipad = Array(16), opad = Array(16); + for(var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = this.core_md5(ipad.concat(this.str2binl(data)), 512 + data.length * this.chrsz); + return this.core_md5(opad.concat(hash), 512 + 128); + }, + + + /** + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + * + */ + 'safe_add' : function(x, y) + { + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }, + + /** + * Bitwise rotate a 32-bit number to the left. + * + */ + 'bit_rol' : function(num, cnt) + { + return (num << cnt) | (num >>> (32 - cnt)); + }, + + /** + * Convert a string to an array of little-endian words. + * If this.chrsz is ASCII, characters >255 have their hi-byte silently ignored. + * + */ + 'str2binl' : function(str) + { + var bin = Array(); + var mask = (1 << this.chrsz) - 1; + for(var i = 0; i < str.length * this.chrsz; i += this.chrsz) + bin[i>>5] |= (str.charCodeAt(i / this.chrsz) & mask) << (i%32); + return bin; + }, + + /** + * Convert an array of little-endian words to a string + * + */ + 'binl2str' : function(bin) + { + var str = ""; + var mask = (1 << this.chrsz) - 1; + for(var i = 0; i < bin.length * 32; i += this.chrsz) + str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask); + return str; + }, + + /** + * Convert an array of little-endian words to a hex string. + * + */ + 'binl2hex' : function(binarray) + { + var hex_tab = this.hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i++) + { + str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF); + } + return str; + }, + + /** + * Convert an array of little-endian words to a base-64 string + * + */ + 'binl2b64' : function(binarray) + { + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i += 3) + { + var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16) + | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 ) + | ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF); + for(var j = 0; j < 4; j++) + { + if(i * 8 + j * 6 > binarray.length * 32) + str += this.b64pad; + else + str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); + } + } + return str; + } + }; + + /** + * Returns the md5 hash of the given string. + * + * @example "JavaScript".md5(); + * @result "686155af75a60a0f6e9d80c1f7edd3e9" + * + * @name md5 + * @return String + */ + if(!String.prototype.md5) + String.prototype.md5 = function() + { + return md5.hex_md5(this); + } +})(); \ No newline at end of file diff --git a/instmess/data/public/js/mousepos.js b/instmess/data/public/js/mousepos.js new file mode 100644 index 0000000..cced142 --- /dev/null +++ b/instmess/data/public/js/mousepos.js @@ -0,0 +1,25 @@ +function mousePosX(e) { + var posx = 0; + if (!e) var e = window.event; + if (e.pageX) { + posx = e.pageX; + } + else if (e.clientX) { + posx = e.clientX + document.body.scrollLeft + + document.documentElement.scrollLeft; + } + return posx; +} + +function mousePosY(e) { + var posy = 0; + if (!e) var e = window.event; + if (e.pageY) { + posy = e.pageY; + } + else if (e.clientY) { + posy = e.clientY + document.body.scrollTop + + document.documentElement.scrollTop; + } + return posy; +} diff --git a/instmess/data/public/js/myprototype.js b/instmess/data/public/js/myprototype.js new file mode 100644 index 0000000..4d2c12c --- /dev/null +++ b/instmess/data/public/js/myprototype.js @@ -0,0 +1,17 @@ + + +function indexOf(array, object) +{ + for (var i = 0; i < array.length; i++) + if (array[i] == object) return i; + return -1; +} + +function without(array,value) { + var res = Array(); + for( var i = 0 ; i < array.length; i++) + { + if (array[i] != value) res.push(array[i]); + } + return res; +} diff --git a/instmess/data/public/js/pfcclient.js b/instmess/data/public/js/pfcclient.js new file mode 100644 index 0000000..7c0f4da --- /dev/null +++ b/instmess/data/public/js/pfcclient.js @@ -0,0 +1,2160 @@ +// Browser detection mostly taken from prototype.js 1.5.1.1. +var is_ie = !!(window.attachEvent && !window.opera); +var is_khtml = !!(navigator.appName.match("Konqueror") || navigator.appVersion.match("KHTML")); +var is_gecko = navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1; +var is_ie7 = navigator.userAgent.indexOf('MSIE 7') > 0; +var is_ie6 = navigator.userAgent.indexOf('MSIE 6') > 0; +var is_opera = !!window.opera; +var is_webkit = navigator.userAgent.indexOf('AppleWebKit/') > -1; + +/** + * This class is the client part of phpFreeChat + * (depends on prototype library) + * @author Stephane Gully + */ +var pfcClient = Class.create(); + +//defining the rest of the class implmentation +pfcClient.prototype = { + + initialize: function() + { + // load the graphical user interface builder + this.gui = new pfcGui(); + // load the resources manager (labels and urls) + this.res = new pfcResource(); + + this.nickname = pfc_nickname; + this.nickid = pfc_nickid; + this.usermeta = $H(); + this.chanmeta = $H(); + this.nickwhoisbox = $H(); + + // this array contains all the sent commands + // use the up and down arrow key to navigate through the history + this.cmdhistory = Array(); + this.cmdhistoryid = -1; + this.cmdhistoryissearching = false; + + /* + this.channels = Array(); + this.channelids = Array(); + */ + this.privmsgs = Array(); + this.privmsgids = Array(); + + this.timeout = null; + this.timeout_time = new Date().getTime(); + + this.refresh_delay = pfc_refresh_delay; + this.refresh_delay_steps = pfc_refresh_delay_steps; + this.last_response_time = new Date().getTime(); + this.last_request_time = new Date().getTime(); + this.last_activity_time = new Date().getTime(); + + /* unique client id for each windows used to identify a open window + * this id is passed every time the JS communicate with server + * (2 clients can use the same session: then only the nickname is shared) */ + this.clientid = pfc_clientid; + + this.isconnected = false; + this.nicklist = $H(); + this.nickcolor = Array(); + this.colorlist = Array(); + + this.blinktmp = Array(); + this.blinkloop = Array(); + this.blinktimeout = Array(); + }, + + loadChat: function() { + new Ajax.Request(pfc_server_script_url, { + method: 'get', + parameters: {pfc_ajax: 1, f: 'loadChat'}, + onSuccess: function(transport) { + eval( transport.responseText ); + } + }); + }, + + connectListener: function() + { + this.el_words = $('pfc_words'); + this.el_handle = $('pfc_handle'); + this.el_container = $('pfc_container'); +// this.el_online = $('pfc_online'); + this.el_errors = $('pfc_errors'); + + this.detectactivity = new DetectActivity(this.el_container); + // restore the window title when user come back to the active zone + if (pfc_notify_window) this.detectactivity.onunactivate = this.gui.unnotifyWindow.bindAsEventListener(this.gui); + + /* the events callbacks */ + this.el_words.onkeypress = this.callbackWords_OnKeypress.bindAsEventListener(this); +// don't use this line because when doing completeNick the "return false" doesn't work (focus is lost) +// Event.observe(this.el_words, 'keypress', this.callbackWords_OnKeypress.bindAsEventListener(this), false); + Event.observe(this.el_words, 'keydown', this.callbackWords_OnKeydown.bindAsEventListener(this), false); + Event.observe(this.el_words, 'keyup', this.callbackWords_OnKeyup.bindAsEventListener(this), false); + Event.observe(this.el_words, 'mouseup', this.callbackWords_OnMouseup.bindAsEventListener(this), false); + Event.observe(this.el_words, 'focus', this.callbackWords_OnFocus.bindAsEventListener(this), false); + Event.observe(document.body, 'unload', this.callback_OnUnload.bindAsEventListener(this), false); + }, + + refreshGUI: function() + { + this.minmax_status = pfc_start_minimized; + var cookie = getCookie('pfc_minmax_status'); + if (cookie != null) + this.minmax_status = (cookie == 'true'); + + cookie = getCookie('pfc_nickmarker'); + this.nickmarker = (cookie == 'true'); + if (cookie == '' || cookie == null) + this.nickmarker = pfc_nickmarker; + + cookie = getCookie('pfc_clock'); + this.clock = (cookie == 'true'); + if (cookie == '' || cookie == null) + this.clock = pfc_clock; + + cookie = getCookie('pfc_showsmileys'); + this.showsmileys = (cookie == 'true'); + if (cookie == '' || cookie == null) + this.showsmileys = pfc_showsmileys; + + cookie = getCookie('pfc_showwhosonline'); + this.showwhosonline = (cookie == 'true'); + if (cookie == '' || cookie == null) + this.showwhosonline = pfc_showwhosonline; + + // '' means no forced color, let CSS choose the text color + this.current_text_color = ''; + cookie = getCookie('pfc_current_text_color'); + if (cookie != null) + this.switch_text_color(cookie); + + cookie = getCookie('pfc_issoundenable'); + this.issoundenable = (cookie == 'true'); + if (cookie == '' || cookie == null) + this.issoundenable = pfc_startwithsound; + + this.refresh_loginlogout(); + this.refresh_minimize_maximize(); + this.refresh_Smileys(); + this.refresh_sound(); + this.refresh_nickmarker(); + }, + + /** + * Show a popup dialog to ask user to choose a nickname + */ + askNick: function(nickname,error_text) + { + // ask to choose a nickname + if (nickname == '' || nickname == undefined) nickname = this.nickname; + + // build a dhtml prompt box + var pfcp = this.getPrompt();//new pfcPrompt($('pfc_container')); + pfcp.callback = function(v) { pfc.askNickResponse(v); } + pfcp.prompt((error_text != undefined ? ''+error_text+'
' : '')+this.res.getLabel('Please enter your nickname'), nickname); + pfcp.focus(); + }, + askNickResponse: function(newnick) + { + if (newnick) + { + if (this.isconnected) + this.sendRequest('/nick "'+newnick+'"'); + else + this.sendRequest('/connect "'+newnick+'"'); + } + }, + + /** + * Reacte to the server response + */ + handleResponse: function(cmd, resp, param) + { + // display some debug messages + if (pfc_debug) + if (cmd != "update") + { + var param2 = param; + if (cmd == "who" || cmd == "who2") + { + param2 = $H(param2); + param2.set('meta', $H(param2.get('meta'))); + param2.get('meta').set('users', $H(param2.get('meta').get('users'))); + trace('handleResponse: '+cmd + "-"+resp+"-"+param2.inspect()); + } + else + if (cmd == "whois" || cmd == "whois2") + { + param2 = $H(param2); + trace('handleResponse: '+cmd + "-"+resp+"-"+param2.inspect()); + } + else + if (cmd == "getnewmsg" || cmd == "join") + { + param2 = $A(param2); + trace('handleResponse: '+cmd + "-"+resp+"-"+param2.inspect()); + } + else + trace('handleResponse: '+cmd + "-"+resp+"-"+param); + } + + if (cmd != "update") + { + // speed up timeout if activity + this.last_activity_time = new Date().getTime(); + var delay = this.calcDelay(); + if (this.timeout_time - new Date().getTime() > delay) + { + clearTimeout(this.timeout); + this.timeout = setTimeout('pfc.updateChat(true)', delay); + this.timeout_time = new Date().getTime() + delay; + } + } + + if (cmd == "connect") + { + if (resp == "ok") + { + this.nickname = param[0]; + this.isconnected = true; + + // start the polling system + this.updateChat(true); + } + else + this.isconnected = false; + this.refresh_loginlogout(); + } + else if (cmd == "quit") + { + if (resp =="ok") + { + // stop updates + this.updateChat(false); + this.isconnected = false; + this.refresh_loginlogout(); + } + } + else if (cmd == "join" || cmd == "join2") + { + if (resp =="ok") + { + // create the new channel + var tabid = param[0]; + var name = param[1]; + this.gui.createTab(name, tabid, "ch"); + if (cmd != "join2" || this.gui.tabs.length == 1) this.gui.setTabById(tabid); + this.refresh_Smileys(); + this.refresh_WhosOnline(); + } + else if (resp == "max_channels") + { + this.displayMsg( cmd, this.res.getLabel('Maximum number of joined channels has been reached') ); + } + else + alert(cmd + "-"+resp+"-"+param); + } + else if (cmd == "leave") + { + if (resp =="ok") + { + // remove the channel + var tabid = param; + this.gui.removeTabById(tabid); + + // synchronize the channel client arrays + /* + var index = -1; + index = this.channelids.indexOf(tabid); + this.channelids = this.channelids.without(tabid); + this.channels = this.channels.without(this.channels[index]); + */ + + // synchronize the privmsg client arrays + index = -1; + index = indexOf(this.privmsgids, tabid); + this.privmsgids = without(this.privmsgids, tabid); + this.privmsgs = without(this.privmsgs, this.privmsgs[index]); + + } + } + else if (cmd == "privmsg" || cmd == "privmsg2") + { + if (resp == "ok") + { + // create the new channel + var tabid = param[0]; + var name = param[1]; + this.gui.createTab(name, tabid, "pv"); + if (cmd != "privmsg2" || this.gui.tabs.length == 1) this.gui.setTabById(tabid); + + this.privmsgs.push(name); + this.privmsgids.push(tabid); + + } + else if (resp == "max_privmsg") + { + this.displayMsg( cmd, this.res.getLabel('Maximum number of private chat has been reached') ); + } + else if (resp == "unknown") + { + // speak to unknown user + this.displayMsg( cmd, this.res.getLabel('You are trying to speak to a unknown (or not connected) user') ); + } + else if (resp == "speak_to_myself") + { + this.displayMsg( cmd, this.res.getLabel('You are not allowed to speak to yourself') ); + } + else + alert(cmd + "-"+resp+"-"+param); + } + else if (cmd == "nick") + { + // give focus the the input text box if wanted + if (pfc_focus_on_connect) this.el_words.focus(); + + if (resp == "connected" || resp == "notchanged") + { + cmd = ''; + } + + if (resp == "ok" || resp == "notchanged" || resp == "changed" || resp == "connected") + { + this.setUserMeta(this.nickid, 'nick', param); + this.el_handle.innerHTML = this.getUserMeta(this.nickid, 'nick').escapeHTML(); + this.nickname = this.getUserMeta(this.nickid, 'nick'); + this.updateNickBox(this.nickid); + + // clear the possible error box generated by the bellow displayMsg(...) function + this.clearError(Array(this.el_words)); + } + else if (resp == "isused") + { + this.setError(this.res.getLabel('Chosen nickname is already used'), Array()); + this.askNick(param,this.res.getLabel('Chosen nickname is already used')); + } + else if (resp == "notallowed") + { + // When frozen_nick is true and the nickname is already used, server will return + // the 'notallowed' status. It will display a message and stop chat update. + // If the chat update is not stopped, this will loop forever + // as long as the forced nickname is not changed. + + // display a message + this.setError(this.res.getLabel('Chosen nickname is not allowed'), Array()); + // then stop chat updates + this.updateChat(false); + this.isconnected = false; + this.refresh_loginlogout(); + } + } + else if (cmd == "update") + { + } + else if (cmd == "version") + { + if (resp == "ok") + { + this.displayMsg( cmd, this.res.getLabel('phpfreechat current version is %s',param) ); + } + } + else if (cmd == "help") + { + if (resp == "ok") + { + this.displayMsg( cmd, param); + } + } + else if (cmd == "rehash") + { + if (resp == "ok") + { + this.displayMsg( cmd, this.res.getLabel('Configuration has been rehashed') ); + } + else if (resp == "ko") + { + this.displayMsg( cmd, this.res.getLabel('A problem occurs during rehash') ); + } + } + else if (cmd == "banlist") + { + if (resp == "ok" || resp == "ko") + { + this.displayMsg( cmd, param ); + } + } + else if (cmd == "unban") + { + if (resp == "ok" || resp == "ko") + { + this.displayMsg( cmd, param ); + } + } + else if (cmd == "auth") + { + if (resp == "ban") + { + alert(param); + } + if (resp == "frozen") + { + alert(param); + } + else if (resp == "nick") + { + this.displayMsg( cmd, param ); + } + } + else if (cmd == "debug") + { + if (resp == "ok" || resp == "ko") + { + this.displayMsg( cmd, param ); + } + } + else if (cmd == "clear") + { + var tabid = this.gui.getTabId(); + var container = this.gui.getChatContentFromTabId(tabid); + container.innerHTML = ""; + } + else if (cmd == "identify") + { + this.displayMsg( cmd, param ); + } + else if (cmd == "checknickchange") + { + this.displayMsg( cmd, param ); + } + else if (cmd == "whois" || cmd == "whois2") + { + param = $H(param); + var nickid = param.get('nickid'); + if (resp == "ok") + { + this.setUserMeta(nickid, param); + this.updateNickBox(nickid); + } + if (cmd == "whois") + { + // display the whois info + var um = this.getAllUserMeta(nickid); + var um_keys = um.keys(); + var msg = ''; + for (var i=0; i' + k + ': ' + v + '
'; + } + this.displayMsg( cmd, msg ); + } + } + else if (cmd == "who" || cmd == "who2") + { + param = $H(param); + var chan = param.get('chan'); + var chanid = param.get('chanid'); + var meta = $H(param.get('meta')); + meta.set('users', $H(meta.get('users'))); + if (resp == "ok") + { + this.setChanMeta(chanid,meta); + // send /whois commands for unknown users + for (var i=0; i' + k + ': ' + v + '
'; + } + } + this.displayMsg( cmd, msg ); + } + } + else if (cmd == "getnewmsg") + { + if (resp == "ok") + { + this.handleComingRequest(param); + } + } + else if (cmd == "send") + { + } + else + alert(cmd + "-"+resp+"-"+param); + }, + + getAllUserMeta: function(nickid) + { + if (nickid && this.usermeta.get(nickid)) + return this.usermeta.get(nickid); + else + return null; + }, + + getUserMeta: function(nickid, key) + { + if (nickid && key && this.usermeta.get(nickid) && this.usermeta.get(nickid).get(key)) + return this.usermeta.get(nickid).get(key); + else + return ''; + }, + + setUserMeta: function(nickid, key, value) + { + if (nickid && key) + { + if (!this.usermeta.get(nickid)) this.usermeta.set(nickid, $H()); + if (value) + this.usermeta.get(nickid).set(key, value); + else + this.usermeta.set(nickid, $H(key)); + } + }, + + getAllChanMeta: function(chanid) + { + if (chanid && this.chanmeta.get(chanid)) + return this.chanmeta.get(chanid); + else + return null; + }, + + getChanMeta: function(chanid, key) + { + if (chanid && key && this.chanmeta.get(chanid) && this.chanmeta.get(chanid).get(key)) + return this.chanmeta.get(chanid).get(key); + else + return ''; + }, + + setChanMeta: function(chanid, key, value) + { + if (chanid && key) + { + if (!this.chanmeta.get(chanid)) this.chanmeta.set(chanid, $H()); + if (value) + this.chanmeta.get(chanid).set(key,value); + else + this.chanmeta.set(chanid, $H(key)); + } + }, + + doSendMessage: function() + { + var w = this.el_words; + var wval = w.value; + + // Append the string to the history. + this.cmdhistory.push(wval); + this.cmdhistoryid = this.cmdhistory.length; + this.cmdhistoryissearching = false; + + // Send the string to the server. + re = new RegExp("^(\/[a-zA-Z0-9]+)( (.*)|)"); + if (wval.match(re)) + { + // A user command. + cmd = wval.replace(re, '$1'); + param = wval.replace(re, '$3'); + this.sendRequest(cmd +' '+ param.substr(0, pfc_max_text_len + 2*this.clientid.length)); + } + else + { + // A classic 'send' command. + + // Empty messages with only spaces. + rx = new RegExp('^[ ]*$','g'); + wval = wval.replace(rx,''); + + // Truncate the text length. + wval = wval.substr(0,pfc_max_text_len); + + // Colorize the text with current_text_color. + if (this.current_text_color != '' && wval.length != '') + wval = '[color=#' + this.current_text_color + '] ' + wval + ' [/color]'; + + this.sendRequest('/send '+ wval); + } + w.value = ''; + return false; + }, + + /** + * Try to complete a nickname like on IRC when pressing the TAB key. + * Nicks with spaces may not work under certain circumstances. + * Replacing spaces with alternate spaces (e.g.,  ) helps. + * Gecko browsers convert the   to regular spaces, so no help for these browsers. + * Note: IRC does not allow nicks with spaces, so it's much easier for those clients. :) + * @author Gerard Pinzone + */ + completeNick: function() + { + var w = this.el_words; + var selStart = w.value.length; // Default for browsers that don't support selection/caret position commands. + var selEnd = selStart; + + // Get selection/caret position. + if (w.setSelectionRange) + { + // We don't rely on the stored values for browsers that support + // the selectionStart and selectionEnd commands. + selStart = w.selectionStart; + selEnd = w.selectionEnd; + } + else if (w.createTextRange && document.selection) + { + // We must rely on the stored values for IE browsers. + selStart = (w.selStart != null) ? w.selStart : w.value.length; + selEnd = (w.selEnd != null) ? w.selEnd : w.value.length; + } + + var begin = w.value.lastIndexOf(' ', selStart - 1) + 1; + var end = (w.value.indexOf(' ', selStart) >= 0) ? w.value.indexOf(' ', selStart) : w.value.length; + var nick_src = w.value.substring(begin, end); + var non_nick_begin = w.value.substring(0, begin); + var non_nick_end = w.value.substring(end, w.value.length); + + if (nick_src != '') + { + var tabid = this.gui.getTabId(); + var n_list = this.getChanMeta(tabid, 'users')['nick']; + var nick_match = false; + for (var i = 0; i < n_list.length; i++) + { + var nick_tmp = n_list[i]; + // replace spaces in nicks with   + nick_tmp = nick_tmp.replace(/ /g, '\240'); + if (nick_tmp.indexOf(nick_src) == 0) + { + if (! nick_match) + { + nick_match = true; + nick_replace = nick_tmp; + } + else + { + // more than one possibility for completion + var nick_len = Math.min(nick_tmp.length, nick_replace.length); + // only keep characters that are common to all matches + var j = 0; + for (j = 0; j < nick_len; j++) + if (nick_tmp.charAt(j) != nick_replace.charAt(j)) + break; + + nick_replace = nick_replace.substr(0, j); + } + } + } + if (nick_match) + { + w.value = non_nick_begin + nick_replace + non_nick_end; + w.selStart = w.selEnd = non_nick_begin.length + nick_replace.length; + + // Move cursor to end of completed nick. + if (w.setSelectionRange) + w.setSelectionRange(w.selEnd, w.selEnd); // Gecko + else + this.setSelection(w); // IE + } + } + }, + + /** + * Cycle to older entry in history + */ + historyUp: function() + { + // Write the previous command in the history. + if (this.cmdhistory.length > 0) + { + var w = this.el_words; + if (this.cmdhistoryissearching == false && w.value != "") + this.cmdhistory.push(w.value); + this.cmdhistoryissearching = true; + this.cmdhistoryid = this.cmdhistoryid - 1; + if (this.cmdhistoryid < 0) + this.cmdhistoryid = 0; // stop at oldest entry + w.value = this.cmdhistory[this.cmdhistoryid]; + } + }, + + /** + * Cycle to newer entry in history + */ + historyDown: function() + { + // Write the next command in the history. + if (this.cmdhistory.length > 0) + { + var w = this.el_words; + if (this.cmdhistoryissearching == false && w.value != "") + this.cmdhistory.push(w.value); + this.cmdhistoryissearching = true; + this.cmdhistoryid = this.cmdhistoryid + 1; + if (this.cmdhistoryid >= this.cmdhistory.length) + { + this.cmdhistoryid = this.cmdhistory.length; // stop at newest entry + 1 + w.value = ""; // blank input box + } + else + w.value = this.cmdhistory[this.cmdhistoryid]; + } + }, + + /** + * Handle the pressed keys. + * see also callbackWords_OnKeydown + */ + callbackWords_OnKeypress: function(evt) + { + // All browsers except for IE should use "evt.which." + var code = (evt.which) ? evt.which : evt.keyCode; + if (code == Event.KEY_RETURN) /* ENTER key */ + { + return this.doSendMessage(); + } + else + { + // Allow other key defaults. + return true; + } + }, + + /** + * Handle the pressed keys. + * see also callbackWords_OnKeypress + * WARNING: Suppressing defaults on the keydown event + * may prevent keypress and/or keyup events + * from firing. + */ + callbackWords_OnKeydown: function(evt) + { + if (!this.isconnected) return false; + this.clearError(Array(this.el_words)); + var code = (evt.which) ? evt.which : evt.keyCode + if (code == 38 && (is_gecko || is_ie || is_opera || is_webkit)) // up arrow key + { + /* TODO: Fix up arrow issue in Opera - may be a bug in Opera. See TAB handler comments below. */ + /* Konqueror cannot use this feature due to keycode conflicts. */ + + // Write the previous command in the history. + this.historyUp(); + + if (evt.returnValue) // IE + evt.returnValue = false; + if (evt.preventDefault) // DOM + evt.preventDefault(); + return false; // should work in all browsers + } + else if (code == 40 && (is_gecko || is_ie || is_opera || is_webkit)) // down arrow key + { + /* Konqueror cannot use this feature due to keycode conflicts. */ + + // Write the previous command in the history. + this.historyDown(); + + if (evt.returnValue) // IE + evt.returnValue = false; + if (evt.preventDefault) // DOM + evt.preventDefault(); + return false; // should work in all browsers + } + else if (code == 9) /* TAB key */ + { + // Do nickname completion like on IRC / Unix command line. + this.completeNick(); + + if (is_opera) + { + // Fixes Opera's loss of focus after TAB key is pressed. + // This is most likely due to a bug in Opera + // that executes the default key operation BEFORE the + // keydown and keypress event handler. + // This is probably the reason for the "up arrow" issue above. + //window.setTimeout(function(){evt.target.focus();}, 0); + evt.target.onblur = function() { this.focus(); this.onblur = null; }; + } + + if (evt.returnValue) // IE + evt.returnValue = false; + if (evt.preventDefault) // DOM + evt.preventDefault(); + return false; // Should work in all browsers. + } + else + { + // Allow other key defaults. + return true; + } + }, + callbackWords_OnKeyup: function(evt) + { + // Needed for IE since the text box loses selection/caret position on blur + this.storeSelectionPos(this.el_words); + }, + callbackWords_OnMouseup: function(evt) + { + // Needed for IE since the text box loses selection/caret position on blur + this.storeSelectionPos(this.el_words); + }, + callbackWords_OnFocus: function(evt) + { + // if (this.el_handle && this.el_handle.value == '' && !this.minmax_status) + // this.el_handle.focus(); + + // Needed for IE since the text box loses selection/caret position on blur + this.setSelection(this.el_words); + }, + callback_OnUnload: function(evt) + { + /* don't disconnect users when they reload the window + * this event doesn't only occurs when the page is closed but also when the page is reloaded */ + if (pfc_quit_on_closedwindow) + { + if (!this.isconnected) return false; + this.sendRequest('/quit'); + } + }, + + + /** + * hide error area and stop blinking fields + */ + clearError: function(ids) + { + this.el_errors.style.display = 'none'; + for (var i=0; i'+msg+''; + + // finaly append this to the message list + container.appendChild(div); + this.gui.scrollDown(tabid, div); +*/ + }, + + handleComingRequest: function( cmds ) + { + var msg_html = $H(); + var max_msgid = $H(); + + //alert(cmds.inspect()); + + for(var mid = 0; mid < cmds.length ; mid++) + { + var id = cmds[mid][0]; + var date = cmds[mid][1]; + var time = cmds[mid][2]; + var sender = cmds[mid][3]; + var recipientid = cmds[mid][4]; + var cmd = cmds[mid][5]; + var param = cmds[mid][6]; + var fromtoday = cmds[mid][7]; + var oldmsg = cmds[mid][8]; + + // format and post message + var line = ''; + line += '
'; + line += ''+ date +' '; + line += ''+ time +' '; + if (cmd == 'send') + { + line += ' '; + line += '‹'; + line += ''; + line += sender.escapeHTML(); + line += ''; + line += '›'; + line += ' '; + } + if (cmd == 'notice') + line += '* ' + this.parseMessage(param) +' '; + else if (cmd == 'me') + line += '* '+ sender.escapeHTML() + ' ' + this.parseMessage(param) +' '; + else + line += ''+ this.parseMessage(param) +' '; + line += '
'; + + if (oldmsg == 0) + if (cmd == 'send' || cmd == 'me') + { + // notify the hidden tab a message has been received + // don't notify anything if this is old messages + var tabid = recipientid; + if (this.gui.getTabId() != tabid) + this.gui.notifyTab(tabid); + // notify the window (change the title) + if (!this.detectactivity.isActive() && pfc_notify_window) + this.gui.notifyWindow(); + } + + if (msg_html.get(recipientid) == null) + msg_html.set(recipientid, line); + else + msg_html.set(recipientid, msg_html.get(recipientid) + line); + + // remember the max message id in order to clean old lines + if (!max_msgid.get(recipientid)) max_msgid.set(recipientid, 0); + if (max_msgid.get(recipientid) < id) max_msgid.set(recipientid, id); + } + + // loop on all recipients and post messages + var keys = msg_html.keys(); + for( var i=0; i scrollDown(..) will be broken + m.innerHTML = msg_html.get(recipientid); + this.colorizeNicks(m); + this.refresh_clock(m); + // finaly append this to the message list + recipientdiv.appendChild(m); + this.gui.scrollDown(tabid, m); + + // delete the old messages from the client (save some memory) + var limit_msgid = max_msgid.get(recipientid) - pfc_max_displayed_lines; + var elt = $('pfc_msg_'+recipientid+'_'+limit_msgid); + while (elt) + { + // delete this element to save browser memory + if(elt.parentNode) + elt.parentNode.removeChild(elt); + else if(elt.parentElement) // older IE browsers (<6.0) may not support parentNode + elt.parentElement.removeChild(elt); + else // if all else fails + elt.innerHTML = ''; + limit_msgid--; + elt = $('pfc_msg_'+recipientid+'_'+limit_msgid); + } + } + + }, + + calcDelay: function() + { + var lastact = new Date().getTime() - this.last_activity_time; + var dlist = this.refresh_delay_steps.slice(); + var delay = dlist.shift(); + if (this.refresh_delay > delay) delay = this.refresh_delay; + var limit; + while (typeof (limit = dlist.shift()) != "undefined") + { + var d = dlist.shift(); + if (d < delay) continue; + if (lastact > limit) delay = d; + } + return delay; + }, + + /** + * Call the ajax request function + * Will query the server + */ + sendRequest: function(cmd, recipientid) + { + // do not send another ajax requests if the last one is not yet finished + if (cmd == '/update' && this.pfc_ajax_connected) return; + + var delay = this.calcDelay(); + + if (cmd != "/update") + { + // setup a new timeout to update the chat in 5 seconds (in refresh_delay more exactly) + clearTimeout(this.timeout); + this.timeout = setTimeout('pfc.updateChat(true)', delay); + this.timeout_time = new Date().getTime() + delay; + + if (pfc_debug) + trace('sendRequest: '+cmd); + } + + // prepare the command string + var rx = new RegExp('(^\/[^ ]+) *(.*)','ig'); + if (!recipientid) recipientid = this.gui.getTabId(); + cmd = cmd.replace(rx, '$1 '+this.clientid+' '+(recipientid==''?'0':recipientid)+' $2'); + + // send the real ajax request + var url = pfc_server_script_url; + new Ajax.Request(url, { + method: 'post', + parameters: {'pfc_ajax':1, 'f':'handleRequest', 'cmd': cmd }, + onCreate: function(transport) { + this.pfc_ajax_connected = true; + // request time counter used by ping indicator + this.last_request_time = new Date().getTime(); + }.bind(this), + onSuccess: function(transport) { + if (!transport.status) return; // fix strange behavior on KHTML + + // request time counter used by ping indicator + this.last_response_time = new Date().getTime(); + // evaluate the javascript response + eval( transport.responseText ); + }.bind(this), + onComplete: function(transport) { + this.pfc_ajax_connected = false; + + // calculate the ping and display it + this.ping = Math.abs(this.last_response_time - this.last_request_time); + if ($('pfc_ping')) $('pfc_ping').innerHTML = this.ping+'ms'+' ['+parseInt(this.calcDelay() / 1000)+'s]'; + }.bind(this) + }); + }, + + /** + * update function to poll the server each 'refresh_delay' time + */ + updateChat: function(start) + { + clearTimeout(this.timeout); + if (start) + { + this.sendRequest('/update'); + + // setup the next update + var delay = this.calcDelay(); + this.timeout = setTimeout('pfc.updateChat(true)', delay); + this.timeout_time = new Date().getTime() + delay; + } + }, + + /** + * Stores the caret/selection position for IE 6.x and 7.x + * Returns true if text range start and end values were updated. + * Code based on: http://www.bazon.net/mishoo/articles.epl?art_id=1292 + */ + storeSelectionPos: function(obj) + { + // We don't need to store the start and end positions if the browser + // supports the Gecko selection model. However, these values may be + // useful for debugging. Also, Opera recognizes Gecko and IE range + // commands, so we need to ensure Opera only uses the Gecko model. + /* WARNING: Do not use this for textareas. They require a more + complex algorithm. */ + if (obj.setSelectionRange) + { + obj.selStart = obj.selectionStart; + obj.selEnd = obj.selectionEnd; + + return true; + } + + // IE + else if (obj.createTextRange && document.selection) + { + // Determine current selection start position. + var range = document.selection.createRange(); + var isCollapsed = range.compareEndPoints("StartToEnd", range) == 0; + if (!isCollapsed) + range.collapse(true); + var b = range.getBookmark(); + obj.selStart = b.charCodeAt(2) - b.charCodeAt(0) - 1; + + // Determine current selection end position. + range = document.selection.createRange(); + isCollapsed = range.compareEndPoints("StartToEnd", range) == 0; + if (!isCollapsed) + range.collapse(false); + b = range.getBookmark(); + obj.selEnd = b.charCodeAt(2) - b.charCodeAt(0) - 1; + + return true; + } + + // Browser does not support selection range processing. + else + return false; + }, + + /** + * Sets the selection/caret in the object based on the + * object's selStart and selEnd parameters. + * This should only be needed for IE only. + */ + setSelection: function(obj) + { + // This part of the function is included to prevent + // Opera from executing the IE portion. + /* WARNING: Do not attempt to use this function as + a wrapper for the Gekco based setSelectionRange. + It causes problems in Opera when executed from + the event trigger onFocus. */ + if (obj.setSelectionRange) + { + return null; + } + // IE + else if (obj.createTextRange) + { + var range = obj.createTextRange(); + range.collapse(true); + range.moveStart("character", obj.selStart); + range.moveEnd("character", obj.selEnd - obj.selStart); + range.select(); + + return range; + } + // Browser does not support selection range processing. + else + return null; + }, + + /** + * insert a smiley + */ + insertSmiley: function(smiley) + { + var w = this.el_words; + + if (w.setSelectionRange) + { + // Gecko + var s = w.selectionStart; + var e = w.selectionEnd; + w.value = w.value.substring(0, s) + smiley + w.value.substr(e); + w.setSelectionRange(s + smiley.length, s + smiley.length); + w.focus(); + } + else if (w.createTextRange) + { + // IE + w.focus(); + + // Get range based on stored values. + var range = this.setSelection(w); + + range.text = smiley; + + // Move caret position to end of smiley and collapse selection, if any. + // Check if internally kept values for selection are initialized. + w.selStart = (w.selStart) ? w.selStart + smiley.length : smiley.length; + w.selEnd = w.selStart; + } + else + { + // Unsupported browsers get smiley at end of string like old times. + w.value += smiley; + w.focus(); + } + }, + + updateNickBox: function(nickid) + { + // @todo optimize this function because it is called lot of times so it could cause CPU consuming on client side + var chanids = this.chanmeta.keys(); + for(var i = 0; chanids.length > i; i++) + { + this.updateNickListBox(chanids[i]); + } + }, + + /** + * fill the nickname list with connected nicknames + */ + updateNickListBox: function(chanid) + { + var className = (!is_ie7 && !is_ie6) ? 'class' : 'className'; + + var nickidlst = this.getChanMeta(chanid,'users').get('nickid'); + var nickdiv = this.gui.getOnlineContentFromTabId(chanid); + var ul = document.createElement('ul'); + ul.setAttribute(className, 'pfc_nicklist'); + for (var i=0; i 1 && + !navigator.appName.match("Explorer|Konqueror") && + !navigator.appVersion.match("KHTML")) + { + msg = ''; + for( var i = 0; i' + (delta>0 ? ttt[i].substring(7,range1)+ ' ... ' + ttt[i].substring(range2,ttt[i].length) : ttt[i]) + ''; + } + else + { + msg = msg + ttt[i]; + } + } + } + else + { + // fallback for IE6/Konqueror which do not support split with regexp + replace = '$1$2$3'; + msg = msg.replace(rx_url, replace); + } +*/ + + // Remove auto-linked entries. + if ( false ) + { + rx = new RegExp('.*?<\/a>','ig'); + msg = msg.replace(rx, '$1'); + rx = new RegExp('.*?<\/a>','ig'); + msg = msg.replace(rx, '$1'); + } + + // Replace double spaces outside of tags by "  " entity. + rx = new RegExp(' (?= )(?![^<]*>)','g'); + msg = msg.replace(rx, ' '); + + // try to parse bbcode + rx = new RegExp('\\[b\\](.+?)\\[\/b\\]','ig'); + msg = msg.replace(rx, '$1'); + rx = new RegExp('\\[i\\](.+?)\\[\/i\\]','ig'); + msg = msg.replace(rx, '$1'); + rx = new RegExp('\\[u\\](.+?)\\[\/u\\]','ig'); + msg = msg.replace(rx, '$1'); + rx = new RegExp('\\[s\\](.+?)\\[\/s\\]','ig'); + msg = msg.replace(rx, '$1'); + // rx = new RegExp('\\[pre\\](.+?)\\[\/pre\\]','ig'); + // msg = msg.replace(rx, '
$1
'); +/* + rx = new RegExp('\\[email\\]([A-z0-9][\\w.-]*@[A-z0-9][\\w\\-\\.]+\\.[A-z0-9]{2,6})\\[\/email\\]','ig'); + msg = msg.replace(rx, '
$1'); + rx = new RegExp('\\[email=([A-z0-9][\\w.-]*@[A-z0-9][\\w\\-\\.]+\\.[A-z0-9]{2,6})\\](.+?)\\[\/email\\]','ig'); + msg = msg.replace(rx, '$2'); +*/ + rx = new RegExp('\\[color=([a-zA-Z]+|\\#?[0-9a-fA-F]{6}|\\#?[0-9a-fA-F]{3})\\](.+?)\\[\/color\\]','ig'); + msg = msg.replace(rx, '$2'); + // parse bbcode colors twice because the current_text_color is a bbcolor + // so it's possible to have a bbcode color imbrication + rx = new RegExp('\\[color=([a-zA-Z]+|\\#?[0-9a-fA-F]{6}|\\#?[0-9a-fA-F]{3})\\](.+?)\\[\/color\\]','ig'); + msg = msg.replace(rx, '$2'); + + // try to parse smileys + var smileys = this.res.getSmileyHash(); + var sl = this.res.getSmileyKeys(); // Keys should be sorted by length from pfc.gui.loadSmileyBox() + for(var i = 0; i < sl.length; i++) + { + // We don't want to replace smiley strings inside of tags. + // Use negative lookahead to search for end of tag. + rx = new RegExp(RegExp.escape(sl[i]) + '(?![^<]*>)','g'); + msg = msg.replace(rx, '' + sl[i] + ''); + } + + // try to parse nickname for highlighting + rx = new RegExp('(^|[ :,;])'+RegExp.escape(this.nickname)+'([ :,;]|$)','gi'); + msg = msg.replace(rx, '$1'+ this.nickname +'$2'); + + // this piece of code is replaced by the word-wrap CSS3 rule. + /* + // don't allow to post words bigger than 65 caracteres + // doesn't work with crappy IE and Konqueror ! + rx = new RegExp('([^ \\:\\<\\>\\/\\&\\;]{60})','ig'); + var ttt = msg.split(rx); + if (ttt.length > 1 && + !navigator.appName.match("Explorer|Konqueror") && + !navigator.appVersion.match("KHTML")) + { + msg = ''; + for( var i = 0; i i; i++) + nicktochange[i].style.color = color; + + }, + + /** + * Returns a list of elements which have a clsName class + */ + getElementsByClassName: function( root, clsName, clsIgnore ) + { + var i, matches = new Array(); + var els = root.getElementsByTagName('*'); + var rx1 = new RegExp('.*'+clsName+'.*'); + var rx2 = new RegExp('.*'+clsIgnore+'.*'); + for(i=0; i i; i++) + if (show) + elts[i].style.display = 'inline'; + else + elts[i].style.display = 'none'; + }, + + + /** + * Nickname marker show/hide + */ + nickmarker_swap: function() + { + if (this.nickmarker) { + this.nickmarker = false; + } else { + this.nickmarker = true; + } + this.refresh_nickmarker() + setCookie('pfc_nickmarker', this.nickmarker); + }, + refresh_nickmarker: function(root) + { + var nickmarker_icon = $('pfc_nickmarker'); + if (!root) root = $('pfc_channels_content'); + if (this.nickmarker) + { + nickmarker_icon.src = this.res.getFileUrl('images/color-on.gif'); + nickmarker_icon.alt = this.res.getLabel("Hide nickname marker"); + nickmarker_icon.title = nickmarker_icon.alt; + this.colorizeNicks(root); + } + else + { + nickmarker_icon.src = this.res.getFileUrl('images/color-off.gif'); + nickmarker_icon.alt = this.res.getLabel("Show nickname marker"); + nickmarker_icon.title = nickmarker_icon.alt; + var elts = this.getElementsByClassName(root, 'pfc_nickmarker', ''); + for(var i = 0; elts.length > i; i++) + { + // this is not supported in konqueror =>>> elts[i].removeAttribute('style'); + elts[i].style.color = ''; + } + } + }, + + + /** + * Date/Hour show/hide + */ + clock_swap: function() + { + if (this.clock) { + this.clock = false; + } else { + this.clock = true; + } + this.refresh_clock(); + setCookie('pfc_clock', this.clock); + }, + refresh_clock: function( root ) + { + var clock_icon = $('pfc_clock'); + if (!root) root = $('pfc_channels_content'); + if (this.clock) + { + clock_icon.src = this.res.getFileUrl('images/clock-on.gif'); + clock_icon.alt = this.res.getLabel('Hide dates and hours'); + clock_icon.title = clock_icon.alt; + this.showClass(root, 'pfc_date', 'pfc_invisible', true); + this.showClass(root, 'pfc_heure', 'pfc_invisible', true); + } + else + { + clock_icon.src = this.res.getFileUrl('images/clock-off.gif'); + clock_icon.alt = this.res.getLabel('Show dates and hours'); + clock_icon.title = clock_icon.alt; + this.showClass(root, 'pfc_date', 'pfc_invisible', false); + this.showClass(root, 'pfc_heure', 'pfc_invisible', false); + } + // browser automaticaly scroll up misteriously when showing the dates + // $('pfc_chat').scrollTop += 30; + }, + + /** + * Sound button + */ + sound_swap: function() + { + if (this.issoundenable) { + this.issoundenable = false; + } else { + this.issoundenable = true; + } + this.refresh_sound(); + setCookie('pfc_issoundenable', this.issoundenable); + }, + refresh_sound: function( root ) + { + var snd_icon = $('pfc_sound'); + if (this.issoundenable) + { + snd_icon.src = this.res.getFileUrl('images/sound-on.gif'); + snd_icon.alt = this.res.getLabel('Disable sound notifications'); + snd_icon.title = snd_icon.alt; + } + else + { + snd_icon.src = this.res.getFileUrl('images/sound-off.gif'); + snd_icon.alt = this.res.getLabel('Enable sound notifications'); + snd_icon.title = snd_icon.alt; + } + }, + + /** + * Connect/disconnect button + */ + connect_disconnect: function() + { + if (this.isconnected) + this.sendRequest('/quit'); + else + { + if (this.nickname == '') + this.askNick(); + else + this.sendRequest('/connect "'+this.nickname+'"'); + } + }, + refresh_loginlogout: function() + { + var loginlogout_icon = $('pfc_loginlogout'); + if (this.isconnected) + { + loginlogout_icon.src = this.res.getFileUrl('images/logout.gif'); + loginlogout_icon.alt = this.res.getLabel('Disconnect'); + loginlogout_icon.title = loginlogout_icon.alt; + } + else + { + this.clearMessages(); + this.clearNickList(); + loginlogout_icon.src = this.res.getFileUrl('images/login.gif'); + loginlogout_icon.alt = this.res.getLabel('Connect'); + loginlogout_icon.title = loginlogout_icon.alt; + } + }, + + + /** + * Minimize/Maximized the chat zone + */ + swap_minimize_maximize: function() + { + if (this.minmax_status) { + this.minmax_status = false; + } else { + this.minmax_status = true; + } + setCookie('pfc_minmax_status', this.minmax_status); + this.refresh_minimize_maximize(); + }, + refresh_minimize_maximize: function() + { + var content = $('pfc_content_expandable'); + var btn = $('pfc_minmax'); + if (this.minmax_status) + { + btn.src = this.res.getFileUrl('images/maximize.gif'); + btn.alt = this.res.getLabel('Magnify'); + btn.title = btn.alt; + content.style.display = 'none'; + } + else + { + btn.src = this.res.getFileUrl('images/minimize.gif'); + btn.alt = this.res.getLabel('Cut down'); + btn.title = btn.alt; + content.style.display = 'block'; + } + }, + + + /** + * BBcode ToolBar + */ + insert_text: function(open, close, promptifselempty) + { + var msgfield = $('pfc_words'); + + var pfcp = this.getPrompt(); + pfcp.msgfield = msgfield; + pfcp.open = open; + pfcp.close = close; + pfcp.promptifselempty = promptifselempty; + pfcp.callback = this.insert_text_callback; + + // Gecko + /* Always check for Gecko selection processing commands + first. This is needed for Opera. */ + if (msgfield.selectionStart || msgfield.selectionStart == '0') + { + var startPos = msgfield.selectionStart; + var endPos = msgfield.selectionEnd; + + var text = msgfield.value.substring(startPos, endPos); + if (startPos == endPos && promptifselempty) + { + pfcp.prompt(this.res.getLabel('Enter the text to format'), ''); + pfcp.focus(); + } + else + this.insert_text_callback(text, pfcp); + } + + // IE + else if (document.selection && document.selection.createRange) + { + msgfield.focus(); + + // Get selection range. + pfcp.range = this.setSelection(msgfield); + var text = pfcp.range.text; + if (text == "" && promptifselempty) + { + pfcp.prompt(this.res.getLabel('Enter the text to format'), ''); + pfcp.focus(); + } + else + this.insert_text_callback(text, pfcp); + } + + // Fallback support for other browsers + else + { + pfcp.prompt(this.res.getLabel('Enter the text to format'), ''); + pfcp.focus(); + } + return; + }, + insert_text_callback: function(text, pfcp) + { + var open = pfcp.open; + var close = pfcp.close; + var promptifselempty = pfcp.promptifselempty; + var msgfield = pfcp.msgfield; + var range = pfcp.range; + + // Gecko + /* Always check for Gecko selection processing commands + first. This is needed for Opera. */ + if (msgfield.selectionStart || msgfield.selectionStart == '0') + { + var startPos = msgfield.selectionStart; + var endPos = msgfield.selectionEnd; + + var extralength = 0; + if (startPos == endPos && promptifselempty) + { + if (text == null) text = ""; + extralength = text.length; + } + if (text.length > 0 || !promptifselempty) + { + msgfield.value = msgfield.value.substring(0, startPos) + open + text + close + msgfield.value.substring(endPos, msgfield.value.length); + var caretPos = endPos + open.length + extralength + close.length; + msgfield.setSelectionRange(caretPos, caretPos); + msgfield.focus(); + } + } + // IE + else if (document.selection && document.selection.createRange) + { + if (text == null) text = ""; + if (text.length > 0 || !promptifselempty) + { + msgfield.focus(); + + range.text = open + text + close; + + // Increment caret position. + // Check if internally kept values for selection are initialized. + msgfield.selStart = (msgfield.selStart) ? msgfield.selStart + open.length + text.length + close.length : open.length + text.length + close.length; + msgfield.selEnd = msgfield.selStart; + + msgfield.focus(); + } + } + // Fallback support for other browsers + else + { + if (text == null) text = ""; + if (text.length > 0 || !promptifselempty) + { + msgfield.value += open + text + close; + msgfield.focus(); + } + } + }, + + /** + * Minimize/Maximize none/inline + */ + minimize_maximize: function(idname, type) + { + var element = $(idname); + if(element.style) + { + if(element.style.display == type ) + { + element.style.display = 'none'; + } + else + { + element.style.display = type; + } + } + }, + + switch_text_color: function(color) + { + /* clear any existing borders on the color buttons */ + var colorbtn = this.getElementsByClassName($('pfc_colorlist'), 'pfc_color', ''); + for(var i = 0; colorbtn.length > i; i++) + { + colorbtn[i].style.border = 'none'; + colorbtn[i].style.padding = '0'; + } + + /* assign the new border style to the selected button */ + this.current_text_color = color; + setCookie('pfc_current_text_color', this.current_text_color); + var idname = 'pfc_color_' + color; + $(idname).style.border = '1px solid #555'; + $(idname).style.padding = '1px'; + + // assigne the new color to the input text box + this.el_words.style.color = '#'+color; + this.el_words.focus(); + }, + + /** + * Smiley show/hide + */ + showHideSmileys: function() + { + if (this.showsmileys) + { + this.showsmileys = false; + } + else + { + this.showsmileys = true; + } + setCookie('pfc_showsmileys', this.showsmileys); + this.refresh_Smileys(); + }, + refresh_Smileys: function() + { + // first of all : show/hide the smiley box + var content = $('pfc_smileys'); + if (this.showsmileys) + content.style.display = 'block'; + else + content.style.display = 'none'; + + // then switch the button icon + var btn = $('pfc_showHideSmileysbtn'); + if (this.showsmileys) + { + if (btn) + { + btn.src = this.res.getFileUrl('images/smiley-on.gif'); + btn.alt = this.res.getLabel('Hide smiley box'); + btn.title = btn.alt; + } + } + else + { + if (btn) + { + btn.src = this.res.getFileUrl('images/smiley-off.gif'); + btn.alt = this.res.getLabel('Show smiley box'); + btn.title = btn.alt; + } + } + }, + + + /** + * Show Hide who's online + */ + showHideWhosOnline: function() + { + if (this.showwhosonline) + { + this.showwhosonline = false; + } + else + { + this.showwhosonline = true; + } + setCookie('pfc_showwhosonline', this.showwhosonline); + this.refresh_WhosOnline(); + }, + refresh_WhosOnline: function() + { + // first of all : show/hide the nickname list box + var root = $('pfc_channels_content'); + var contentlist = this.getElementsByClassName(root, 'pfc_online', ''); + for(var i = 0; i < contentlist.length; i++) + { + var content = contentlist[i]; + if (this.showwhosonline) + content.style.display = 'block'; + else + content.style.display = 'none'; + content.style.zIndex = '100'; // for IE6, force the nickname list borders to be shown + } + + // then refresh the button icon + var btn = $('pfc_showHideWhosOnlineBtn'); + if (!btn) return; + if (this.showwhosonline) + { + btn.src = this.res.getFileUrl('images/online-on.gif'); + btn.alt = this.res.getLabel('Hide online users box'); + btn.title = btn.alt; + } + else + { + btn.src = this.res.getFileUrl('images/online-off.gif'); + btn.alt = this.res.getLabel('Show online users box'); + btn.title = btn.alt; + } + this.refresh_Chat(); + }, + + /** + * Resize chat + */ + refresh_Chat: function() + { + // resize all the tabs content + var root = $('pfc_channels_content'); + var contentlist = this.getElementsByClassName(root, 'pfc_chat', ''); + for(var i = 0; i < contentlist.length; i++) + { + var chatdiv = contentlist[i]; + if (!this.showwhosonline) + { + chatdiv.style.width = '100%'; + } + else + { + chatdiv.style.width = ''; + } + } + }, + + getPrompt: function() + { + if (!this.pfc) + this.pfc = new pfcPrompt($('pfc_container')); + return this.pfc; + } +}; diff --git a/instmess/data/public/js/pfcgui.js b/instmess/data/public/js/pfcgui.js new file mode 100644 index 0000000..8e925b2 --- /dev/null +++ b/instmess/data/public/js/pfcgui.js @@ -0,0 +1,448 @@ +/** + * This class centralize the pfc' Graphic User Interface manipulations + * (depends on prototype library) + * @author Stephane Gully + */ +var pfcGui = Class.create(); +pfcGui.prototype = { + + initialize: function() + { +// this.builder = new pfcGuiBuilder(); + this.current_tab = ''; + this.current_tab_id = ''; + this.tabs = Array(); + this.tabids = Array(); + this.tabtypes = Array(); + this.chatcontent = $H(); + this.onlinecontent = $H(); + this.scrollpos = $H(); + this.elttoscroll = $H(); + this.windownotifynb = 0; + }, + + /** + * Scroll down the message list area by elttoscroll height + * - elttoscroll is a message DOM element which has been appended to the tabid's message list + * - this.elttoscroll is an array containing the list of messages that will be scrolled + * when the corresponding tab will be shown (see setTabById bellow). + * It is necessary to keep in cache the list of hidden (because the tab is inactive) messages + * because the 'scrollTop' javascript attribute + * will not work if the element (tab content) is hidden. + */ + scrollDown: function(tabid, elttoscroll) + { + // check the wanted tabid is the current active one + if (this.getTabId() != tabid) + { + // no it's not the current active one so just cache the elttoscroll in the famouse this.elttoscroll array + if (!this.elttoscroll.get(tabid)) this.elttoscroll.set(tabid, Array()); + this.elttoscroll.get(tabid).push(elttoscroll); + return; + } + // the wanted tab is active so just scroll down the tab content element + // by elttoscroll element height (use 'offsetHeight' attribute) + var content = this.getChatContentFromTabId(tabid); + + // the next line seems to help with IE6 scroll on the first load + // http://sourceforge.net/tracker/index.php?func=detail&aid=1568264&group_id=158880&atid=809601 + var dudVar = content.scrollTop; + content.scrollTop += elttoscroll.offsetHeight+2; + this.scrollpos.set(tabid, content.scrollTop); + }, + + isCreated: function(tabid) + { + /* + for (var i = 0; i < this.tabids.length ; i++) + { + if (this.tabids[i] == tabid) return true; + } + return false; + */ + return (indexOf(this.tabids, tabid) >= 0); + }, + + setTabById: function(tabid) + { + var className = (!is_ie7 && !is_ie6) ? 'class' : 'className'; + + // first of all save the scroll pos of the visible tab + var content = this.getChatContentFromTabId(this.current_tab_id); + this.scrollpos.set(this.current_tab_id, content.scrollTop); + + // start without selected tabs + this.current_tab = ''; + this.current_tab_id = ''; + var tab_to_show = null; + // try to fine the tab to select and select it! + for (var i=0; i 0) + { + // on by one + for (var i=0; i'; + flash += ''; + flash += ''; + flash += ''; + soundcontainer.innerHTML = flash; + } + }, + unnotifyWindow: function() + { + this.windownotifynb = 0; + var rx = new RegExp('^\\[[0-9]+\\](.*)','ig'); + document.title = document.title.replace(rx,'$1'); + + // stop the sound + var soundcontainer = document.getElementById('pfc_sound_container'); + if (pfc.issoundenable) + soundcontainer.innerHTML = ''; + }, + + /** + * This function change the tab icon in order to catch the attention + */ + notifyTab: function(tabid) + { + var className = (!is_ie7 && !is_ie6) ? 'class' : 'className'; + + // first of all be sure the tab highlighting is cleared + this.unnotifyTab(tabid); + + var tabpos = indexOf(this.tabids, tabid); + var tabtype = this.tabtypes[tabpos]; + + // handle the tab's image modification + var img = $('pfc_tabimg'+tabid); + if (img) + { + if (tabtype == 'ch') + img.src = pfc.res.getFileUrl('images/ch-active.gif'); + if (tabtype == 'pv') + img.src = pfc.res.getFileUrl('images/pv-active.gif'); + } + + // handle the blicking effect + var div = $('pfc_tabdiv'+tabid); + if (div) + { + if (div.blinkstat == true) + { + div.setAttribute(className, 'pfc_tabblink1'); + } + else + { + div.setAttribute(className, 'pfc_tabblink2'); + } + div.blinkstat = !div.blinkstat; + div.blinktimeout = setTimeout('pfc.gui.notifyTab(\''+tabid+'\');', 500); + } + }, + + /** + * This function restore the tab icon to its default value + */ + unnotifyTab: function(tabid) + { + var className = (!is_ie7 && !is_ie6) ? 'class' : 'className'; + + var tabpos = indexOf(this.tabids, tabid); + var tabtype = this.tabtypes[tabpos]; + + // restore the tab's image + var img = $('pfc_tabimg'+tabid); + if (img) + { + if (tabtype == 'ch') + img.src = pfc.res.getFileUrl('images/ch.gif'); + if (tabtype == 'pv') + img.src = pfc.res.getFileUrl('images/pv.gif'); + } + + // stop the blinking effect + var div = $('pfc_tabdiv'+tabid); + if (div) + { + div.removeAttribute(className); + clearTimeout(div.blinktimeout); + } + }, + + loadSmileyBox: function() + { + var container = $('pfc_smileys'); + var smileys = pfc.res.getSmileyReverseHash();//getSmileyHash(); + var sl = smileys.keys(); + pfc.res.sortSmileyKeys(); // Sort smiley keys once. + for(var i = 0; i < sl.length; i++) + { + s_url = sl[i]; + s_symbol = smileys.get(sl[i]); + s_symbol = s_symbol.unescapeHTML(); + // Replace " with " for IE and Webkit browsers. + // The prototype.js version 1.5.1.1 unescapeHTML() function does not do this. + if (is_ie || is_webkit) + s_symbol = s_symbol.replace(/"/g,'"'); + + var img = document.createElement('img'); + img.setAttribute('src', s_url); + img.setAttribute('alt', s_symbol); + img.setAttribute('title', s_symbol); + img.s_symbol = s_symbol; + img.onclick = function(){ pfc.insertSmiley(this.s_symbol); } + container.appendChild(img); + container.appendChild(document.createTextNode(' ')); // so smileys will wrap fine if lot of smiles in theme. + } + }, + + loadBBCodeColorList: function() + { + var className = (!is_ie7 && !is_ie6) ? 'class' : 'className'; + + // color list + var clist = $('pfc_colorlist'); + var clist_v = pfc_bbcode_color_list; + for (var i=0 ; i this.container.offsetHeight + || this.container.scrollWidth > this.container.offsetWidth) + { + this.bgbox.style.height = this.container.scrollHeight+'px'; + this.bgbox.style.width = this.container.scrollWidth+'px'; + } + else + { + this.bgbox.style.height = this.container.offsetHeight+'px'; + this.bgbox.style.width = this.container.offsetWidth+'px'; + } + this.bgbox.style.display = 'block'; + + // Position the dialog box on the screen and make it visible. + this.box.style.display = 'block'; + this.box.style.top = parseInt(pos[1]+(this.bgbox.offsetHeight-this.box.offsetHeight)/2)+'px'; + this.box.style.left = parseInt(pos[0]+(this.bgbox.offsetWidth-this.box.offsetWidth)/2)+'px'; + this.prompt_field.value = def; + this.prompt_field.focus(); // Give the dialog box's input field the focus. + this.prompt_title.innerHTML = text; + }, + + _doSubmit: function(canceled) + { + // _doSubmit is called when the user enters or cancels the box. + var val = this.prompt_field.value; + if (is_gecko) this.box.focus(); // test is_gecko because it doesn't work on KHTML browser, the popup shows infinitly + this.box.style.display = 'none'; // clear out the dialog box + this.bgbox.style.display = 'none'; // clear out the screen + this.prompt_field.value = ''; // clear out the text field + // if the cancel button was pushed, force value to null. + if (canceled) { val = '' } + // call the user's function + this.callback(val,this); + return false; + }, + + _findPos: function(obj) + { + var curleft = curtop = 0; + if (obj.offsetParent) { + curleft = obj.offsetLeft; + curtop = obj.offsetTop; + while (obj = obj.offsetParent) { + curleft += obj.offsetLeft; + curtop += obj.offsetTop; + } + } + return [curleft,curtop]; + }, + + focus: function() + { + this.prompt_field.focus(); + }, + + callback: function(v,pfcp) + { + } + + +} + diff --git a/instmess/data/public/js/pfcresource.js b/instmess/data/public/js/pfcresource.js new file mode 100644 index 0000000..eafded4 --- /dev/null +++ b/instmess/data/public/js/pfcresource.js @@ -0,0 +1,96 @@ +/** + * This class centralize the pfc resources (translated messages, images, themes ...) + * (depends on prototype library) + * @author Stephane Gully + */ +var pfcResource = Class.create(); +pfcResource.prototype = { + + initialize: function() + { + this.labels = $H(); + this.fileurl = $H(); + this.smileys = $H(); + this.smileysreverse = $H(); + this.smileyskeys = new Array(); + }, + + setLabel: function(key, value) + { + this.labels.set(key,value); + }, + + getLabel: function() + { + var key = this.getLabel.arguments[0]; + if (this.labels.get(key)) + { + this.getLabel.arguments[0] = this.labels.get(key); + return String.sprintf2(this.getLabel.arguments); + } + else + return '_'+key+'_'; + }, + + setFileUrl: function(key, value) + { + this.fileurl.set(key,value); + }, + + getFileUrl: function(key) + { + if (this.fileurl.get(key)) + return this.fileurl.get(key); + else + return ""; + }, + + setSmiley: function(key, value) + { + this.smileys.set(key, value); + this.smileysreverse.set(value,key); + this.smileyskeys.push(key); + }, + getSmiley: function(key) + { + if (this.smileys.get(key)) + return this.smileys.get(key); + else + return ""; + }, + getSmileyHash: function() + { + return this.smileys; + }, + getSmileyReverseHash: function() + { + return this.smileysreverse; + }, + getSmileyKeys: function() + { + return this.smileyskeys; + }, + sortSmileyKeys: function() + { + // Sort keys by longest to shortest. This prevents a smiley like :) from being used on >:) + return this.smileyskeys.sort( + function (a,b) + { + var x = a.unescapeHTML(); + var y = b.unescapeHTML(); + + // Replace " with " for IE and Webkit browsers. + // The prototype.js version 1.5.1.1 unescapeHTML() function does not do this. + if (is_ie || is_webkit) + { + x = x.replace(/"/g,'"'); + y = y.replace(/"/g,'"'); + } + + return (y.length - x.length); + } + ); + } +}; + + diff --git a/instmess/data/public/js/prototype.js b/instmess/data/public/js/prototype.js new file mode 100644 index 0000000..6385503 --- /dev/null +++ b/instmess/data/public/js/prototype.js @@ -0,0 +1,4221 @@ +/* Prototype JavaScript framework, version 1.6.0.2 + * (c) 2005-2008 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.6.0.2', + + Browser: { + IE: !!(window.attachEvent && !window.opera), + Opera: !!window.opera, + WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, + MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) + }, + + BrowserFeatures: { + XPath: !!document.evaluate, + ElementExtensions: !!window.HTMLElement, + SpecificElementExtensions: + document.createElement('div').__proto__ && + document.createElement('div').__proto__ !== + document.createElement('form').__proto__ + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +/* Based on Alex Arnell's inheritance implementation. */ +var Class = { + create: function() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + var subclass = function() { }; + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + + return klass; + } +}; + +Class.Methods = { + addMethods: function(source) { + var ancestor = this.superclass && this.superclass.prototype; + var properties = Object.keys(source); + + if (!Object.keys({ toString: true }).length) + properties.push("toString", "valueOf"); + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames().first() == "$super") { + var method = value, value = Object.extend((function(m) { + return function() { return ancestor[m].apply(this, arguments) }; + })(property).wrap(method), { + valueOf: function() { return method }, + toString: function() { return method.toString() } + }); + } + this.prototype[property] = value; + } + + return this; + } +}; + +var Abstract = { }; + +Object.extend = function(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; +}; + +Object.extend(Object, { + inspect: function(object) { + try { + if (Object.isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + toJSON: function(object) { + var type = typeof object; + switch (type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (Object.isElement(object)) return; + + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (!Object.isUndefined(value)) + results.push(property.toJSON() + ': ' + value); + } + + return '{' + results.join(', ') + '}'; + }, + + toQueryString: function(object) { + return $H(object).toQueryString(); + }, + + toHTML: function(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({ }, object); + }, + + isElement: function(object) { + return object && object.nodeType == 1; + }, + + isArray: function(object) { + return object != null && typeof object == "object" && + 'splice' in object && 'join' in object; + }, + + isHash: function(object) { + return object instanceof Hash; + }, + + isFunction: function(object) { + return typeof object == "function"; + }, + + isString: function(object) { + return typeof object == "string"; + }, + + isNumber: function(object) { + return typeof object == "number"; + }, + + isUndefined: function(object) { + return typeof object == "undefined"; + } +}); + +Object.extend(Function.prototype, { + argumentNames: function() { + var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); + return names.length == 1 && !names[0] ? [] : names; + }, + + bind: function() { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } + }, + + bindAsEventListener: function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } + }, + + curry: function() { + if (!arguments.length) return this; + var __method = this, args = $A(arguments); + return function() { + return __method.apply(this, args.concat($A(arguments))); + } + }, + + delay: function() { + var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + }, + + wrap: function(wrapper) { + var __method = this; + return function() { + return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + } + }, + + methodize: function() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + return __method.apply(null, [this].concat($A(arguments))); + }; + } +}); + +Function.prototype.defer = Function.prototype.delay.curry(0.01); + +Date.prototype.toJSON = function() { + return '"' + this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z"'; +}; + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + } finally { + this.currentlyExecuting = false; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + unescapeHTML: function() { + var div = new Element('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? (div.childNodes.length > 1 ? + $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : + div.childNodes[0].nodeValue) : ''; + }, + + toQueryParams: function(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + }, + + camelize: function() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + }, + + capitalize: function() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + }, + + underscore: function() { + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { + var character = String.specialChar[match[0]]; + return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + }, + + toJSON: function() { + return this.inspect(true); + }, + + unfilterJSON: function(filter) { + return this.sub(filter || Prototype.JSONFilter, '#{1}'); + }, + + isJSON: function() { + var str = this; + if (str.blank()) return false; + str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + return /^\s*$/.test(this); + }, + + interpolate: function(object, pattern) { + return new Template(this, pattern).evaluate(object); + } +}); + +if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { + escapeHTML: function() { + return this.replace(/&/g,'&').replace(//g,'>'); + }, + unescapeHTML: function() { + return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +}; + +String.prototype.parseQuery = String.prototype.toQueryParams; + +Object.extend(String.prototype.escapeHTML, { + div: document.createElement('div'), + text: document.createTextNode('') +}); + +with (String.prototype.escapeHTML) div.appendChild(text); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return ''; + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = { + each: function(iterator, context) { + var index = 0; + iterator = iterator.bind(context); + try { + this._each(function(value) { + iterator(value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + }, + + all: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function(iterator, context) { + iterator = iterator.bind(context); + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator, context) { + iterator = iterator.bind(context); + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(filter, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(filter); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator(value, index)); + }); + return results; + }, + + include: function(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator, context) { + iterator = iterator.bind(context); + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result; + this.each(function(value, index) { + value = iterator(value, index); + if (result == null || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result; + this.each(function(value, index) { + value = iterator(value, index); + if (result == null || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator, context) { + iterator = iterator.bind(context); + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator, context) { + iterator = iterator.bind(context); + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +}; + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + filter: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray, + every: Enumerable.all, + some: Enumerable.any +}); +function $A(iterable) { + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + +if (Prototype.Browser.WebKit) { + $A = function(iterable) { + if (!iterable) return []; + if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && + iterable.toArray) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; + }; +} + +Array.from = $A; + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(Object.isArray(value) ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + }, + + intersect: function(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + }, + + toJSON: function() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (!Object.isUndefined(value)) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } +}); + +// use native browser JS 1.6 implementation if available +if (Object.isFunction(Array.prototype.forEach)) + Array.prototype._each = Array.prototype.forEach; + +if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; +}; + +if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; +}; + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for (var i = 0, length = arguments.length; i < length; i++) { + if (Object.isArray(arguments[i])) { + for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + }; +} +Object.extend(Number.prototype, { + toColorPart: function() { + return this.toPaddedString(2, 16); + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +$w('abs round ceil floor').each(function(method){ + Number.prototype[method] = Math[method].methodize(); +}); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + return { + initialize: function(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + }, + + _each: function(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + set: function(key, value) { + return this._object[key] = value; + }, + + get: function(key) { + return this._object[key]; + }, + + unset: function(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + }, + + toObject: function() { + return Object.clone(this._object); + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + index: function(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + }, + + merge: function(object) { + return this.clone().update(object); + }, + + update: function(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return values.map(toQueryPair.curry(key)).join('&'); + } + return toQueryPair(key, values); + }).join('&'); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Object.toJSON(this.toObject()); + }, + + clone: function() { + return new Hash(this); + } + } +})()); + +Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; +Hash.from = $H; +var ObjectRange = Class.create(Enumerable, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +}; + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); + +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); + +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + // when GET, append parameters to URL + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + // DOM level 2 ECMAScript Language Binding + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + +(function() { + var element = this.Element; + this.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (Prototype.Browser.IE && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(this.Element, element || { }); +}).call(window); + +Element.cache = { }; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + content = Object.toHTML(content); + element.innerHTML = content.stripScripts(); + content.evalScripts.bind(content).defer(); + return element; + }, + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $(element).select("*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (Object.isString(selector)) + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = element.ancestors(); + return Object.isNumber(expression) ? ancestors[expression] : + Selector.findElement(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? element.descendants()[expression] : + element.select(expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = element.previousSiblings(); + return Object.isNumber(expression) ? previousSiblings[expression] : + Selector.findElement(previousSiblings, expression, index); + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = element.nextSiblings(); + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); + }, + + select: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + adjacent: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = element.readAttribute('id'), self = arguments.callee; + if (id) return id; + do { id = 'anonymous_element_' + self.counter++ } while ($(id)); + element.writeAttribute('id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!element.hasClassName(className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return element[element.hasClassName(className) ? + 'removeClassName' : 'addClassName'](className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + var originalAncestor = ancestor; + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (element.sourceIndex && !Prototype.Browser.Opera) { + var e = element.sourceIndex, a = ancestor.sourceIndex, + nextAncestor = ancestor.nextSibling; + if (!nextAncestor) { + do { ancestor = ancestor.parentNode; } + while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); + } + if (nextAncestor && nextAncestor.sourceIndex) + return (e > a && e < nextAncestor.sourceIndex); + } + + while (element = element.parentNode) + if (element == originalAncestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = element.cumulativeOffset(); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = $(element).getStyle('display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (element.getStyle('position') == 'absolute') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + var offsets = element.positionedOffset(); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (element.getStyle('position') == 'relative') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + // find page position of source + source = $(source); + var p = source.viewportOffset(); + + // find coordinate system to use + element = $(element); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(element, 'position') == 'absolute') { + parent = element.getOffsetParent(); + delta = parent.viewportOffset(); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Element.Methods.identify.counter = 1; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + // returns '0px' for hidden elements; we want it to return null + if (!Element.visible(element)) return null; + + // returns the border-box dimensions rather than the content-box + // dimensions, so we subtract padding and borders from the value + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + // IE doesn't report offsets correctly for static elements, so we change them + // to "relative" to get the values, then change them back. + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + // Trigger hasLayout on the offset parent so that IE6 reports + // accurate offsetTop and offsetLeft values for position: fixed. + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = { + read: { + names: { + 'class': 'className', + 'for': 'htmlFor' + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: function(element, attribute) { + attribute = element.getAttribute(attribute); + return attribute ? attribute.toString().slice(23, -2) : null; + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + }; + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr, + src: v._getAttr, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + // Safari returns margins on body which is incorrect if the child is absolutely + // positioned. For performance reasons, redefine Element#cumulativeOffset for + // KHTML/WebKit only. + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if (Prototype.Browser.IE || Prototype.Browser.Opera) { + // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements + Element.Methods.update = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + + content = Object.toHTML(content); + var tagName = element.tagName.toUpperCase(); + + if (tagName in Element._insertionTranslations.tags) { + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { element.appendChild(node) }); + } + else element.innerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +if ('outerHTML' in document.createElement('div')) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + } else div.innerHTML = html; + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['', '
', 1], + TBODY: ['', '
', 2], + TR: ['', '
', 3], + TD: ['
', '
', 4], + SELECT: ['', 1] + } +}; + +(function() { + Object.extend(this.tags, { + THEAD: this.tags.TBODY, + TFOOT: this.tags.TBODY, + TH: this.tags.TD + }); +}).call(Element._insertionTranslations); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return node && node.specified; + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +if (!Prototype.BrowserFeatures.ElementExtensions && + document.createElement('div').__proto__) { + window.HTMLElement = { }; + window.HTMLElement.prototype = document.createElement('div').__proto__; + Prototype.BrowserFeatures.ElementExtensions = true; +} + +Element.extend = (function() { + if (Prototype.BrowserFeatures.SpecificElementExtensions) + return Prototype.K; + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || element._extendedByPrototype || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName, property, value; + + // extend methods for specific tags + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + for (property in methods) { + value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + // extend methods for all tags (Safari doesn't need this) + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = { }; + window[klass].prototype = document.createElement(tagName).__proto__; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + +document.viewport = { + getDimensions: function() { + var dimensions = { }; + var B = Prototype.Browser; + $w('width height').each(function(d) { + var D = d.capitalize(); + dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : + (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; + }); + return dimensions; + }, + + getWidth: function() { + return this.getDimensions().width; + }, + + getHeight: function() { + return this.getDimensions().height; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; +/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + this.compileMatcher(); + }, + + shouldUseXPath: function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + // Safari 3 chokes on :*-of-type and :empty + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + // XPath can't do namespaced attributes, nor can it read + // the "checked" property from DOM nodes + if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) + return false; + + return true; + }, + + compileMatcher: function() { + if (this.shouldUseXPath()) + return this.compileXPathMatcher(); + + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : + new Template(c[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + if (m = e.match(ps[i])) { + this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : + new Template(x[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + if (this.xpath) return document._getElementsByXPath(this.xpath, root); + return this.matcher(root); + }, + + match: function(element) { + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m; + + while (e && le !== e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + // use the Selector.assertions methods unless the selector + // is too complex. + if (as[i]) { + this.tokens.push([i, Object.clone(m)]); + e = e.replace(m[0], ''); + } else { + // reluctantly do a document-wide search + // and look for a match in the array + return this.findElements(document).include(element); + } + } + } + } + + var match = true, name, matches; + for (var i = 0, token; token = this.tokens[i]; i++) { + name = token[0], matches = token[1]; + if (!Selector.assertions[name](element, matches)) { + match = false; break; + } + } + + return match; + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } +}); + +Object.extend(Selector, { + _cache: { }, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, + attr: function(m) { + m[1] = m[1].toLowerCase(); + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", + 'checked': "[@checked]", + 'disabled': "[@disabled]", + 'enabled': "[not(@disabled)]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, v; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in p) { + if (m = e.match(p[i])) { + v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: { + // combinators must be listed first + // (and descendant needs to be last combinator) + laterSibling: /^\s*~\s*/, + child: /^\s*>\s*/, + adjacent: /^\s*\+\s*/, + descendant: /^\s/, + + // selectors follow + tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, + id: /^#([\w\-\*]+)(\b|$)/, + className: /^\.([\w\-\*]+)(\b|$)/, + pseudo: +/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, + attrPresence: /^\[([\w]+)\]/, + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ + }, + + // for Selector.match and Element#match + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); + } + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + var _true = Prototype.emptyFunction; + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = _true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = undefined; + return nodes; + }, + + // mark each child node with its position (for nth calls) + // "ofType" flag indicates whether we're indexing for nth-of-type + // rather than nth-child + index: function(parentNode, reverse, ofType) { + parentNode._countedByPrototype = Prototype.emptyFunction; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + var node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + }, + + // filters out duplicates and extends all nodes + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (!(n = nodes[i])._countedByPrototype) { + n._countedByPrototype = Prototype.emptyFunction; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // TOKEN FUNCTIONS + tagName: function(nodes, root, tagName, combinator) { + var uTagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + // fastlane for ordinary descendant combinators + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() === uTagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + if (!targetNode) return []; + if (!nodes && root == document) return [targetNode]; + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + // handles the an+b logic + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._countedByPrototype) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + // IE treats comments as element nodes + if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._countedByPrototype) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled) results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv.startsWith(v); }, + '$=': function(nv, v) { return nv.endsWith(v); }, + '*=': function(nv, v) { return nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } + }, + + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; + }, + + matchElements: function(elements, expression) { + var matches = $$(expression), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._countedByPrototype) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + expressions = Selector.split(expressions.join(',')); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + Object.extend(Selector.handlers, { + // IE returns comment nodes on getElementsByTagName("*"). + // Filter them out. + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + }, + + // IE improperly serializes _countedByPrototype in (inner|outer)HTML. + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } + }); +} + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + // a key is already present; construct an array of values + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.blur(); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, index) { + if (Object.isUndefined(index)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, value, single = !Object.isArray(index); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + value = this.optionValue(opt); + if (single) { + if (value == index) { + opt.selected = true; + return; + } + } + else opt.selected = index.include(value); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) var Event = { }; + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: { }, + + relatedTarget: function(event) { + var element; + switch(event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } +}); + +Event.Methods = (function() { + var isButton; + + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + isButton = function(event, code) { + return event.button == buttonMap[code]; + }; + + } else if (Prototype.Browser.WebKit) { + isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + + } else { + isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + return { + isLeftClick: function(event) { return isButton(event, 0) }, + isMiddleClick: function(event) { return isButton(event, 1) }, + isRightClick: function(event) { return isButton(event, 2) }, + + element: function(event) { + var node = Event.extend(event).target; + return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); + }, + + findElement: function(event, expression) { + var element = Event.element(event); + if (!expression) return element; + var elements = [element].concat(element.ancestors()); + return Selector.findElement(elements, expression, 0); + }, + + pointer: function(event) { + return { + x: event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)), + y: event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)) + }; + }, + + pointerX: function(event) { return Event.pointer(event).x }, + pointerY: function(event) { return Event.pointer(event).y }, + + stop: function(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + event.stopped = true; + } + }; +})(); + +Event.extend = (function() { + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return "[object Event]" } + }); + + return function(event) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + Object.extend(event, { + target: event.srcElement, + relatedTarget: Event.relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + return Object.extend(event, methods); + }; + + } else { + Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; + Object.extend(Event.prototype, methods); + return Prototype.K; + } +})(); + +Object.extend(Event, (function() { + var cache = Event.cache; + + function getEventID(element) { + if (element._prototypeEventID) return element._prototypeEventID[0]; + arguments.callee.id = arguments.callee.id || 1; + return element._prototypeEventID = [++arguments.callee.id]; + } + + function getDOMEventName(eventName) { + if (eventName && eventName.include(':')) return "dataavailable"; + return eventName; + } + + function getCacheForID(id) { + return cache[id] = cache[id] || { }; + } + + function getWrappersForEventName(id, eventName) { + var c = getCacheForID(id); + return c[eventName] = c[eventName] || []; + } + + function createWrapper(element, eventName, handler) { + var id = getEventID(element); + var c = getWrappersForEventName(id, eventName); + if (c.pluck("handler").include(handler)) return false; + + var wrapper = function(event) { + if (!Event || !Event.extend || + (event.eventName && event.eventName != eventName)) + return false; + + Event.extend(event); + handler.call(element, event); + }; + + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + + function findWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + return c.find(function(wrapper) { return wrapper.handler == handler }); + } + + function destroyWrapper(id, eventName, handler) { + var c = getCacheForID(id); + if (!c[eventName]) return false; + c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + } + + function destroyCache() { + for (var id in cache) + for (var eventName in cache[id]) + cache[id][eventName] = null; + } + + if (window.attachEvent) { + window.attachEvent("onunload", destroyCache); + } + + return { + observe: function(element, eventName, handler) { + element = $(element); + var name = getDOMEventName(eventName); + + var wrapper = createWrapper(element, eventName, handler); + if (!wrapper) return element; + + if (element.addEventListener) { + element.addEventListener(name, wrapper, false); + } else { + element.attachEvent("on" + name, wrapper); + } + + return element; + }, + + stopObserving: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + if (!handler && eventName) { + getWrappersForEventName(id, eventName).each(function(wrapper) { + element.stopObserving(eventName, wrapper.handler); + }); + return element; + + } else if (!eventName) { + Object.keys(getCacheForID(id)).each(function(eventName) { + element.stopObserving(eventName); + }); + return element; + } + + var wrapper = findWrapper(id, eventName, handler); + if (!wrapper) return element; + + if (element.removeEventListener) { + element.removeEventListener(name, wrapper, false); + } else { + element.detachEvent("on" + name, wrapper); + } + + destroyWrapper(id, eventName, handler); + + return element; + }, + + fire: function(element, eventName, memo) { + element = $(element); + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent("HTMLEvents"); + event.initEvent("dataavailable", true, true); + } else { + event = document.createEventObject(); + event.eventType = "ondataavailable"; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent(event.eventType, event); + } + + return Event.extend(event); + } + }; +})()); + +Object.extend(Event, Event.Methods); + +Element.addMethods({ + fire: Event.fire, + observe: Event.observe, + stopObserving: Event.stopObserving +}); + +Object.extend(document, { + fire: Element.Methods.fire.methodize(), + observe: Element.Methods.observe.methodize(), + stopObserving: Element.Methods.stopObserving.methodize(), + loaded: false +}); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards and John Resig. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearInterval(timer); + document.fire("dom:loaded"); + document.loaded = true; + } + + if (document.addEventListener) { + if (Prototype.Browser.WebKit) { + timer = window.setInterval(function() { + if (/loaded|complete/.test(document.readyState)) + fireContentLoadedEvent(); + }, 0); + + Event.observe(window, "load", fireContentLoadedEvent); + + } else { + document.addEventListener("DOMContentLoaded", + fireContentLoadedEvent, false); + } + + } else { + document.write(" + + +

+ +

+ csstidy version; ?>) +

+ : English Deutsch French Chinese

+

+ +

+ +
+
+
+ + + size="35" />
+ +
+
+
+
+ +
+
+ + + get_cfg('preserve_css')) echo 'checked="checked"'; ?> /> +
+ + + get_cfg('sort_selectors')) echo 'checked="checked"'; ?> /> +
+ + + get_cfg('sort_properties')) echo 'checked="checked"'; ?> /> +
+ + + +
+ + +
+ + + get_cfg('compress_colors')) echo 'checked="checked"';?> /> +
+ + + get_cfg('compress_font-weight')) echo 'checked="checked"';?> /> +
+ + + get_cfg('lowercase_s')) echo 'checked="checked"'; ?> /> +
+ + +
+ get_cfg('case_properties') == 0) echo 'checked="checked"'; ?> /> + + get_cfg('case_properties') == 1) echo 'checked="checked"'; ?> /> + + get_cfg('case_properties') == 2) echo 'checked="checked"'; ?> /> +
+ + get_cfg('remove_bslash')) echo 'checked="checked"'; ?> /> +
+ + + get_cfg('remove_last_;')) echo 'checked="checked"'; ?> /> +
+ + + get_cfg('discard_invalid_properties')) echo 'checked="checked"'; ?> /> + +
+ + + get_cfg('timestamp')) echo 'checked="checked"'; ?> /> +
+ + + /> +
+ +
+ +
+
+
+ load_template($_REQUEST['custom'],false); + } + break; + + case 3: + $css->load_template('highest_compression'); + break; + + case 2: + $css->load_template('high_compression'); + break; + + case 0: + $css->load_template('low_compression'); + break; + } + } + + if($url) + { + if(substr($_REQUEST['url'],0,7) != 'http://') + { + $_REQUEST['url'] = 'http://'.$_REQUEST['url']; + } + $result = $css->parse_from_url($_REQUEST['url'],0); + } + elseif(isset($_REQUEST['css_text']) && strlen($_REQUEST['css_text'])>5) + { + $result = $css->parse($_REQUEST['css_text']); + } + + if($result) + { + $ratio = $css->print->get_ratio(); + $diff = $css->print->get_diff(); + if(isset($_REQUEST['file_output'])) + { + $filename = md5(mt_rand().time().mt_rand()); + $handle = fopen('temp/'.$filename.'.css','w'); + if($handle) { + if(fwrite($handle,$css->print->plain())) + { + $file_ok = true; + } + } + fclose($handle); + } + if($ratio>0) $ratio = ''.$ratio.'% + ('.$diff.' Bytes)'; else $ratio = ''.$ratio.'% ('.$diff.' Bytes)'; + if(count($css->log) > 0): ?> +
Messages +
log as $line => $array) + { + echo '
'.$line.'
'; + for($i = 0; $i < count($array); $i++) + { + echo '
'.$array[$i]['m'].'
'; + } + } + ?>
+
+ '.$lang[$l][37].': '.$css->print->size('input').'KB, '.$lang[$l][38].':'.$css->print->size('output').'KB, '.$lang[$l][36].': '.$ratio; + if($file_ok) + { + echo ' - Download'; + } + echo ''; + echo '
';
+        echo $css->print->formatted();
+        echo '
'; + echo ''; + } + elseif(isset($_REQUEST['css_text']) || isset($_REQUEST['url'])) { + echo '

'.$lang[$l][28].'

'; + } + ?> +

+ For bugs and suggestions feel free to contact me. +

+ + \ No newline at end of file diff --git a/instmess/lib/csstidy-1.2/cssparse.css b/instmess/lib/csstidy-1.2/cssparse.css new file mode 100644 index 0000000..bd8cc13 --- /dev/null +++ b/instmess/lib/csstidy-1.2/cssparse.css @@ -0,0 +1,137 @@ +body { +font:0.8em Verdana,Helvetica,sans-serif; +background:#F8F8F6; +} + +code { +font-size:1.2em; +} + +div#rightcol { +padding-left:32em; +} + +fieldset { +display:block; +margin:0.5em 0; +padding:1em; +border:solid #7284AB 2px; +} + +h1 { +font-size:2em; +} + +small { +font-size:0.7em; +} + +fieldset#field_input { +float:left; +margin:0 0.5em 1em 0; +} + +fieldset#options,fieldset#code_layout { +width:31em; +} + +input#submit { +clear:both; +display:block; +margin:1em; +} + +select { +margin:2px 0 0; +} + +label.block { +display:block; +} + +legend { +background:#c4E1C3; +padding:2px 4px; +border:dashed 1px; +} + +span.at { +color:darkblue; +} + +span.format { +color:gray; +} + +span.property { +color:green; +} + +span.selector { +color:blue; +} + +span.value { +color:red; +} + +span.comment { +color:orange; +} + +textarea#css_text { +width:27em; +height:370px; +display:block; +margin-right:1em; +} + +.help { +cursor:help; +} + +p.important { +border:solid 1px red; +font-weight:bold; +padding:1em; +background:white; +} + +p { +margin:1em 0; +} + +dl { +padding-left:0.5em; +} + +dt { +font-weight:bold; +margin:0; +float:left; +clear:both; +height:1.5em; +} + +dd { +margin:0 0 0 4em; +height:1.5em; +} + +fieldset#messages { +background:white; +padding:0 0 0 1em; +} + +fieldset#messages div { +height:10em; +overflow:auto; +} + +dd.Warning { +color:orange; +} + +dd.Information { +color:green; +} \ No newline at end of file diff --git a/instmess/lib/csstidy-1.2/data.inc.php b/instmess/lib/csstidy-1.2/data.inc.php new file mode 100644 index 0000000..1e67084 --- /dev/null +++ b/instmess/lib/csstidy-1.2/data.inc.php @@ -0,0 +1,469 @@ +?[]^`|~'; + +/** + * All CSS units (CSS 3 units included) + * + * @see compress_numbers() + * @global array $GLOBALS['csstidy']['units'] + * @version 1.0 + */ +$GLOBALS['csstidy']['units'] = array('in','cm','mm','pt','pc','px','rem','em','%','ex','gd','vw','vh','vm','deg','grad','rad','ms','s','khz','hz'); + +/** + * Available at-rules + * + * @global array $GLOBALS['csstidy']['at_rules'] + * @version 1.0 + */ +$GLOBALS['csstidy']['at_rules'] = array('page' => 'is','font-face' => 'is','charset' => 'iv', 'import' => 'iv','namespace' => 'iv','media' => 'at'); + + /** + * Properties that allow as value + * + * @todo CSS3 properties + * @see compress_numbers(); + * @global array $GLOBALS['csstidy']['number_values'] + * @version 1.2 + */ +$GLOBALS['csstidy']['number_values'] = array('line-height','pitch-range','richness','speech-rate','stress','volume','font','font-weight','z-index','counter-increment','counter-reset','orphans','widows'); + +/** + * Properties that allow as value + * + * @todo CSS3 properties + * @see compress_numbers(); + * @global array $GLOBALS['csstidy']['color_values'] + * @version 1.0 + */ +$GLOBALS['csstidy']['color_values'] = array(); +$GLOBALS['csstidy']['color_values'][] = 'background-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-top-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-right-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-bottom-color'; +$GLOBALS['csstidy']['color_values'][] = 'border-left-color'; +$GLOBALS['csstidy']['color_values'][] = 'color'; +$GLOBALS['csstidy']['color_values'][] = 'outline-color'; + + +/** + * Default values for the background properties + * + * @todo Possibly property names will change during CSS3 development + * @global array $GLOBALS['csstidy']['background_prop_default'] + * @see dissolve_short_bg() + * @see merge_bg() + * @version 1.0 + */ +$GLOBALS['csstidy']['background_prop_default'] = array(); +$GLOBALS['csstidy']['background_prop_default']['background-image'] = 'none'; +$GLOBALS['csstidy']['background_prop_default']['background-size'] = 'auto'; +$GLOBALS['csstidy']['background_prop_default']['background-repeat'] = 'repeat'; +$GLOBALS['csstidy']['background_prop_default']['background-position'] = '0 0'; +$GLOBALS['csstidy']['background_prop_default']['background-attachment'] = 'scroll'; +$GLOBALS['csstidy']['background_prop_default']['background-clip'] = 'border'; +$GLOBALS['csstidy']['background_prop_default']['background-origin'] = 'padding'; +$GLOBALS['csstidy']['background_prop_default']['background-color'] = 'transparent'; + +/** + * A list of non-W3C color names which get replaced by their hex-codes + * + * @global array $GLOBALS['csstidy']['replace_colors'] + * @see cut_color() + * @version 1.0 + */ +$GLOBALS['csstidy']['replace_colors'] = array(); +$GLOBALS['csstidy']['replace_colors']['aliceblue'] = '#F0F8FF'; +$GLOBALS['csstidy']['replace_colors']['antiquewhite'] = '#FAEBD7'; +$GLOBALS['csstidy']['replace_colors']['aquamarine'] = '#7FFFD4'; +$GLOBALS['csstidy']['replace_colors']['azure'] = '#F0FFFF'; +$GLOBALS['csstidy']['replace_colors']['beige'] = '#F5F5DC'; +$GLOBALS['csstidy']['replace_colors']['bisque'] = '#FFE4C4'; +$GLOBALS['csstidy']['replace_colors']['blanchedalmond'] = '#FFEBCD'; +$GLOBALS['csstidy']['replace_colors']['blueviolet'] = '#8A2BE2'; +$GLOBALS['csstidy']['replace_colors']['brown'] = '#A52A2A'; +$GLOBALS['csstidy']['replace_colors']['burlywood'] = '#DEB887'; +$GLOBALS['csstidy']['replace_colors']['cadetblue'] = '#5F9EA0'; +$GLOBALS['csstidy']['replace_colors']['chartreuse'] = '#7FFF00'; +$GLOBALS['csstidy']['replace_colors']['chocolate'] = '#D2691E'; +$GLOBALS['csstidy']['replace_colors']['coral'] = '#FF7F50'; +$GLOBALS['csstidy']['replace_colors']['cornflowerblue'] = '#6495ED'; +$GLOBALS['csstidy']['replace_colors']['cornsilk'] = '#FFF8DC'; +$GLOBALS['csstidy']['replace_colors']['crimson'] = '#DC143C'; +$GLOBALS['csstidy']['replace_colors']['cyan'] = '#00FFFF'; +$GLOBALS['csstidy']['replace_colors']['darkblue'] = '#00008B'; +$GLOBALS['csstidy']['replace_colors']['darkcyan'] = '#008B8B'; +$GLOBALS['csstidy']['replace_colors']['darkgoldenrod'] = '#B8860B'; +$GLOBALS['csstidy']['replace_colors']['darkgray'] = '#A9A9A9'; +$GLOBALS['csstidy']['replace_colors']['darkgreen'] = '#006400'; +$GLOBALS['csstidy']['replace_colors']['darkkhaki'] = '#BDB76B'; +$GLOBALS['csstidy']['replace_colors']['darkmagenta'] = '#8B008B'; +$GLOBALS['csstidy']['replace_colors']['darkolivegreen'] = '#556B2F'; +$GLOBALS['csstidy']['replace_colors']['darkorange'] = '#FF8C00'; +$GLOBALS['csstidy']['replace_colors']['darkorchid'] = '#9932CC'; +$GLOBALS['csstidy']['replace_colors']['darkred'] = '#8B0000'; +$GLOBALS['csstidy']['replace_colors']['darksalmon'] = '#E9967A'; +$GLOBALS['csstidy']['replace_colors']['darkseagreen'] = '#8FBC8F'; +$GLOBALS['csstidy']['replace_colors']['darkslateblue'] = '#483D8B'; +$GLOBALS['csstidy']['replace_colors']['darkslategray'] = '#2F4F4F'; +$GLOBALS['csstidy']['replace_colors']['darkturquoise'] = '#00CED1'; +$GLOBALS['csstidy']['replace_colors']['darkviolet'] = '#9400D3'; +$GLOBALS['csstidy']['replace_colors']['deeppink'] = '#FF1493'; +$GLOBALS['csstidy']['replace_colors']['deepskyblue'] = '#00BFFF'; +$GLOBALS['csstidy']['replace_colors']['dimgray'] = '#696969'; +$GLOBALS['csstidy']['replace_colors']['dodgerblue'] = '#1E90FF'; +$GLOBALS['csstidy']['replace_colors']['feldspar'] = '#D19275'; +$GLOBALS['csstidy']['replace_colors']['firebrick'] = '#B22222'; +$GLOBALS['csstidy']['replace_colors']['floralwhite'] = '#FFFAF0'; +$GLOBALS['csstidy']['replace_colors']['forestgreen'] = '#228B22'; +$GLOBALS['csstidy']['replace_colors']['gainsboro'] = '#DCDCDC'; +$GLOBALS['csstidy']['replace_colors']['ghostwhite'] = '#F8F8FF'; +$GLOBALS['csstidy']['replace_colors']['gold'] = '#FFD700'; +$GLOBALS['csstidy']['replace_colors']['goldenrod'] = '#DAA520'; +$GLOBALS['csstidy']['replace_colors']['greenyellow'] = '#ADFF2F'; +$GLOBALS['csstidy']['replace_colors']['honeydew'] = '#F0FFF0'; +$GLOBALS['csstidy']['replace_colors']['hotpink'] = '#FF69B4'; +$GLOBALS['csstidy']['replace_colors']['indianred'] = '#CD5C5C'; +$GLOBALS['csstidy']['replace_colors']['indigo'] = '#4B0082'; +$GLOBALS['csstidy']['replace_colors']['ivory'] = '#FFFFF0'; +$GLOBALS['csstidy']['replace_colors']['khaki'] = '#F0E68C'; +$GLOBALS['csstidy']['replace_colors']['lavender'] = '#E6E6FA'; +$GLOBALS['csstidy']['replace_colors']['lavenderblush'] = '#FFF0F5'; +$GLOBALS['csstidy']['replace_colors']['lawngreen'] = '#7CFC00'; +$GLOBALS['csstidy']['replace_colors']['lemonchiffon'] = '#FFFACD'; +$GLOBALS['csstidy']['replace_colors']['lightblue'] = '#ADD8E6'; +$GLOBALS['csstidy']['replace_colors']['lightcoral'] = '#F08080'; +$GLOBALS['csstidy']['replace_colors']['lightcyan'] = '#E0FFFF'; +$GLOBALS['csstidy']['replace_colors']['lightgoldenrodyellow'] = '#FAFAD2'; +$GLOBALS['csstidy']['replace_colors']['lightgrey'] = '#D3D3D3'; +$GLOBALS['csstidy']['replace_colors']['lightgreen'] = '#90EE90'; +$GLOBALS['csstidy']['replace_colors']['lightpink'] = '#FFB6C1'; +$GLOBALS['csstidy']['replace_colors']['lightsalmon'] = '#FFA07A'; +$GLOBALS['csstidy']['replace_colors']['lightseagreen'] = '#20B2AA'; +$GLOBALS['csstidy']['replace_colors']['lightskyblue'] = '#87CEFA'; +$GLOBALS['csstidy']['replace_colors']['lightslateblue'] = '#8470FF'; +$GLOBALS['csstidy']['replace_colors']['lightslategray'] = '#778899'; +$GLOBALS['csstidy']['replace_colors']['lightsteelblue'] = '#B0C4DE'; +$GLOBALS['csstidy']['replace_colors']['lightyellow'] = '#FFFFE0'; +$GLOBALS['csstidy']['replace_colors']['limegreen'] = '#32CD32'; +$GLOBALS['csstidy']['replace_colors']['linen'] = '#FAF0E6'; +$GLOBALS['csstidy']['replace_colors']['magenta'] = '#FF00FF'; +$GLOBALS['csstidy']['replace_colors']['mediumaquamarine'] = '#66CDAA'; +$GLOBALS['csstidy']['replace_colors']['mediumblue'] = '#0000CD'; +$GLOBALS['csstidy']['replace_colors']['mediumorchid'] = '#BA55D3'; +$GLOBALS['csstidy']['replace_colors']['mediumpurple'] = '#9370D8'; +$GLOBALS['csstidy']['replace_colors']['mediumseagreen'] = '#3CB371'; +$GLOBALS['csstidy']['replace_colors']['mediumslateblue'] = '#7B68EE'; +$GLOBALS['csstidy']['replace_colors']['mediumspringgreen'] = '#00FA9A'; +$GLOBALS['csstidy']['replace_colors']['mediumturquoise'] = '#48D1CC'; +$GLOBALS['csstidy']['replace_colors']['mediumvioletred'] = '#C71585'; +$GLOBALS['csstidy']['replace_colors']['midnightblue'] = '#191970'; +$GLOBALS['csstidy']['replace_colors']['mintcream'] = '#F5FFFA'; +$GLOBALS['csstidy']['replace_colors']['mistyrose'] = '#FFE4E1'; +$GLOBALS['csstidy']['replace_colors']['moccasin'] = '#FFE4B5'; +$GLOBALS['csstidy']['replace_colors']['navajowhite'] = '#FFDEAD'; +$GLOBALS['csstidy']['replace_colors']['oldlace'] = '#FDF5E6'; +$GLOBALS['csstidy']['replace_colors']['olivedrab'] = '#6B8E23'; +$GLOBALS['csstidy']['replace_colors']['orangered'] = '#FF4500'; +$GLOBALS['csstidy']['replace_colors']['orchid'] = '#DA70D6'; +$GLOBALS['csstidy']['replace_colors']['palegoldenrod'] = '#EEE8AA'; +$GLOBALS['csstidy']['replace_colors']['palegreen'] = '#98FB98'; +$GLOBALS['csstidy']['replace_colors']['paleturquoise'] = '#AFEEEE'; +$GLOBALS['csstidy']['replace_colors']['palevioletred'] = '#D87093'; +$GLOBALS['csstidy']['replace_colors']['papayawhip'] = '#FFEFD5'; +$GLOBALS['csstidy']['replace_colors']['peachpuff'] = '#FFDAB9'; +$GLOBALS['csstidy']['replace_colors']['peru'] = '#CD853F'; +$GLOBALS['csstidy']['replace_colors']['pink'] = '#FFC0CB'; +$GLOBALS['csstidy']['replace_colors']['plum'] = '#DDA0DD'; +$GLOBALS['csstidy']['replace_colors']['powderblue'] = '#B0E0E6'; +$GLOBALS['csstidy']['replace_colors']['rosybrown'] = '#BC8F8F'; +$GLOBALS['csstidy']['replace_colors']['royalblue'] = '#4169E1'; +$GLOBALS['csstidy']['replace_colors']['saddlebrown'] = '#8B4513'; +$GLOBALS['csstidy']['replace_colors']['salmon'] = '#FA8072'; +$GLOBALS['csstidy']['replace_colors']['sandybrown'] = '#F4A460'; +$GLOBALS['csstidy']['replace_colors']['seagreen'] = '#2E8B57'; +$GLOBALS['csstidy']['replace_colors']['seashell'] = '#FFF5EE'; +$GLOBALS['csstidy']['replace_colors']['sienna'] = '#A0522D'; +$GLOBALS['csstidy']['replace_colors']['skyblue'] = '#87CEEB'; +$GLOBALS['csstidy']['replace_colors']['slateblue'] = '#6A5ACD'; +$GLOBALS['csstidy']['replace_colors']['slategray'] = '#708090'; +$GLOBALS['csstidy']['replace_colors']['snow'] = '#FFFAFA'; +$GLOBALS['csstidy']['replace_colors']['springgreen'] = '#00FF7F'; +$GLOBALS['csstidy']['replace_colors']['steelblue'] = '#4682B4'; +$GLOBALS['csstidy']['replace_colors']['tan'] = '#D2B48C'; +$GLOBALS['csstidy']['replace_colors']['thistle'] = '#D8BFD8'; +$GLOBALS['csstidy']['replace_colors']['tomato'] = '#FF6347'; +$GLOBALS['csstidy']['replace_colors']['turquoise'] = '#40E0D0'; +$GLOBALS['csstidy']['replace_colors']['violet'] = '#EE82EE'; +$GLOBALS['csstidy']['replace_colors']['violetred'] = '#D02090'; +$GLOBALS['csstidy']['replace_colors']['wheat'] = '#F5DEB3'; +$GLOBALS['csstidy']['replace_colors']['whitesmoke'] = '#F5F5F5'; +$GLOBALS['csstidy']['replace_colors']['yellowgreen'] = '#9ACD32'; + + +/** + * A list of all shorthand properties that are devided into four properties and/or have four subvalues + * + * @global array $GLOBALS['csstidy']['shorthands'] + * @todo Are there new ones in CSS3? + * @see dissolve_4value_shorthands() + * @see merge_4value_shorthands() + * @version 1.0 + */ +$GLOBALS['csstidy']['shorthands'] = array(); +$GLOBALS['csstidy']['shorthands']['border-color'] = array('border-top-color','border-right-color','border-bottom-color','border-left-color'); +$GLOBALS['csstidy']['shorthands']['border-style'] = array('border-top-style','border-right-style','border-bottom-style','border-left-style'); +$GLOBALS['csstidy']['shorthands']['border-width'] = array('border-top-width','border-right-width','border-bottom-width','border-left-width'); +$GLOBALS['csstidy']['shorthands']['margin'] = array('margin-top','margin-right','margin-bottom','margin-left'); +$GLOBALS['csstidy']['shorthands']['padding'] = array('padding-top','padding-right','padding-bottom','padding-left'); +$GLOBALS['csstidy']['shorthands']['-moz-border-radius'] = 0; + +/** + * All CSS Properties. Needed for csstidy::property_is_next() + * + * @global array $GLOBALS['csstidy']['all_properties'] + * @todo Add CSS3 properties + * @version 1.0 + * @see csstidy::property_is_next() + */ +$GLOBALS['csstidy']['all_properties'] = array(); +$GLOBALS['csstidy']['all_properties']['background'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['background-color'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['background-image'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['background-repeat'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['background-attachment'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['background-position'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-top'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-right'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-left'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-color'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-top-color'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-bottom-color'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-left-color'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-right-color'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-style'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-top-style'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-right-style'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-left-style'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-bottom-style'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-width'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-top-width'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-right-width'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-left-width'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-bottom-width'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-collapse'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['border-spacing'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['bottom'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['caption-side'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['content'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['clear'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['clip'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['color'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['counter-reset'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['counter-increment'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['cursor'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['empty-cells'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['display'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['direction'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['float'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['font'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['font-family'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['font-style'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['font-variant'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['font-weight'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['font-stretch'] = 'CSS2.0'; +$GLOBALS['csstidy']['all_properties']['font-size-adjust'] = 'CSS2.0'; +$GLOBALS['csstidy']['all_properties']['font-size'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['height'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['left'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['line-height'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['list-style'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['list-style-type'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['list-style-image'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['list-style-position'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['margin'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['margin-top'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['margin-right'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['margin-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['margin-left'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['marks'] = 'CSS1.0,CSS2.0'; +$GLOBALS['csstidy']['all_properties']['marker-offset'] = 'CSS2.0'; +$GLOBALS['csstidy']['all_properties']['max-height'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['max-width'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['min-height'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['min-width'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['overflow'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['orphans'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['outline'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['outline-width'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['outline-style'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['outline-color'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['padding'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['padding-top'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['padding-right'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['padding-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['padding-left'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['page-break-before'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['page-break-after'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['page-break-inside'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['page'] = 'CSS2.0'; +$GLOBALS['csstidy']['all_properties']['position'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['quotes'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['right'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['size'] = 'CSS1.0,CSS2.0'; +$GLOBALS['csstidy']['all_properties']['speak-header'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['table-layout'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['top'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['text-indent'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['text-align'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['text-decoration'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['text-shadow'] = 'CSS2.0'; +$GLOBALS['csstidy']['all_properties']['letter-spacing'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['word-spacing'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['text-transform'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['white-space'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['unicode-bidi'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['vertical-align'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['visibility'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['width'] = 'CSS1.0,CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['widows'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['z-index'] = 'CSS1.0,CSS2.0,CSS2.1'; +/* Speech */ +$GLOBALS['csstidy']['all_properties']['volume'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['speak'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['pause'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['pause-before'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['pause-after'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['cue'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['cue-before'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['cue-after'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['play-during'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['azimuth'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['elevation'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['speech-rate'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['voice-family'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['pitch'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['pitch-range'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['stress'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['richness'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['speak-punctuation'] = 'CSS2.0,CSS2.1'; +$GLOBALS['csstidy']['all_properties']['speak-numeral'] = 'CSS2.0,CSS2.1'; + +/** + * An array containing all predefined templates. + * + * @global array $GLOBALS['csstidy']['predefined_templates'] + * @version 1.0 + * @see csstidy::load_template() + */ +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //string before @rule +$GLOBALS['csstidy']['predefined_templates']['default'][] = ' {'."\n"; //bracket after @-rule +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //string before selector +$GLOBALS['csstidy']['predefined_templates']['default'][] = ' {'."\n"; //bracket after selector +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //string before property +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //string after property+before value +$GLOBALS['csstidy']['predefined_templates']['default'][] = ';'."\n"; //string after value +$GLOBALS['csstidy']['predefined_templates']['default'][] = '}'; //closing bracket - selector +$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n\n"; //space between blocks {...} +$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n".'}'. "\n\n"; //closing bracket @-rule +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //indent in @-rule +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; // before comment +$GLOBALS['csstidy']['predefined_templates']['default'][] = ''."\n"; // after comment +$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n"; // after last line @-rule + +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ' {'."\n"; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '{'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ';'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '}'; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n"; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n". '}'."\n".''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; // before comment +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = ''; // after comment +$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n"; + +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '{'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '{'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ';'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '}'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '}'; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; // before comment +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; // after comment +$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = ''; + +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ' {'."\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ''."\n".'{'."\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ' '; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ''; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ';'."\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '}'; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n".'}'."\n\n"; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ' '; +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ''; // before comment +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ''."\n"; // after comment +$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n"; + +?> \ No newline at end of file diff --git a/instmess/lib/csstidy-1.2/lang.inc.php b/instmess/lib/csstidy-1.2/lang.inc.php new file mode 100644 index 0000000..522447f --- /dev/null +++ b/instmess/lib/csstidy-1.2/lang.inc.php @@ -0,0 +1,238 @@ +no validator which points out errors in your CSS code. To make sure that your code is valid, use the W3C Validator.'; +$lang['de'][6] = 'Der CSS Code sollte wohlgeformt sein. Der CSS Code wird nicht auf Gültigkeit überprüft. Um sicherzugehen dass dein Code valide ist, benutze den W3C Validierungsservice.'; +$lang['en'][7] = 'all comments are removed'; +$lang['de'][7] = 'alle Kommentare werden entfernt'; +$lang['en'][8] = 'CSS Input:'; +$lang['de'][8] = 'CSS Eingabe:'; +$lang['en'][9] = 'CSS-Code:'; +$lang['de'][9] = 'CSS-Code:'; +$lang['en'][10] = 'CSS from URL:'; +$lang['de'][10] = 'CSS von URL:'; +$lang['en'][11] = 'Code Layout:'; +$lang['de'][11] = 'Code Layout:'; +$lang['en'][12] = 'Compression (code layout):'; +$lang['de'][12] = 'Komprimierung (Code Layout):'; +$lang['en'][13] = 'Highest (no readability, smallest size)'; +$lang['de'][13] = 'Höchste (keine Lesbarkeit, niedrigste Größe)'; +$lang['en'][14] = 'High (moderate readability, smaller size)'; +$lang['de'][14] = 'Hoch (mittelmäßige Lesbarkeit, geringe Größe)'; +$lang['en'][15] = 'Standard (balance between readability and size)'; +$lang['de'][15] = 'Standard (Kompromiss zwischen Lesbarkeit und Größe)'; +$lang['en'][16] = 'Low (higher readability)'; +$lang['de'][16] = 'Niedrig (höhere Lesbarkeit)'; +$lang['en'][17] = 'Custom (enter below)'; +$lang['de'][17] = 'Benutzerdefiniert (unten eingeben)'; +$lang['en'][18] = 'Custom template'; +$lang['de'][18] = 'Benutzerdefinierte Vorlage'; +$lang['en'][19] = 'Options'; +$lang['de'][19] = 'Optionen'; +$lang['en'][20] = 'Sort Selectors (caution)'; +$lang['de'][20] = 'Selektoren sortieren (Vorsicht)'; +$lang['en'][21] = 'Sort Properties'; +$lang['de'][21] = 'Eigenschaften sortieren'; +$lang['en'][22] = 'Regroup selectors'; +$lang['de'][22] = 'Selektoren umgruppieren'; +$lang['en'][23] = 'Optimise shorthands'; +$lang['de'][23] = 'Shorthands optimieren'; +$lang['en'][24] = 'Compress colors'; +$lang['de'][24] = 'Farben komprimieren'; +$lang['en'][25] = 'Lowercase selectors'; +$lang['de'][25] = 'Selektoren in Kleinbuchstaben'; +$lang['en'][26] = 'Case for properties:'; +$lang['de'][26] = 'Groß-/Kleinschreibung für Eigenschaften'; +$lang['en'][27] = 'Lowercase'; +$lang['de'][27] = 'Kleinbuchstaben'; +$lang['en'][28] = 'No or invalid CSS input or wrong URL!'; +$lang['de'][28] = 'Keine oder ungültige CSS Eingabe oder falsche URL!'; +$lang['en'][29] = 'Uppercase'; +$lang['de'][29] = 'Großbuchstaben'; +$lang['en'][30] = 'lowercase elementnames needed for XHTML'; +$lang['de'][30] = 'kleingeschriebene Elementnamen benötigt für XHTML'; +$lang['en'][31] = 'Remove unnecessary backslashes'; +$lang['de'][31] = 'Unnötige Backslashes entfernen'; +$lang['en'][32] = 'convert !important-hack'; +$lang['de'][32] = '!important-Hack konvertieren'; +$lang['en'][33] = 'Output as file'; +$lang['de'][33] = 'Als Datei ausgeben'; +$lang['en'][34] = 'Bigger compression because of smaller newlines (copy & paste doesn\'t work)'; +$lang['de'][34] = 'Größere Komprimierung augrund von kleineren Neuezeile-Zeichen'; +$lang['en'][35] = 'Process CSS'; +$lang['de'][35] = 'CSS verarbeiten'; +$lang['en'][36] = 'Compression Ratio'; +$lang['de'][36] = 'Komprimierungsrate'; +$lang['en'][37] = 'Input'; +$lang['de'][37] = 'Eingabe'; +$lang['en'][38] = 'Output'; +$lang['de'][38] = 'Ausgabe'; +$lang['en'][39] = 'Language'; +$lang['de'][39] = 'Sprache'; +$lang['en'][41] = 'Attention: This may change the behaviour of your CSS Code!'; +$lang['de'][41] = 'Achtung: Dies könnte das Verhalten ihres CSS-Codes verändern!'; +$lang['en'][42] = 'Remove last ;'; +$lang['de'][42] = 'Letztes ; entfernen'; +$lang['en'][43] = 'Discard invalid properties'; +$lang['de'][43] = 'Ungültige Eigenschaften entfernen'; +$lang['en'][44] = 'Only safe optimisations'; +$lang['de'][44] = 'Nur sichere Optimierungen'; +$lang['en'][45] = 'Compress font-weight'; +$lang['de'][45] = 'font-weight komprimieren'; +$lang['en'][46] = 'Save comments'; +$lang['de'][46] = 'Kommentare beibehalten'; +$lang['en'][47] = 'Do not change anything'; +$lang['en'][48] = 'Only seperate selectors (split at ,)'; +$lang['en'][49] = 'Merge selectors with the same properties (fast)'; +$lang['en'][50] = 'Merge selectors intelligently (slow)'; +$lang['de'][47] = 'Nichts ändern'; +$lang['de'][48] = 'Selektoren nur trennen (am Komma)'; +$lang['de'][49] = 'Selektoren mit gleichen Eigenschaften zusammenfassen (schnell)'; +$lang['de'][50] = 'Selektoren intelligent zusammenfassen (langsam!)'; +$lang['en'][51] = 'Preserve CSS'; +$lang['de'][51] = 'CSS erhalten'; +$lang['en'][52] = 'Save comments, hacks, etc. Most optimisations can *not* be applied if this is enabled.'; +$lang['de'][52] = 'Kommentare, Hacks, etc. speichern. Viele Optimierungen sind dann aber nicht mehr möglich.'; +$lang['en'][53] = 'None'; +$lang['de'][53] = 'Keine'; +$lang['en'][54] = 'Don\'t optimise'; +$lang['de'][54] = 'Nicht optimieren'; +$lang['en'][55] = 'Safe optimisations'; +$lang['de'][55] = 'Sichere Optimierungen'; +$lang['en'][56] = 'All optimisations'; +$lang['de'][56] = 'Alle Optimierungen'; +$lang['en'][57] = 'Add timestamp'; +$lang['de'][57] = 'Zeitstempel hinzufügen'; + +$lang['fr'][0] = 'CSS Formatteur et Optimiseur (basé sur CSSTidy '; +$lang['fr'][1] = 'CSS Formatteur et Optimiseur'; +$lang['fr'][2] = '(basé sur '; +$lang['fr'][3] = '(Version Text)'; +$lang['fr'][4] = 'notes Importantes:'; +$lang['fr'][5] = 'les mêmes sélecteurs et les propriétés sont automatiquement fusionnés'; +$lang['fr'][6] = 'votre code doit être Valide. Ce n\'est pas un validateur qui signale des erreurs dans votre code de CSS. Assurez-vous que votre code est correct en utilisant le le validateur : W3C Validator'; +$lang['fr'][7] = 'tous les commentaires sont enlevés'; +$lang['fr'][8] = 'Champ CSS:'; +$lang['fr'][9] = 'CSS-Code:'; +$lang['fr'][10] = 'CSS en provenance d\'une URL:
'; +$lang['fr'][11] = 'Mise en page du code:'; +$lang['fr'][12] = 'Compression (Mise en page du code):'; +$lang['fr'][13] = 'Le plus compact (aucune lisibilité, plus petite taille)'; +$lang['fr'][14] = 'Trés compact (lisibilité modérée, plus petite taille)'; +$lang['fr'][15] = 'Normale (équilibre entre la lisibilité et la taille)'; +$lang['fr'][16] = 'Peu compact (une lisibilité plus élevée)'; +$lang['fr'][17] = 'Sur mesure (entrer ci-dessous)'; +$lang['fr'][18] = 'Sur mesure Gabarit'; +$lang['fr'][19] = 'Options'; +$lang['fr'][20] = 'Trier les sélecteurs (attention)'; +$lang['fr'][21] = 'Trier les propriétés'; +$lang['fr'][22] = 'Fusionner les sélecteurs'; +$lang['fr'][23] = 'Optimise shorthands'; +$lang['fr'][24] = 'Compresser les couleurs'; +$lang['fr'][25] = 'Sélecteurs en minuscules'; +$lang['fr'][26] = 'Cases (Minuscules & Majuscule) pour les propriétés:'; +$lang['fr'][27] = 'Minuscules'; +$lang['fr'][28] = 'CSS non valide ou URL incorrecte!'; +$lang['fr'][29] = 'Majuscule'; +$lang['fr'][30] = 'les noms des éléments en minuscules (indispensables pour XHTML)'; +$lang['fr'][31] = 'enlever les antislashs inutiles'; +$lang['fr'][32] = 'convertir !important-hack'; +$lang['fr'][33] = 'Sauver en tant que fichier'; +$lang['fr'][34] = 'Une plus grande compression en raison de plus petits caractères'; +$lang['fr'][35] = 'Compresser le CSS'; +$lang['fr'][36] = 'Facteur de Compression'; +$lang['fr'][37] = 'Entrée'; +$lang['fr'][38] = 'Sortie'; +$lang['fr'][39] = 'Language'; +$lang['fr'][41] = 'Attention : ceci peut changer le comportement de votre code de CSS !'; +$lang['fr'][42] = 'Enlever le dernier ;'; +$lang['fr'][43] = 'Supprimer les propriétés non valide'; +$lang['fr'][44] = 'Seulement les optimisations sûres'; +$lang['fr'][45] = 'Compresser font-weight'; +$lang['fr'][46] = 'Sauvegarder les commentaires '; +$lang['fr'][47] = 'Ne changer rien'; +$lang['fr'][48] = 'Sépare les sélecteurs (sépare au niveau de ,)'; +$lang['fr'][49] = 'Fusionne les sélecteurs avec les mêmes propriétés (rapides)'; +$lang['fr'][50] = 'Fusionne les sélecteurs intelligemment (lent)'; +$lang['fr'][51] = 'Preserve CSS'; +$lang['fr'][52] = 'Save comments, hacks, etc. Most optimisations can *not* be applied if this is enabled.'; +$lang['fr'][53] = 'None'; +$lang['fr'][54] = 'Don\'t optimise'; +$lang['fr'][55] = 'Safe optimisations'; +$lang['fr'][56] = 'All optimisations'; +$lang['fr'][57] = 'Add timestamp'; + +$lang['zh'][0] = 'CSS整形與最佳化工具(使用 CSSTidy '; +$lang['zh'][1] = 'CSS整形與最佳化工具'; +$lang['zh'][2] = '(使用'; +$lang['zh'][3] = '(純文字)'; +$lang['zh'][4] = 'é‡è¦äº‹é …:'; +$lang['zh'][6] = '你的原始碼必須是良構的(well-formed). 這個工具沒有內建驗證器(validator). 驗證器能夠指出你CSS原始碼裡的錯誤. 請使用 W3C 驗證器, 確ä¿ä½ çš„原始碼åˆä¹Žè¦ç¯„.'; +$lang['zh'][7] = '所有註解都移除了'; +$lang['zh'][8] = 'CSS 輸入:'; +$lang['zh'][9] = 'CSS 原始碼:'; +$lang['zh'][10] = 'CSS 檔案網å€(URL):'; +$lang['zh'][11] = '原始碼è¦åŠƒ:'; +$lang['zh'][12] = '壓縮程度(原始碼è¦åŠƒ):'; +$lang['zh'][13] = '最高 (沒有辦法讀, 檔案最å°)'; +$lang['zh'][14] = '高 (é©åº¦çš„å¯è®€æ€§, 檔案å°)'; +$lang['zh'][15] = '標準 (兼顧å¯è®€æ€§èˆ‡æª”案大å°)'; +$lang['zh'][16] = '低 (注é‡å¯è®€æ€§)'; +$lang['zh'][17] = '自訂 (在下方設定)'; +$lang['zh'][18] = '自訂樣æ¿'; +$lang['zh'][19] = 'é¸é …'; +$lang['zh'][20] = 'æ•´ç†é¸æ“‡ç¬¦(請謹慎使用)'; +$lang['zh'][21] = 'æ•´ç†å±¬æ€§'; +$lang['zh'][22] = 'é‡çµ„é¸æ“‡ç¬¦'; +$lang['zh'][23] = '速記法(shorthand)最佳化'; +$lang['zh'][24] = '壓縮色彩語法'; +$lang['zh'][25] = '改用å°å¯«é¸æ“‡ç¬¦'; +$lang['zh'][26] = '屬性的字形:'; +$lang['zh'][27] = 'å°å¯«'; +$lang['zh'][28] = '沒有輸入CSS, 語法ä¸ç¬¦åˆè¦å®š, 或是網å€éŒ¯èª¤!'; +$lang['zh'][29] = '大寫'; +$lang['zh'][30] = 'XHTML必須使用å°å¯«çš„元素å稱'; +$lang['zh'][31] = '移除ä¸å¿…è¦çš„å斜線'; +$lang['zh'][32] = 'è½‰æ› !important-hack'; +$lang['zh'][33] = '輸出æˆæª”案形å¼'; +$lang['zh'][34] = '由於比較少æ›è¡Œå­—å…ƒ, 會有更大的壓縮比率(複製&貼上沒有用)'; +$lang['zh'][35] = '執行'; +$lang['zh'][36] = '壓縮比率'; +$lang['zh'][37] = '輸入'; +$lang['zh'][38] = '輸出'; +$lang['zh'][39] = '語言'; +$lang['zh'][41] = '注æ„: 這或許會變更你CSS原始碼的行為!'; +$lang['zh'][42] = '除去最後一個分號'; +$lang['zh'][43] = '拋棄ä¸ç¬¦åˆè¦å®šçš„屬性'; +$lang['zh'][44] = 'åªå®‰å…¨åœ°æœ€ä½³åŒ–'; +$lang['zh'][45] = '壓縮 font-weight'; +$lang['zh'][46] = 'ä¿ç•™è¨»è§£'; +$lang['zh'][47] = '什麼都ä¸è¦æ”¹'; +$lang['zh'][48] = 'åªåˆ†é–‹åŽŸæœ¬ç”¨é€—號分隔的é¸æ“‡ç¬¦'; +$lang['zh'][49] = 'åˆä½µæœ‰ç›¸åŒå±¬æ€§çš„é¸æ“‡ç¬¦(快速)'; +$lang['zh'][50] = 'è°æ˜Žåœ°åˆä½µé¸æ“‡ç¬¦(慢速)'; +$lang['zh'][51] = 'ä¿è­·CSS'; +$lang['zh'][52] = 'ä¿ç•™è¨»è§£èˆ‡ hack 等等. 如果啟用這個é¸é …, 大多數的最佳化程åºéƒ½ä¸æœƒåŸ·è¡Œ.'; +$lang['zh'][53] = 'ä¸æ”¹è®Š'; +$lang['zh'][54] = 'ä¸åšæœ€ä½³åŒ–'; +$lang['zh'][55] = '安全地最佳化'; +$lang['zh'][56] = '全部最佳化'; +$lang['zh'][57] = '加上時間戳記'; +?> diff --git a/instmess/lib/csstidy-1.2/template.tpl b/instmess/lib/csstidy-1.2/template.tpl new file mode 100644 index 0000000..731985e --- /dev/null +++ b/instmess/lib/csstidy-1.2/template.tpl @@ -0,0 +1,10 @@ +| { +|| { +|||; +|}| + +| +} + +||| +| diff --git a/instmess/lib/csstidy-1.2/template1.tpl b/instmess/lib/csstidy-1.2/template1.tpl new file mode 100644 index 0000000..2f6cd4d --- /dev/null +++ b/instmess/lib/csstidy-1.2/template1.tpl @@ -0,0 +1,12 @@ +| { +|| +{ +| ||; +|}| + +| + +} + +| || +| diff --git a/instmess/lib/csstidy-1.2/template2.tpl b/instmess/lib/csstidy-1.2/template2.tpl new file mode 100644 index 0000000..dc2903c --- /dev/null +++ b/instmess/lib/csstidy-1.2/template2.tpl @@ -0,0 +1,5 @@ +| { +||{|||;|}| +| +} +|||| diff --git a/instmess/lib/csstidy-1.2/template3.tpl b/instmess/lib/csstidy-1.2/template3.tpl new file mode 100644 index 0000000..ac752c9 --- /dev/null +++ b/instmess/lib/csstidy-1.2/template3.tpl @@ -0,0 +1 @@ +|{||{|||;|}||}|||| \ No newline at end of file diff --git a/instmess/lib/ctype/ctype.php b/instmess/lib/ctype/ctype.php new file mode 100644 index 0000000..5d15977 --- /dev/null +++ b/instmess/lib/ctype/ctype.php @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/instmess/lib/json/JSON.php b/instmess/lib/json/JSON.php new file mode 100644 index 0000000..0cddbdd --- /dev/null +++ b/instmess/lib/json/JSON.php @@ -0,0 +1,806 @@ + + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * + */ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + */ + function Services_JSON($use = 0) + { + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + } + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + } + + // treat it like a regular array + $elements = array_map(array($this, 'encode'), $var); + + foreach($elements as $element) { + if(Services_JSON::isError($element)) { + return $element; + } + } + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = get_object_vars($var); + + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode($value); + + if(Services_JSON::isError($encoded_value)) { + return $encoded_value; + } + + return $this->encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + $m = array(); + + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + $parts = array(); + + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { + // found a quote, we're in a string, and it's not escaped + // we know that it's not escaped becase there is _not_ an + // odd number of backslashes at the end of the string so far + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('PEAR_Error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} + +?> diff --git a/instmess/lib/json/LICENSE b/instmess/lib/json/LICENSE new file mode 100644 index 0000000..4ae6bef --- /dev/null +++ b/instmess/lib/json/LICENSE @@ -0,0 +1,21 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +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 ``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 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. diff --git a/instmess/lib/pear/PHPUnit.php b/instmess/lib/pear/PHPUnit.php new file mode 100644 index 0000000..f2b5906 --- /dev/null +++ b/instmess/lib/pear/PHPUnit.php @@ -0,0 +1,132 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: PHPUnit.php,v 1.17 2005/11/10 09:47:11 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +require_once 'PHPUnit/TestCase.php'; +require_once 'PHPUnit/TestResult.php'; +require_once 'PHPUnit/TestSuite.php'; + +/** + * PHPUnit runs a TestSuite and returns a TestResult object. + * + * Here is an example: + * + * + * PHPUnit_TestCase($name); + * } + * + * function setUp() { + * $this->fValue1 = 2; + * $this->fValue2 = 3; + * } + * + * function testAdd() { + * $this->assertTrue($this->fValue1 + $this->fValue2 == 5); + * } + * } + * + * $suite = new PHPUnit_TestSuite(); + * $suite->addTest(new MathTest('testAdd')); + * + * $result = PHPUnit::run($suite); + * print $result->toHTML(); + * ?> + * + * + * Alternatively, you can pass a class name to the PHPUnit_TestSuite() + * constructor and let it automatically add all methods of that class + * that start with 'test' to the suite: + * + * + * toHTML(); + * ?> + * + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit { + /** + * Runs a test(suite). + * + * @param mixed + * @return PHPUnit_TestResult + * @access public + */ + function &run(&$suite) { + $result = new PHPUnit_TestResult(); + $suite->run($result); + + return $result; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/PHPUnit/Assert.php b/instmess/lib/pear/PHPUnit/Assert.php new file mode 100644 index 0000000..fbfaba7 --- /dev/null +++ b/instmess/lib/pear/PHPUnit/Assert.php @@ -0,0 +1,426 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: Assert.php,v 1.29 2005/11/10 09:47:14 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +/** + * A set of assert methods. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_Assert { + /** + * @var boolean + * @access private + */ + var $_looselyTyped = FALSE; + + /** + * Asserts that a haystack contains a needle. + * + * @param mixed + * @param mixed + * @param string + * @access public + * @since Method available since Release 1.1.0 + */ + function assertContains($needle, $haystack, $message = '') { + if (is_string($needle) && is_string($haystack)) { + $this->assertTrue(strpos($haystack, $needle) !== FALSE, $message); + } + + else if (is_array($haystack) && !is_object($needle)) { + $this->assertTrue(in_array($needle, $haystack), $message); + } + + else { + $this->fail('Unsupported parameter passed to assertContains().'); + } + } + + /** + * Asserts that a haystack does not contain a needle. + * + * @param mixed + * @param mixed + * @param string + * @access public + * @since Method available since Release 1.1.0 + */ + function assertNotContains($needle, $haystack, $message = '') { + if (is_string($needle) && is_string($haystack)) { + $this->assertFalse(strpos($haystack, $needle) !== FALSE, $message); + } + + else if (is_array($haystack) && !is_object($needle)) { + $this->assertFalse(in_array($needle, $haystack), $message); + } + + else { + $this->fail('Unsupported parameter passed to assertNotContains().'); + } + } + + /** + * Asserts that two variables are equal. + * + * @param mixed + * @param mixed + * @param string + * @param mixed + * @access public + */ + function assertEquals($expected, $actual, $message = '', $delta = 0) { + if ((is_array($actual) && is_array($expected)) || + (is_object($actual) && is_object($expected))) { + if (is_array($actual) && is_array($expected)) { + ksort($actual); + ksort($expected); + } + + if ($this->_looselyTyped) { + $actual = $this->_convertToString($actual); + $expected = $this->_convertToString($expected); + } + + $actual = serialize($actual); + $expected = serialize($expected); + + $message = sprintf( + '%sexpected %s, actual %s', + + !empty($message) ? $message . ' ' : '', + $expected, + $actual + ); + + if ($actual !== $expected) { + return $this->fail($message); + } + } + + elseif (is_numeric($actual) && is_numeric($expected)) { + $message = sprintf( + '%sexpected %s%s, actual %s', + + !empty($message) ? $message . ' ' : '', + $expected, + ($delta != 0) ? ('+/- ' . $delta) : '', + $actual + ); + + if (!($actual >= ($expected - $delta) && $actual <= ($expected + $delta))) { + return $this->fail($message); + } + } + + else { + $message = sprintf( + '%sexpected %s, actual %s', + + !empty($message) ? $message . ' ' : '', + $expected, + $actual + ); + + if ($actual !== $expected) { + return $this->fail($message); + } + } + } + + /** + * Asserts that two variables reference the same object. + * This requires the Zend Engine 2 to work. + * + * @param object + * @param object + * @param string + * @access public + * @deprecated + */ + function assertSame($expected, $actual, $message = '') { + if (!version_compare(phpversion(), '5.0.0', '>=')) { + $this->fail('assertSame() only works with PHP >= 5.0.0.'); + } + + if ((is_object($expected) || is_null($expected)) && + (is_object($actual) || is_null($actual))) { + $message = sprintf( + '%sexpected two variables to reference the same object', + + !empty($message) ? $message . ' ' : '' + ); + + if ($expected !== $actual) { + return $this->fail($message); + } + } else { + $this->fail('Unsupported parameter passed to assertSame().'); + } + } + + /** + * Asserts that two variables do not reference the same object. + * This requires the Zend Engine 2 to work. + * + * @param object + * @param object + * @param string + * @access public + * @deprecated + */ + function assertNotSame($expected, $actual, $message = '') { + if (!version_compare(phpversion(), '5.0.0', '>=')) { + $this->fail('assertNotSame() only works with PHP >= 5.0.0.'); + } + + if ((is_object($expected) || is_null($expected)) && + (is_object($actual) || is_null($actual))) { + $message = sprintf( + '%sexpected two variables to reference different objects', + + !empty($message) ? $message . ' ' : '' + ); + + if ($expected === $actual) { + return $this->fail($message); + } + } else { + $this->fail('Unsupported parameter passed to assertNotSame().'); + } + } + + /** + * Asserts that a variable is not NULL. + * + * @param mixed + * @param string + * @access public + */ + function assertNotNull($actual, $message = '') { + $message = sprintf( + '%sexpected NOT NULL, actual NULL', + + !empty($message) ? $message . ' ' : '' + ); + + if (is_null($actual)) { + return $this->fail($message); + } + } + + /** + * Asserts that a variable is NULL. + * + * @param mixed + * @param string + * @access public + */ + function assertNull($actual, $message = '') { + $message = sprintf( + '%sexpected NULL, actual NOT NULL', + + !empty($message) ? $message . ' ' : '' + ); + + if (!is_null($actual)) { + return $this->fail($message); + } + } + + /** + * Asserts that a condition is true. + * + * @param boolean + * @param string + * @access public + */ + function assertTrue($condition, $message = '') { + $message = sprintf( + '%sexpected TRUE, actual FALSE', + + !empty($message) ? $message . ' ' : '' + ); + + if (!$condition) { + return $this->fail($message); + } + } + + /** + * Asserts that a condition is false. + * + * @param boolean + * @param string + * @access public + */ + function assertFalse($condition, $message = '') { + $message = sprintf( + '%sexpected FALSE, actual TRUE', + + !empty($message) ? $message . ' ' : '' + ); + + if ($condition) { + return $this->fail($message); + } + } + + /** + * Asserts that a string matches a given regular expression. + * + * @param string + * @param string + * @param string + * @access public + */ + function assertRegExp($pattern, $string, $message = '') { + $message = sprintf( + '%s"%s" does not match pattern "%s"', + + !empty($message) ? $message . ' ' : '', + $string, + $pattern + ); + + if (!preg_match($pattern, $string)) { + return $this->fail($message); + } + } + + /** + * Asserts that a string does not match a given regular expression. + * + * @param string + * @param string + * @param string + * @access public + * @since Method available since Release 1.1.0 + */ + function assertNotRegExp($pattern, $string, $message = '') { + $message = sprintf( + '%s"%s" matches pattern "%s"', + + !empty($message) ? $message . ' ' : '', + $string, + $pattern + ); + + if (preg_match($pattern, $string)) { + return $this->fail($message); + } + } + + /** + * Asserts that a variable is of a given type. + * + * @param string $expected + * @param mixed $actual + * @param optional string $message + * @access public + */ + function assertType($expected, $actual, $message = '') { + return $this->assertEquals( + $expected, + gettype($actual), + $message + ); + } + + /** + * Converts a value to a string. + * + * @param mixed $value + * @access private + */ + function _convertToString($value) { + foreach ($value as $k => $v) { + if (is_array($v)) { + $value[$k] = $this->_convertToString($value[$k]); + } else { + settype($value[$k], 'string'); + } + } + + return $value; + } + + /** + * @param boolean $looselyTyped + * @access public + */ + function setLooselyTyped($looselyTyped) { + if (is_bool($looselyTyped)) { + $this->_looselyTyped = $looselyTyped; + } + } + + /** + * Fails a test with the given message. + * + * @param string + * @access protected + * @abstract + */ + function fail($message = '') { /* abstract */ } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/PHPUnit/GUI/Gtk.php b/instmess/lib/pear/PHPUnit/GUI/Gtk.php new file mode 100644 index 0000000..9155bad --- /dev/null +++ b/instmess/lib/pear/PHPUnit/GUI/Gtk.php @@ -0,0 +1,740 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Scott Mattocks + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: Gtk.php,v 1.6 2005/11/10 09:47:15 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.2.0 + */ + +if (!function_exists('is_a')) { + require_once 'PHP/Compat/Function/is_a.php'; +} + +/** + * GTK GUI interface for PHPUnit. + * + * This class is a PHP port of junit.awtui.testrunner. Documentation + * for junit.awtui.testrunner can be found at + * http://junit.sourceforge.net + * + * Due to the limitations of PHP4 and PHP-Gtk, this class can not + * duplicate all of the functionality of the JUnit GUI. Some of the + * things this class cannot do include: + * - Reloading the class for each run + * - Stopping the test in progress + * + * To use simply intantiate the class and call main() + * $gtk =& new PHPUnit_GUI_Gtk; + * $gtk->main(); + * + * Once the window has finished loading, you can enter the name of + * a class that has been loaded (include/require some where in your + * code, or you can pass the name of the file containing the class. + * + * You can also load classes using the SetupDecorator class. + * require_once 'PHPUnit/GUI/SetupDecorator.php'; + * require_once 'PHPUnit/GUI/Gtk.php'; + * $gui = new PHPUnit_GUI_SetupDecorator(new PHPUnit_GUI_Gtk()); + * $gui->getSuitesFromDir('/path/to/test','.*\.php$',array('index.php','sql.php')); + * $gui->show(); + * + * + * @category Testing + * @package PHPUnit + * @author Scott Mattocks + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.2.0 + * @todo Allow file drop. (Gtk_FileDrop) + */ +class PHPUnit_GUI_Gtk { + + /** + * The main gtk window + * @var object + */ + var $gui; + /** + * The text entry that contains the name of the + * file that holds the test(s)/suite(s). + * @var object + */ + var $suiteField; + /** + * The label that shows the number of tests that + * were run. + * @var object + */ + var $numberOfRuns; + /** + * The label that shows the number of errors that + * were encountered. + * @var object + */ + var $numberOfErrors; + /** + * The label that shows the number of failures + * that were encountered. + * @var object + */ + var $numberOfFailures; + /** + * The label for reporting user messages. + * @var object + */ + var $statusLine; + /** + * The text area for reporting messages from successful + * test runs. (not necessarily successful tests) + * @var object + */ + var $reportArea; + /** + * The text area for reporting errors encountered when + * running tests. + * @var object + */ + var $dumpArea; + /** + * The progress bar indicator. Shows the percentage of + * passed tests. + * @var object + */ + var $progress; + /** + * A checkbox for the user to indicate whether or not they + * would like to see results from all tests or just failures. + * @object + */ + var $showPassed; + + /** + * Constructor. + * + * The constructor checks for the gtk extension and loads it + * if needed. Then it creates the GUI. The GUI is not shown + * nor is the main gtk loop started until main() is called. + * + * @access public + * @param none + * @return void + */ + function PHPUnit_GUI_Gtk() + { + // Check for php-gtk extension. + if (!extension_loaded('gtk')) { + dl( 'php_gtk.' . PHP_SHLIB_SUFFIX); + } + + // Create the interface but don't start the loop + $this->_createUI(); + } + /** + * Start the main gtk loop. + * + * main() first sets the default state of the showPassed + * check box. Next all widgets that are part of the GUI + * are shown. Finally the main gtk loop is started. + * + * @access public + * @param boolean $showPassed + * @return void + */ + function main($showPassed = true) + { + $this->showPassed->set_active($showPassed); + $this->gui->show_all(); + + gtk::main(); + } + /** + * Create the user interface. + * + * The user interface is pretty simple. It consists of a + * menu, text entry, run button, some labels, a progress + * indicator, and a couple of areas for notification of + * any messages. + * + * @access private + * @param none + * @return void + */ + function _createUI() + { + // Create a window. + $window =& new GtkWindow; + $window->set_title('PHPUnit Gtk'); + $window->set_usize(400, -1); + + // Create the main box. + $mainBox =& new GtkVBox; + $window->add($mainBox); + + // Start with the menu. + $mainBox->pack_start($this->_createMenu()); + + // Then add the suite field entry. + $mainBox->pack_start($this->_createSuiteEntry()); + + // Then add the report labels. + $mainBox->pack_start($this->_createReportLabels()); + + // Next add the progress bar. + $mainBox->pack_start($this->_createProgressBar()); + + // Then add the report area and the dump area. + $mainBox->pack_start($this->_createReportAreas()); + + // Finish off with the status line. + $mainBox->pack_start($this->_createStatusLine()); + + // Connect the destroy signal. + $window->connect_object('destroy', array('gtk', 'main_quit')); + + // Assign the member. + $this->gui =& $window; + } + /** + * Create the menu. + * + * The menu is very simple. It an exit menu item, which exits + * the application, and an about menu item, which shows some + * basic information about the application itself. + * + * @access private + * @param none + * @return &object The GtkMenuBar + */ + function &_createMenu() + { + // Create the menu bar. + $menuBar =& new GtkMenuBar; + + // Create the main (only) menu item. + $phpHeader =& new GtkMenuItem('PHPUnit'); + + // Add the menu item to the menu bar. + $menuBar->append($phpHeader); + + // Create the PHPUnit menu. + $phpMenu =& new GtkMenu; + + // Add the menu items + $about =& new GtkMenuItem('About...'); + $about->connect('activate', array(&$this, 'about')); + $phpMenu->append($about); + + $exit =& new GtkMenuItem('Exit'); + $exit->connect_object('activate', array('gtk', 'main_quit')); + $phpMenu->append($exit); + + // Complete the menu. + $phpHeader->set_submenu($phpMenu); + + return $menuBar; + } + /** + * Create the suite entry and related widgets. + * + * The suite entry has some supporting components such as a + * label, the show passed check box and the run button. All + * of these items are packed into two nested boxes. + * + * @access private + * @param none + * @return &object A box that contains all of the suite entry pieces. + */ + function &_createSuiteEntry() + { + // Create the outermost box. + $outerBox =& new GtkVBox; + + // Create the suite label, box, and field. + $suiteLabel =& new GtkLabel('Test class name:'); + $suiteBox =& new GtkHBox; + $this->suiteField =& new GtkEntry; + $this->suiteField->set_text($suiteName != NULL ? $suiteName : ''); + + // Create the button the user will use to start the test. + $runButton =& new GtkButton('Run'); + $runButton->connect_object('clicked', array(&$this, 'run')); + + // Create the check box that lets the user show only failures. + $this->showPassed =& new GtkCheckButton('Show passed tests'); + + // Add the components to their respective boxes. + $suiteLabel->set_alignment(0, 0); + $outerBox->pack_start($suiteLabel); + $outerBox->pack_start($suiteBox); + $outerBox->pack_start($this->showPassed); + + $suiteBox->pack_start($this->suiteField); + $suiteBox->pack_start($runButton); + + return $outerBox; + } + + /** + * Create the labels that tell the user what has happened. + * + * There are three labels, one each for total runs, errors and + * failures. There is also one label for each of these that + * describes what the label is. It could be done with one label + * instead of two but that would make updates much harder. + * + * @access private + * @param none + * @return &object A box containing the labels. + */ + function &_createReportLabels() + { + // Create a box to hold everything. + $labelBox =& new GtkHBox; + + // Create the non-updated labels. + $numberOfRuns =& new GtkLabel('Runs:'); + $numberOfErrors =& new GtkLabel('Errors:'); + $numberOfFailures =& new GtkLabel('Failures:'); + + // Create the labels that will be updated. + // These are asssigned to members to make it easier to + // set their values later. + $this->numberOfRuns =& new GtkLabel(0); + $this->numberOfErrors =& new GtkLabel(0); + $this->numberOfFailures =& new GtkLabel(0); + + // Pack everything in. + $labelBox->pack_start($numberOfRuns); + $labelBox->pack_start($this->numberOfRuns); + $labelBox->pack_start($numberOfErrors); + $labelBox->pack_start($this->numberOfErrors); + $labelBox->pack_start($numberOfFailures); + $labelBox->pack_start($this->numberOfFailures); + + return $labelBox; + } + + /** + * Create the success/failure indicator. + * + * A GtkProgressBar is used to visually indicate how many + * tests were successful compared to how many were not. The + * progress bar shows the percentage of success and will + * change from green to red if there are any failures. + * + * @access private + * @param none + * @return &object The progress bar + */ + function &_createProgressBar() + { + // Create the progress bar. + $this->progress =& new GtkProgressBar(new GtkAdjustment(0, 0, 1, .1, 1, 0)); + + // Set the progress bar to print the percentage. + $this->progress->set_show_text(true); + + return $this->progress; + } + + /** + * Create the report text areas. + * + * The report area consists of one text area for failures, one + * text area for errors and one label for identification purposes. + * All three widgets are packed into a box. + * + * @access private + * @param none + * @return &object The box containing the report areas. + */ + function &_createReportAreas() + { + // Create the containing box. + $reportBox =& new GtkVBox; + + // Create the identification label + $reportLabel =& new GtkLabel('Errors and Failures:'); + $reportLabel->set_alignment(0, 0); + + // Create the scrolled windows for the text areas. + $reportScroll =& new GtkScrolledWindow; + $dumpScroll =& new GtkScrolledWindow; + + // Make the scroll areas big enough. + $reportScroll->set_usize(-1, 150); + $dumpScroll->set_usize(-1, 150); + + // Only show the vertical scroll bar when needed. + // Never show the horizontal scroll bar. + $reportScroll->set_policy(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + $dumpScroll->set_policy(GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + + // Create the text areas. + $this->reportArea =& new GtkText; + $this->dumpArea =& new GtkText; + + // Don't let words get broken. + $this->reportArea->set_word_wrap(true); + $this->dumpArea->set_word_wrap(true); + + // Pack everything in. + $reportBox->pack_start($reportLabel); + $reportScroll->add($this->reportArea); + $reportBox->pack_start($reportScroll); + $dumpScroll->add($this->dumpArea); + $reportBox->pack_start($dumpScroll); + + return $reportBox; + } + + /** + * Create a status label. + * + * A status line at the bottom of the application is used + * to notify the user of non-test related messages such as + * failures loading a test suite. + * + * @access private + * @param none + * @return &object The status label. + */ + function &_createStatusLine() + { + // Create the status label. + $this->statusLine =& new GtkLabel(''); + $this->statusLine->set_alignment(0, 0); + + return $this->statusLine; + } + + /** + * Show a popup with information about the application. + * + * The popup should show information about the version, + * the author, the license, where to get the latest + * version and a short description. + * + * @access public + * @param none + * @return void + */ + function about() + { + // Create the new window. + $about =& new GtkWindow; + $about->set_title('About PHPUnit GUI Gtk'); + $about->set_usize(250, -1); + + // Put two vboxes in the hbox. + $vBox =& new GtkVBox; + $about->add($vBox); + + // Create the labels. + $version =& new GtkLabel(" Version: 1.0"); + $license =& new GtkLabel(" License: PHP License v3.0"); + $where =& new GtkLabel(" Download from: http://pear.php.net/PHPUnit/"); + $unitAuth =& new GtkLabel(" PHPUnit Author: Sebastian Bergman"); + $gtkAuth =& new GtkLabel(" Gtk GUI Author: Scott Mattocks"); + + // Align everything to the left + $where->set_alignment(0, .5); + $version->set_alignment(0, .5); + $license->set_alignment(0, .5); + $gtkAuth->set_alignment(0, .5); + $unitAuth->set_alignment(0, .5); + + // Pack everything into the vBox; + $vBox->pack_start($version); + $vBox->pack_start($license); + $vBox->pack_start($where); + $vBox->pack_start($unitAuth); + $vBox->pack_start($gtkAuth); + + // Connect the destroy signal. + $about->connect('destroy', array('gtk', 'true')); + + // Show the goods. + $about->show_all(); + } + + /** + * Load the test suite. + * + * This method tries to load test suite based on the user + * info. If the user passes the name of a tests suite, it + * is instantiated and a new object is returned. If the + * user passes a file that contains a test suite, the class + * is instantiated and a new object is returned. If the user + * passes a file that contains a test case, the test case is + * passed to a new test suite and the new suite object is + * returned. + * + * @access public + * @param string The file that contains a test case/suite or the classname. + * @return &object The new test suite. + */ + function &loadTest(&$file) + { + // Check to see if a class name was given. + if (is_a($file, 'PHPUnit_TestSuite')) { + return $file; + } elseif (class_exists($file)) { + require_once 'PHPUnit/TestSuite.php'; + return new PHPUnit_TestSuite($file); + } + + // Check that the file exists. + if (!@is_readable($file)) { + $this->_showStatus('Cannot find file: ' . $file); + return false; + } + + $this->_showStatus('Loading test suite...'); + + // Instantiate the class. + // If the path is /path/to/test/TestClass.php + // the class name should be test_TestClass + require_once $file; + $className = str_replace(DIRECTORY_SEPARATOR, '_', $file); + $className = substr($className, 0, strpos($className, '.')); + + require_once 'PHPUnit/TestSuite.php'; + return new PHPUnit_TestSuite($className); + } + + /** + * Run the test suite. + * + * This method runs the test suite and updates the messages + * for the user. When finished it changes the status line + * to 'Test Complete' + * + * @access public + * @param none + * @return void + */ + function runTest() + { + // Notify the user that the test is running. + $this->_showStatus('Running Test...'); + + // Run the test. + $result = PHPUnit::run($this->suite); + + // Update the labels. + $this->_setLabelValue($this->numberOfRuns, $result->runCount()); + $this->_setLabelValue($this->numberOfErrors, $result->errorCount()); + $this->_setLabelValue($this->numberOfFailures, $result->failureCount()); + + // Update the progress bar. + $this->_updateProgress($result->runCount(), + $result->errorCount(), + $result->failureCount() + ); + + // Show the errors. + $this->_showFailures($result->errors(), $this->dumpArea); + + // Show the messages from the tests. + if ($this->showPassed->get_active()) { + // Show failures and success. + $this->_showAll($result, $this->reportArea); + } else { + // Show only failures. + $this->_showFailures($result->failures(), $this->reportArea); + } + + // Update the status message. + $this->_showStatus('Test complete'); + } + + /** + * Set the text of a label. + * + * Change the text of a given label. + * + * @access private + * @param widget &$label The label whose value is to be changed. + * @param string $value The new text of the label. + * @return void + */ + function _setLabelValue(&$label, $value) + { + $label->set_text($value); + } + + /** + * The main work of the application. + * + * Load the test suite and then execute the tests. + * + * @access public + * @param none + * @return void + */ + function run() + { + // Load the test suite. + $this->suite =& $this->loadTest($this->suiteField->get_text()); + + // Check to make sure the suite was loaded properly. + if (!is_object($this->suite)) { + // Raise an error. + $this->_showStatus('Could not load test suite.'); + return false; + } + + // Run the tests. + $this->runTest(); + } + + /** + * Update the status message. + * + * @access private + * @param string $status The new message. + * @return void + */ + function _showStatus($status) + { + $this->statusLine->set_text($status); + } + + /** + * Alias for main() + * + * @see main + */ + function show($showPassed = true) + { + $this->main($showPassed); + } + + /** + * Add a suite to the tests. + * + * This method is require by SetupDecorator. It adds a + * suite to the the current set of suites. + * + * @access public + * @param object $testSuite The suite to add. + * @return void + */ + function addSuites($testSuite) + { + if (!is_array($testSuite)) { + settype($testSuite, 'array'); + } + + foreach ($testSuite as $suite) { + + if (is_a($this->suite, 'PHPUnit_TestSuite')) { + $this->suite->addTestSuite($suite->getName()); + } else { + $this->suite =& $this->loadTest($suite); + } + + // Set the suite field. + $text = $this->suiteField->get_text(); + if (empty($text)) { + $this->suiteField->set_text($this->suite->getName()); + } + } + } + + /** + * Show all test messages. + * + * @access private + * @param object The TestResult from the test suite. + * @return void + */ + function _showAll(&$result) + { + // Clear the area first. + $this->reportArea->delete_text(0, -1); + $this->reportArea->insert_text($result->toString(), 0); + } + + /** + * Show failure/error messages in the given text area. + * + * @access private + * @param object &$results The results of the test. + * @param widget &$area The area to show the results in. + */ + function _showFailures(&$results, &$area) + { + $area->delete_text(0, -1); + foreach (array_reverse($results, true) as $result) { + $area->insert_text($result->toString(), 0); + } + } + + /** + * Update the progress indicator. + * + * @access private + * @param integer $runs + * @param integer $errors + * @param integer $failures + * @return void + */ + function _updateProgress($runs, $errors, $failures) + { + $percentage = 1 - (($errors + $failures) / $runs); + $this->progress->set_percentage($percentage); + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/PHPUnit/GUI/HTML.php b/instmess/lib/pear/PHPUnit/GUI/HTML.php new file mode 100644 index 0000000..a2c38a7 --- /dev/null +++ b/instmess/lib/pear/PHPUnit/GUI/HTML.php @@ -0,0 +1,252 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Wolfram Kriesing + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: HTML.php,v 1.19 2005/11/10 09:47:15 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +/** + * HTML GUI. + * + * @category Testing + * @package PHPUnit + * @author Wolfram Kriesing + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_GUI_HTML +{ + var $_suites = array(); + + /** + * the current implementation of PHPUnit is designed + * this way that adding a suite to another suite only + * grabs all the tests and adds them to the suite, so you + * have no chance to find out which test goes with which suite + * therefore you can simply pass an array of suites to this constructor here + * + * @param array The suites to be tested. If not given, then you might + * be using the SetupDecorator, which detects them automatically + * when calling getSuitesFromDir() + */ + function PHPUnit_GUI_HTML($suites = array()) + { + if (!is_array($suites)) { + $this->_suites = array($suites); + } else { + $this->_suites = $suites; + } + } + + /** + * Add suites to the GUI + * + * @param object this should be an instance of PHPUnit_TestSuite + */ + function addSuites($suites) + { + $this->_suites = array_merge($this->_suites,$suites); + } + + /** + * this prints the HTML code straight out + * + */ + function show() + { + $request = $_REQUEST; + $showPassed = FALSE; + $submitted = @$request['submitted']; + + if ($submitted) { + $showPassed = @$request['showOK'] ? TRUE : FALSE; + } + + $suiteResults = array(); + + foreach ($this->_suites as $aSuite) { + $aSuiteResult = array(); + + // remove the first directory's name from the test-suite name, since it + // mostly is something like 'tests' or alike + $removablePrefix = explode('_',$aSuite->getName()); + $aSuiteResult['name'] = str_replace($removablePrefix[0].'_', '', $aSuite->getName()); + + if ($submitted && isset($request[$aSuiteResult['name']])) { + $result = PHPUnit::run($aSuite); + + $aSuiteResult['counts']['run'] = $result->runCount(); + $aSuiteResult['counts']['error'] = $result->errorCount(); + $aSuiteResult['counts']['failure'] = $result->failureCount(); + + $aSuiteResult['results'] = $this->_prepareResult($result,$showPassed); + + $per = 100/$result->runCount(); + $failed = ($per*$result->errorCount())+($per*$result->failureCount()); + $aSuiteResult['percent'] = round(100-$failed,2); + } else { + $aSuiteResult['addInfo'] = 'NOT EXECUTED'; + } + + $suiteResults[] = $aSuiteResult; + } + + $final['name'] = 'OVERALL RESULT'; + $final['counts'] = array(); + $final['percent'] = 0; + $numExecutedTests = 0; + + foreach ($suiteResults as $aSuiteResult) { + if (sizeof(@$aSuiteResult['counts'])) { + foreach ($aSuiteResult['counts'] as $key=>$aCount) { + if (!isset($final['counts'][$key])) { + $final['counts'][$key] = 0; + } + + $final['counts'][$key] += $aCount; + } + } + } + + if (isset($final['counts']['run'])) { + $per = 100/$final['counts']['run']; + $failed = ($per*$final['counts']['error'])+($per*$final['counts']['failure']); + $final['percent'] = round(100-$failed,2); + } else { + $final['percent'] = 0; + } + + array_unshift($suiteResults,$final); + + include 'PHPUnit/GUI/HTML.tpl'; + } + + function _prepareResult($result,$showPassed) + { + $ret = array(); + $failures = $result->failures(); + + foreach($failures as $aFailure) { + $ret['failures'][] = $this->_prepareFailure($aFailure); + } + + $errors = $result->errors(); + + foreach($errors as $aError) { + $ret['errors'][] = $this->_prepareErrors($aError); + } + + if ($showPassed) { + $passed = $result->passedTests(); + + foreach($passed as $aPassed) { + $ret['passed'][] = $this->_preparePassedTests($aPassed); + } + } + + return $ret; + } + + function _prepareFailure($failure) + { + $test = $failure->failedTest(); + $ret['testName'] = $test->getName(); + $exception = $failure->thrownException(); + + // a serialized string starts with a 'character:decimal:{' + // if so we try to unserialize it + // this piece of the regular expression is for detecting a serialized + // type like 'a:3:' for an array with three element or an object i.e. 'O:12:"class":3' + $serialized = '(\w:\d+:(?:"[^"]+":\d+:)?\{.*\})'; + + // Spaces might make a diff, so we shall show them properly (since a + // user agent ignores them). + if (preg_match('/^(.*)expected ' . $serialized . ', actual ' . $serialized . '$/sU', $exception, $matches)) { + ob_start(); + print_r(unserialize($matches[2])); + $ret['expected'] = htmlspecialchars($matches[1]) . "
" . htmlspecialchars(rtrim(ob_get_contents())) . "
"; + // Improved compatibility, ob_clean() would be PHP >= 4.2.0 only. + ob_end_clean(); + + ob_start(); + print_r(unserialize($matches[3])); + $ret['actual'] = htmlspecialchars($matches[1]) . "
" . htmlspecialchars(rtrim(ob_get_contents())) . "
"; + ob_end_clean(); + } + + else if (preg_match('/^(.*)expected (.*), actual (.*)$/sU', $exception, $matches)) { + $ret['expected'] = nl2br(str_replace(" ", " ", htmlspecialchars($matches[1] . $matches[2]))); + $ret['actual'] = nl2br(str_replace(" ", " ", htmlspecialchars($matches[1] . $matches[3]))); + } else { + $ret['message'] = nl2br(str_replace(" ", " ", htmlspecialchars($exception))); + } + + return $ret; + } + + function _preparePassedTests($passed) + { + $ret['testName'] = $passed->getName(); + return $ret; + } + + function _prepareError($error) + { + $ret['testName'] = $error->getName(); + $ret['message'] = $error->toString(); + return $ret; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/PHPUnit/GUI/HTML.tpl b/instmess/lib/pear/PHPUnit/GUI/HTML.tpl new file mode 100644 index 0000000..edf7f25 --- /dev/null +++ b/instmess/lib/pear/PHPUnit/GUI/HTML.tpl @@ -0,0 +1,156 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Options +
+ + (un)check all +     + show OK > +     + +
+ > + +   + + + +
+ + + + + +
+ + + +
+
+ $value): ?> + s =         + +
+ + + + + + + + + + + + +
expected
actual
+ +
+ +
OK
+
+ + + + + diff --git a/instmess/lib/pear/PHPUnit/GUI/SetupDecorator.php b/instmess/lib/pear/PHPUnit/GUI/SetupDecorator.php new file mode 100644 index 0000000..0a9aa18 --- /dev/null +++ b/instmess/lib/pear/PHPUnit/GUI/SetupDecorator.php @@ -0,0 +1,209 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Wolfram Kriesing + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: SetupDecorator.php,v 1.15 2005/11/10 09:47:15 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +/** + * This decorator actually just adds the functionality to read the + * test-suite classes from a given directory and instanciate them + * automatically, use it as given in the example below. + * + * + * getSuitesFromDir('/path/to/dir/tests','.*\.php$',array('index.php','sql.php')); + * $gui->show(); + * ?> + * + * + * The example calls this class and tells it to: + * + * - find all file under the directory /path/to/dir/tests + * - for files, which end with '.php' (this is a piece of a regexp, that's why the . is escaped) + * - and to exclude the files 'index.php' and 'sql.php' + * - and include all the files that are left in the tests. + * + * Given that the path (the first parameter) ends with 'tests' it will be assumed + * that the classes are named tests_* where * is the directory plus the filename, + * according to PEAR standards. + * + * So that: + * + * - 'testMe.php' in the dir 'tests' bill be assumed to contain a class tests_testMe + * - '/moretests/aTest.php' should contain a class 'tests_moretests_aTest' + * + * @category Testing + * @package PHPUnit + * @author Wolfram Kriesing + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_GUI_SetupDecorator +{ + /** + * + * + */ + function PHPUnit_GUI_SetupDecorator(&$gui) + { + $this->_gui = &$gui; + } + + /** + * just forwarding the action to the decorated class. + * + */ + function show($showPassed=TRUE) + { + $this->_gui->show($showPassed); + } + + /** + * Setup test suites that can be found in the given directory + * Using the second parameter you can also choose a subsets of the files found + * in the given directory. I.e. only all the files that contain '_UnitTest_', + * in order to do this simply call it like this: + * getSuitesFromDir($dir,'.*_UnitTest_.*'). + * There you can already see that the pattern is built for the use within a regular expression. + * + * @param string the directory where to search for test-suite files + * @param string the pattern (a regexp) by which to find the files + * @param array an array of file names that shall be excluded + */ + function getSuitesFromDir($dir, $filenamePattern = '', $exclude = array()) + { + if ($dir{strlen($dir)-1} == DIRECTORY_SEPARATOR) { + $dir = substr($dir, 0, -1); + } + + $files = $this->_getFiles(realpath($dir), $filenamePattern, $exclude, realpath($dir . '/..')); + asort($files); + + foreach ($files as $className => $aFile) { + include_once($aFile); + + if (class_exists($className)) { + $suites[] =& new PHPUnit_TestSuite($className); + } else { + trigger_error("$className could not be found in $dir$aFile!"); + } + } + + $this->_gui->addSuites($suites); + } + + /** + * This method searches recursively through the directories + * to find all the files that shall be added to the be visible. + * + * @param string the path where find the files + * @param srting the string pattern by which to find the files + * @param string the file names to be excluded + * @param string the root directory, which serves as the prefix to the fully qualified filename + * @access private + */ + function _getFiles($dir, $filenamePattern, $exclude, $rootDir) + { + $files = array(); + + if ($dp = opendir($dir)) { + while (FALSE !== ($file = readdir($dp))) { + $filename = $dir . DIRECTORY_SEPARATOR . $file; + $match = TRUE; + + if ($filenamePattern && !preg_match("~$filenamePattern~", $file)) { + $match = FALSE; + } + + if (sizeof($exclude)) { + foreach ($exclude as $aExclude) { + if (strpos($file, $aExclude) !== FALSE) { + $match = FALSE; + break; + } + } + } + + if (is_file($filename) && $match) { + $tmp = str_replace($rootDir, '', $filename); + + if (strpos($tmp, DIRECTORY_SEPARATOR) === 0) { + $tmp = substr($tmp, 1); + } + + if (strpos($tmp, '/') === 0) { + $tmp = substr($tmp, 1); + } + + $className = str_replace(DIRECTORY_SEPARATOR, '_', $tmp); + $className = basename($className, '.php'); + + $files[$className] = $filename; + } + + if ($file != '.' && $file != '..' && is_dir($filename)) { + $files = array_merge($files, $this->_getFiles($filename, $filenamePattern, $exclude, $rootDir)); + } + } + + closedir($dp); + } + + return $files; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/PHPUnit/RepeatedTest.php b/instmess/lib/pear/PHPUnit/RepeatedTest.php new file mode 100644 index 0000000..54c8ee2 --- /dev/null +++ b/instmess/lib/pear/PHPUnit/RepeatedTest.php @@ -0,0 +1,154 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: RepeatedTest.php,v 1.13 2005/11/10 09:47:14 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +require_once 'PHPUnit/TestDecorator.php'; + +/** + * A Decorator that runs a test repeatedly. + * + * Here is an example: + * + * + * PHPUnit_TestCase($name); + * } + * + * function setUp() { + * $this->fValue1 = 2; + * $this->fValue2 = 3; + * } + * + * function testAdd() { + * $this->assertTrue($this->fValue1 + $this->fValue2 == 5); + * } + * } + * + * $suite = new PHPUnit_TestSuite; + * + * $suite->addTest( + * new PHPUnit_RepeatedTest( + * new MathTest('testAdd'), + * 10 + * ) + * ); + * + * $result = PHPUnit::run($suite); + * print $result->toString(); + * ?> + * + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_RepeatedTest extends PHPUnit_TestDecorator { + /** + * @var integer + * @access private + */ + var $_timesRepeat = 1; + + /** + * Constructor. + * + * @param object + * @param integer + * @access public + */ + function PHPUnit_RepeatedTest(&$test, $timesRepeat = 1) { + $this->PHPUnit_TestDecorator($test); + $this->_timesRepeat = $timesRepeat; + } + + /** + * Counts the number of test cases that + * will be run by this test. + * + * @return integer + * @access public + */ + function countTestCases() { + return $this->_timesRepeat * $this->_test->countTestCases(); + } + + /** + * Runs the decorated test and collects the + * result in a TestResult. + * + * @param object + * @access public + * @abstract + */ + function run(&$result) { + for ($i = 0; $i < $this->_timesRepeat; $i++) { + $this->_test->run($result); + } + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/PHPUnit/Skeleton.php b/instmess/lib/pear/PHPUnit/Skeleton.php new file mode 100644 index 0000000..a2be387 --- /dev/null +++ b/instmess/lib/pear/PHPUnit/Skeleton.php @@ -0,0 +1,448 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Scott Mattocks + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: Skeleton.php,v 1.8 2005/11/10 09:47:14 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.1.0 + */ + +/** + * Class for creating a PHPUnit_TestCase skeleton file. + * + * This class will take a classname as a parameter on construction and will + * create a PHP file that contains the skeleton of a PHPUnit_TestCase + * subclass. The test case will contain a test foreach method of the class. + * Methods of the parent class will, by default, be excluded from the test + * class. Passing and optional construction parameter will include them. + * + * Example + * + * createTestClass(); + * + * // Write the new test class to file. + * // By default, code to run the test will be included. + * $ps->writeTestClass(); + * ?> + * + * Now open the skeleton class and fill in the details. + * If you run the test as is, all tests will fail and + * you will see plenty of undefined constant errors. + * + * @category Testing + * @package PHPUnit + * @author Scott Mattocks + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.1.0 + */ +class PHPUnit_Skeleton { + /** + * Path to the class file to create a skeleton for. + * @var string + */ + var $classPath; + + /** + * The name of the class + * @var string + */ + var $className; + + /** + * Path to the configuration file needed by class to test. + * @var string + */ + var $configFile; + + /** + * Whether or not to include the methods of the parent class when testing. + * @var boolean + */ + var $includeParents; + + /** + * Whether or not to test private methods. + * @var boolean + */ + var $includePrivate; + + /** + * The test class that will be created. + * @var string + */ + var $testClass; + + /** + * Constructor. Sets the class members and check that the class + * to test is accessible. + * + * @access public + * @param string $className + * @param string $classPath + * @param boolean $includeParents Wheter to include the parent's methods in the test. + * @return void + */ + function PHPUnit_Skeleton($className, $classPath, $includeParents = FALSE, $includePrivate = TRUE) { + // Set up the members. + if (@is_readable($classPath)) { + $this->className = $className; + $this->classPath = $classPath; + } else { + $this->_handleErrors($classPath . ' is not readable. Cannot create test class.'); + } + + // Do we want to include parent methods? + $this->includeParents = $includeParents; + + // Do we want to allow private methods? + $this->includePrivate = $includePrivate; + } + + /** + * The class to test may require a special config file before it can be + * instantiated. This method lets you set that file. + * + * @access public + * @param string $configPath + * @return void + */ + function setConfigFile($configFile) { + // Check that the file is readable + if (@is_readable($configFile)) { + $this->configFile = $configFile; + } else { + $this->_handleErrors($configFile . ' is not readable. Cannot create test class.'); + } + } + + /** + * Create the code that will be the skeleton of the test case. + * + * The test case must have a clss definition, one var, a constructor + * setUp, tearDown, and methods. Optionally and by default the code + * to run the test is added when the class is written to file. + * + * @access public + * @param none + * @return void + */ + function createTestClass() { + // Instantiate the object. + if (isset($this->configFile)) { + require_once $this->configFile; + } + + require_once $this->classPath; + + // Get the methods. + $classMethods = get_class_methods($this->className); + + // Remove the parent methods if needed. + if (!$this->includeParents) { + $parentMethods = get_class_methods(get_parent_class($this->className)); + + if (count($parentMethods)) { + $classMethods = array_diff($classMethods, $parentMethods); + } + } + + // Create the class definition, constructor, setUp and tearDown. + $this->_createDefinition(); + $this->_createConstructor(); + $this->_createSetUpTearDown(); + + if (count($classMethods)) { + // Foreach method create a test case. + foreach ($classMethods as $method) { + // Unless it is the constructor. + if (strcasecmp($this->className, $method) !== 0) { + // Check for private methods. + if (!$this->includePrivate && strpos($method, '_') === 0) { + continue; + } else { + $this->_createMethod($method); + } + } + } + } + + // Finis off the class. + $this->_finishClass(); + } + + /** + * Create the class definition. + * + * The definition consist of a header comment, require statment + * for getting the PHPUnit file, the actual class definition, + * and the definition of the class member variable. + * + * All of the code needed for the new class is stored in the + * testClass member. + * + * @access private + * @param none + * @return void + */ + function _createDefinition() { + // Create header comment. + $this->testClass = + "/**\n" . + " * PHPUnit test case for " . $this->className . "\n" . + " * \n" . + " * The method skeletons below need to be filled in with \n" . + " * real data so that the tests will run correctly. Replace \n" . + " * all EXPECTED_VAL and PARAM strings with real data. \n" . + " * \n" . + " * Created with PHPUnit_Skeleton on " . date('Y-m-d') . "\n" . + " */\n"; + + // Add the require statements. + $this->testClass .= "require_once 'PHPUnit.php';\n"; + + // Add the class definition and variable definition. + $this->testClass .= + "class " . $this->className . "Test extends PHPUnit_TestCase {\n\n" . + " var \$" . $this->className . ";\n\n"; + } + + /** + * Create the class constructor. (PHP4 style) + * + * The constructor simply calls the PHPUnit_TestCase method. + * This code is taken from the PHPUnit documentation. + * + * All of the code needed for the new class is stored in the + * testClass member. + * + * @access private + * @param none + * @return void + */ + function _createConstructor() { + // Create the test class constructor. + $this->testClass.= + " function " . $this->className . "Test(\$name)\n" . + " {\n" . + " \$this->PHPUnit_TestCase(\$name);\n" . + " }\n\n"; + } + + /** + * Create setUp and tearDown methods. + * + * The setUp method creates the instance of the object to test. + * The tearDown method releases the instance. + * This code is taken from the PHPUnit documentation. + * + * All of the code needed for the new class is stored in the + * testClass member. + * + * @access private + * @param none + * @return void + */ + function _createSetUpTearDown() { + // Create the setUp method. + $this->testClass .= + " function setUp()\n" . + " {\n"; + + if (isset($this->configFile)) { + $this->testClass .= + " require_once '" . $this->configFile . "';\n"; + } + + $this->testClass .= + " require_once '" . $this->classPath . "';\n" . + " \$this->" . $this->className . " =& new " . $this->className . "(PARAM);\n" . + " }\n\n"; + + // Create the tearDown method. + $this->testClass .= + " function tearDown()\n" . + " {\n" . + " unset(\$this->" . $this->className . ");\n" . + " }\n\n"; + } + + /** + * Create a basic skeleton for test methods. + * + * This code is taken from the PHPUnit documentation. + * + * All of the code needed for the new class is stored in the + * testClass member. + * + * @access private + * @param none + * @return void + */ + function _createMethod($methodName) { + // Create a test method. + $this->testClass .= + " function test" . $methodName . "()\n" . + " {\n" . + " \$result = \$this->" . $this->className . "->" . $methodName . "(PARAM);\n" . + " \$expected = EXPECTED_VAL;\n" . + " \$this->assertEquals(\$expected, \$result);\n" . + " }\n\n"; + } + + /** + * Add the closing brace needed for a proper class definition. + * + * All of the code needed for the new class is stored in the + * testClass member. + * + * @access private + * @param none + * @return void + */ + function _finishClass() { + // Close off the class. + $this->testClass.= "}\n"; + } + + /** + * Create the code that will actually run the test. + * + * This code is added by default so that the test can be run + * just by running the file. To have it not added pass false + * as the second parameter to the writeTestClass method. + * This code is taken from the PHPUnit documentation. + * + * All of the code needed for the new class is stored in the + * testClass member. + * + * @access private + * @param none + * @return void + */ + function _createTest() { + // Create a call to the test. + $test = + "// Running the test.\n" . + "\$suite = new PHPUnit_TestSuite('" . $this->className . "Test');\n" . + "\$result = PHPUnit::run(\$suite);\n" . + "echo \$result->toString();\n"; + + return $test; + } + + /** + * Write the test class to file. + * + * This will write the test class created using the createTestClass + * method to a file called Test.php. By default the file + * is written to the current directory and will have code to run + * the test appended to the bottom of the file. + * + * @access public + * @param string $destination The directory to write the file to. + * @param boolean $addTest Wheter to add the test running code. + * @return void + */ + function writeTestClass($destination = './', $addTest = TRUE) { + // Check for something to write to file. + if (!isset($this->testClass)) { + $this->_handleErrors('Noting to write.', PHPUS_WARNING); + return; + } + + // Open the destination file. + $fp = fopen($destination . $this->className . 'Test.php', 'w'); + fwrite($fp, "testClass); + + // Add the call to test the class in the file if we were asked to. + if ($addTest) { + fwrite($fp, $this->_createTest()); + } + + // Close the file. + fwrite($fp, "?>\n"); + fclose($fp); + } + + /** + * Error handler. + * + * This method should be rewritten to use the prefered error + * handling method. (PEAR_ErrorStack) + * + * @access private + * @param string $message The error message. + * @param integer $type An indication of the severity of the error. + * @return void Code may cause PHP to exit. + */ + function _handleErrors($message, $type = E_USER_ERROR) { + // For now just echo the message. + echo $message; + + // Check to see if we should quit. + if ($type == E_USER_ERROR) { + exit; + } + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/PHPUnit/TestCase.php b/instmess/lib/pear/PHPUnit/TestCase.php new file mode 100644 index 0000000..8d94f4a --- /dev/null +++ b/instmess/lib/pear/PHPUnit/TestCase.php @@ -0,0 +1,293 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: TestCase.php,v 1.21 2005/11/10 09:47:14 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +require_once 'PHPUnit/Assert.php'; +require_once 'PHPUnit/TestResult.php'; + +/** + * A TestCase defines the fixture to run multiple tests. + * + * To define a TestCase + * + * 1) Implement a subclass of PHPUnit_TestCase. + * 2) Define instance variables that store the state of the fixture. + * 3) Initialize the fixture state by overriding setUp(). + * 4) Clean-up after a test by overriding tearDown(). + * + * Each test runs in its own fixture so there can be no side effects + * among test runs. + * + * Here is an example: + * + * + * PHPUnit_TestCase($name); + * } + * + * function setUp() { + * $this->fValue1 = 2; + * $this->fValue2 = 3; + * } + * } + * ?> + * + * + * For each test implement a method which interacts with the fixture. + * Verify the expected results with assertions specified by calling + * assert with a boolean. + * + * + * assertTrue($this->fValue1 + $this->fValue2 == 5); + * } + * ?> + * + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_TestCase extends PHPUnit_Assert { + /** + * @var boolean + * @access private + */ + var $_failed = FALSE; + + /** + * The name of the test case. + * + * @var string + * @access private + */ + var $_name = ''; + + /** + * PHPUnit_TestResult object + * + * @var object + * @access private + */ + var $_result; + + /** + * Constructs a test case with the given name. + * + * @param string + * @access public + */ + function PHPUnit_TestCase($name = FALSE) { + if ($name !== FALSE) { + $this->setName($name); + } + } + + /** + * Counts the number of test cases executed by run(TestResult result). + * + * @return integer + * @access public + */ + function countTestCases() { + return 1; + } + + /** + * Gets the name of a TestCase. + * + * @return string + * @access public + */ + function getName() { + return $this->_name; + } + + /** + * Runs the test case and collects the results in a given TestResult object. + * + * @param object + * @return object + * @access public + */ + function run(&$result) { + $this->_result = &$result; + $this->_result->run($this); + + return $this->_result; + } + + /** + * Runs the bare test sequence. + * + * @access public + */ + function runBare() { + $this->setUp(); + $this->runTest(); + $this->tearDown(); + $this->pass(); + } + + /** + * Override to run the test and assert its state. + * + * @access protected + */ + function runTest() { + call_user_func( + array( + &$this, + $this->_name + ) + ); + } + + /** + * Sets the name of a TestCase. + * + * @param string + * @access public + */ + function setName($name) { + $this->_name = $name; + } + + /** + * Returns a string representation of the test case. + * + * @return string + * @access public + */ + function toString() { + return ''; + } + + /** + * Creates a default TestResult object. + * + * @return object + * @access protected + */ + function &createResult() { + return new PHPUnit_TestResult; + } + + /** + * Fails a test with the given message. + * + * @param string + * @access protected + */ + function fail($message = '') { + if (function_exists('debug_backtrace')) { + $trace = debug_backtrace(); + + if (isset($trace['1']['file'])) { + $message = sprintf( + "%s in %s:%s", + + $message, + $trace['1']['file'], + $trace['1']['line'] + ); + } + } + + $this->_result->addFailure($this, $message); + $this->_failed = TRUE; + } + + /** + * Passes a test. + * + * @access protected + */ + function pass() { + if (!$this->_failed) { + $this->_result->addPassedTest($this); + } + } + + /** + * Sets up the fixture, for example, open a network connection. + * This method is called before a test is executed. + * + * @access protected + * @abstract + */ + function setUp() { /* abstract */ } + + /** + * Tears down the fixture, for example, close a network connection. + * This method is called after a test is executed. + * + * @access protected + * @abstract + */ + function tearDown() { /* abstract */ } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/PHPUnit/TestDecorator.php b/instmess/lib/pear/PHPUnit/TestDecorator.php new file mode 100644 index 0000000..c5cbf1a --- /dev/null +++ b/instmess/lib/pear/PHPUnit/TestDecorator.php @@ -0,0 +1,156 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: TestDecorator.php,v 1.17 2005/11/10 09:47:14 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +require_once 'PHPUnit/TestCase.php'; +require_once 'PHPUnit/TestSuite.php'; + +if (!function_exists('is_a')) { + require_once 'PHP/Compat/Function/is_a.php'; +} + +/** + * A Decorator for Tests. + * + * Use TestDecorator as the base class for defining new + * test decorators. Test decorator subclasses can be introduced + * to add behaviour before or after a test is run. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_TestDecorator { + /** + * The Test to be decorated. + * + * @var object + * @access protected + */ + var $_test = NULL; + + /** + * Constructor. + * + * @param object + * @access public + */ + function PHPUnit_TestDecorator(&$test) { + if (is_object($test) && + (is_a($test, 'PHPUnit_TestCase') || + is_a($test, 'PHPUnit_TestSuite'))) { + + $this->_test = &$test; + } + } + + /** + * Runs the test and collects the + * result in a TestResult. + * + * @param object + * @access public + */ + function basicRun(&$result) { + $this->_test->run($result); + } + + /** + * Counts the number of test cases that + * will be run by this test. + * + * @return integer + * @access public + */ + function countTestCases() { + return $this->_test->countTestCases(); + } + + /** + * Returns the test to be run. + * + * @return object + * @access public + */ + function &getTest() { + return $this->_test; + } + + /** + * Runs the decorated test and collects the + * result in a TestResult. + * + * @param object + * @access public + * @abstract + */ + function run(&$result) { /* abstract */ } + + /** + * Returns a string representation of the test. + * + * @return string + * @access public + */ + function toString() { + return $this->_test->toString(); + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/PHPUnit/TestFailure.php b/instmess/lib/pear/PHPUnit/TestFailure.php new file mode 100644 index 0000000..ceca202 --- /dev/null +++ b/instmess/lib/pear/PHPUnit/TestFailure.php @@ -0,0 +1,130 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: TestFailure.php,v 1.13 2005/11/10 09:47:14 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +/** + * A TestFailure collects a failed test together with the caught exception. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_TestFailure { + /** + * @var object + * @access private + */ + var $_failedTest; + + /** + * @var string + * @access private + */ + var $_thrownException; + + /** + * Constructs a TestFailure with the given test and exception. + * + * @param object + * @param string + * @access public + */ + function PHPUnit_TestFailure(&$failedTest, &$thrownException) { + $this->_failedTest = &$failedTest; + $this->_thrownException = &$thrownException; + } + + /** + * Gets the failed test. + * + * @return object + * @access public + */ + function &failedTest() { + return $this->_failedTest; + } + + /** + * Gets the thrown exception. + * + * @return object + * @access public + */ + function &thrownException() { + return $this->_thrownException; + } + + /** + * Returns a short description of the failure. + * + * @return string + * @access public + */ + function toString() { + return sprintf( + "TestCase %s->%s() failed: %s\n", + + get_class($this->_failedTest), + $this->_failedTest->getName(), + $this->_thrownException + ); + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/PHPUnit/TestListener.php b/instmess/lib/pear/PHPUnit/TestListener.php new file mode 100644 index 0000000..142d3de --- /dev/null +++ b/instmess/lib/pear/PHPUnit/TestListener.php @@ -0,0 +1,162 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: TestListener.php,v 1.12 2005/11/10 09:47:15 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +/** + * A Listener for test progress. + * + * Here is an example: + * + * + * PHPUnit_TestCase($name); + * } + * + * function setUp() { + * $this->fValue1 = 2; + * $this->fValue2 = 3; + * } + * + * function testAdd() { + * $this->assertTrue($this->fValue1 + $this->fValue2 == 4); + * } + * } + * + * class MyListener extends PHPUnit_TestListener { + * function addError(&$test, &$t) { + * print "MyListener::addError() called.\n"; + * } + * + * function addFailure(&$test, &$t) { + * print "MyListener::addFailure() called.\n"; + * } + * + * function endTest(&$test) { + * print "MyListener::endTest() called.\n"; + * } + * + * function startTest(&$test) { + * print "MyListener::startTest() called.\n"; + * } + * } + * + * $suite = new PHPUnit_TestSuite; + * $suite->addTest(new MathTest('testAdd')); + * + * $result = new PHPUnit_TestResult; + * $result->addListener(new MyListener); + * + * $suite->run($result); + * print $result->toString(); + * ?> + * + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_TestListener { + /** + * An error occurred. + * + * @param object + * @param object + * @access public + * @abstract + */ + function addError(&$test, &$t) { /*abstract */ } + + /** + * A failure occurred. + * + * @param object + * @param object + * @access public + * @abstract + */ + function addFailure(&$test, &$t) { /*abstract */ } + + /** + * A test ended. + * + * @param object + * @access public + * @abstract + */ + function endTest(&$test) { /*abstract */ } + + /** + * A test started. + * + * @param object + * @access public + * @abstract + */ + function startTest(&$test) { /*abstract */ } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/PHPUnit/TestResult.php b/instmess/lib/pear/PHPUnit/TestResult.php new file mode 100644 index 0000000..7d1e3be --- /dev/null +++ b/instmess/lib/pear/PHPUnit/TestResult.php @@ -0,0 +1,347 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: TestResult.php,v 1.18 2005/11/10 09:47:15 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +require_once 'PHPUnit/TestFailure.php'; +require_once 'PHPUnit/TestListener.php'; + +if (!function_exists('is_a')) { + require_once 'PHP/Compat/Function/is_a.php'; +} + +/** + * A TestResult collects the results of executing a test case. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_TestResult { + /** + * @var array + * @access protected + */ + var $_errors = array(); + + /** + * @var array + * @access protected + */ + var $_failures = array(); + + /** + * @var array + * @access protected + */ + var $_listeners = array(); + + /** + * @var array + * @access protected + */ + var $_passedTests = array(); + + /** + * @var integer + * @access protected + */ + var $_runTests = 0; + + /** + * @var boolean + * @access private + */ + var $_stop = FALSE; + + /** + * Adds an error to the list of errors. + * The passed in exception caused the error. + * + * @param object + * @param object + * @access public + */ + function addError(&$test, &$t) { + $this->_errors[] = new PHPUnit_TestFailure($test, $t); + + for ($i = 0; $i < sizeof($this->_listeners); $i++) { + $this->_listeners[$i]->addError($test, $t); + } + } + + /** + * Adds a failure to the list of failures. + * The passed in exception caused the failure. + * + * @param object + * @param object + * @access public + */ + function addFailure(&$test, &$t) { + $this->_failures[] = new PHPUnit_TestFailure($test, $t); + + for ($i = 0; $i < sizeof($this->_listeners); $i++) { + $this->_listeners[$i]->addFailure($test, $t); + } + } + + /** + * Registers a TestListener. + * + * @param object + * @access public + */ + function addListener(&$listener) { + if (is_object($listener) && + is_a($listener, 'PHPUnit_TestListener')) { + $this->_listeners[] = &$listener; + } + } + + /** + * Adds a passed test to the list of passed tests. + * + * @param object + * @access public + */ + function addPassedTest(&$test) { + $this->_passedTests[] = &$test; + } + + /** + * Informs the result that a test was completed. + * + * @param object + * @access public + */ + function endTest(&$test) { + for ($i = 0; $i < sizeof($this->_listeners); $i++) { + $this->_listeners[$i]->endTest($test); + } + } + + /** + * Gets the number of detected errors. + * + * @return integer + * @access public + */ + function errorCount() { + return sizeof($this->_errors); + } + + /** + * Returns an Enumeration for the errors. + * + * @return array + * @access public + */ + function &errors() { + return $this->_errors; + } + + /** + * Gets the number of detected failures. + * + * @return integer + * @access public + */ + function failureCount() { + return sizeof($this->_failures); + } + + /** + * Returns an Enumeration for the failures. + * + * @return array + * @access public + */ + function &failures() { + return $this->_failures; + } + + /** + * Returns an Enumeration for the passed tests. + * + * @return array + * @access public + */ + function &passedTests() { + return $this->_passedTests; + } + + /** + * Unregisters a TestListener. + * This requires the Zend Engine 2 (to work properly). + * + * @param object + * @access public + */ + function removeListener(&$listener) { + for ($i = 0; $i < sizeof($this->_listeners); $i++) { + if ($this->_listeners[$i] === $listener) { + unset($this->_listeners[$i]); + } + } + } + + /** + * Runs a TestCase. + * + * @param object + * @access public + */ + function run(&$test) { + $this->startTest($test); + $this->_runTests++; + $test->runBare(); + $this->endTest($test); + } + + /** + * Gets the number of run tests. + * + * @return integer + * @access public + */ + function runCount() { + return $this->_runTests; + } + + /** + * Checks whether the test run should stop. + * + * @access public + */ + function shouldStop() { + return $this->_stop; + } + + /** + * Informs the result that a test will be started. + * + * @param object + * @access public + */ + function startTest(&$test) { + for ($i = 0; $i < sizeof($this->_listeners); $i++) { + $this->_listeners[$i]->startTest($test); + } + } + + /** + * Marks that the test run should stop. + * + * @access public + */ + function stop() { + $this->_stop = TRUE; + } + + /** + * Returns a HTML representation of the test result. + * + * @return string + * @access public + */ + function toHTML() { + return '
' . htmlspecialchars($this->toString()) . '
'; + } + + /** + * Returns a text representation of the test result. + * + * @return string + * @access public + */ + function toString() { + $result = ''; + + foreach ($this->_passedTests as $passedTest) { + $result .= sprintf( + "TestCase %s->%s() passed\n", + + get_class($passedTest), + $passedTest->getName() + ); + } + + foreach ($this->_failures as $failedTest) { + $result .= $failedTest->toString(); + } + + return $result; + } + + /** + * Returns whether the entire test was successful or not. + * + * @return boolean + * @access public + */ + function wasSuccessful() { + if (empty($this->_errors) && empty($this->_failures)) { + return TRUE; + } else { + return FALSE; + } + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/PHPUnit/TestSuite.php b/instmess/lib/pear/PHPUnit/TestSuite.php new file mode 100644 index 0000000..2121613 --- /dev/null +++ b/instmess/lib/pear/PHPUnit/TestSuite.php @@ -0,0 +1,262 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * 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. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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, STRIC + * 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. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version CVS: $Id: TestSuite.php,v 1.17 2005/11/10 09:47:15 sebastian Exp $ + * @link http://pear.php.net/package/PHPUnit + * @since File available since Release 1.0.0 + */ + +require_once 'PHPUnit/TestCase.php'; + +/** + * A TestSuite is a Composite of Tests. It runs a collection of test cases. + * + * Here is an example using the dynamic test definition. + * + * + * addTest(new MathTest('testPass')); + * ?> + * + * + * Alternatively, a TestSuite can extract the tests to be run automatically. + * To do so you pass the classname of your TestCase class to the TestSuite + * constructor. + * + * + * + * + * + * This constructor creates a suite with all the methods starting with + * "test" that take no arguments. + * + * @category Testing + * @package PHPUnit + * @author Sebastian Bergmann + * @copyright 2002-2005 Sebastian Bergmann + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version Release: 1.3.2 + * @link http://pear.php.net/package/PHPUnit + * @since Class available since Release 1.0.0 + */ +class PHPUnit_TestSuite { + /** + * The name of the test suite. + * + * @var string + * @access private + */ + var $_name = ''; + + /** + * The tests in the test suite. + * + * @var array + * @access private + */ + var $_tests = array(); + + /** + * Constructs a TestSuite. + * + * @param mixed + * @access public + */ + function PHPUnit_TestSuite($test = FALSE) { + if ($test !== FALSE) { + $this->setName($test); + $this->addTestSuite($test); + } + } + + /** + * Adds a test to the suite. + * + * @param object + * @access public + */ + function addTest(&$test) { + $this->_tests[] = &$test; + } + + /** + * Adds the tests from the given class to the suite. + * + * @param string + * @access public + */ + function addTestSuite($testClass) { + if (class_exists($testClass)) { + $methods = get_class_methods($testClass); + $parentClasses = array(strtolower($testClass)); + $parentClass = $testClass; + + while(is_string($parentClass = get_parent_class($parentClass))) { + $parentClasses[] = $parentClass; + } + + foreach ($methods as $method) { + if (substr($method, 0, 4) == 'test' && + !in_array($method, $parentClasses)) { + $this->addTest(new $testClass($method)); + } + } + } + } + + /** + * Counts the number of test cases that will be run by this test. + * + * @return integer + * @access public + */ + function countTestCases() { + $count = 0; + + foreach ($this->_tests as $test) { + $count += $test->countTestCases(); + } + + return $count; + } + + /** + * Returns the name of the suite. + * + * @return string + * @access public + */ + function getName() { + return $this->_name; + } + + /** + * Runs the tests and collects their result in a TestResult. + * + * @param object + * @access public + */ + function run(&$result) { + for ($i = 0; $i < sizeof($this->_tests) && !$result->shouldStop(); $i++) { + $this->_tests[$i]->run($result); + } + } + + /** + * Runs a test. + * + * @param object + * @param object + * @access public + */ + function runTest(&$test, &$result) { + $test->run($result); + } + + /** + * Sets the name of the suite. + * + * @param string + * @access public + */ + function setName($name) { + $this->_name = $name; + } + + /** + * Returns the test at the given index. + * + * @param integer + * @return object + * @access public + */ + function &testAt($index) { + if (isset($this->_tests[$index])) { + return $this->_tests[$index]; + } else { + return FALSE; + } + } + + /** + * Returns the number of tests in this suite. + * + * @return integer + * @access public + */ + function testCount() { + return sizeof($this->_tests); + } + + /** + * Returns the tests as an enumeration. + * + * @return array + * @access public + */ + function &tests() { + return $this->_tests; + } + + /** + * Returns a string representation of the test suite. + * + * @return string + * @access public + */ + function toString() { + return ''; + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * c-hanging-comment-ender-p: nil + * End: + */ +?> diff --git a/instmess/lib/pear/pear.sh b/instmess/lib/pear/pear.sh new file mode 100644 index 0000000..f9164c7 --- /dev/null +++ b/instmess/lib/pear/pear.sh @@ -0,0 +1 @@ +pear -c ./pearrc $1 $2 $3 $4 diff --git a/instmess/lib/pear/pearrc b/instmess/lib/pear/pearrc new file mode 100644 index 0000000..bc7560e --- /dev/null +++ b/instmess/lib/pear/pearrc @@ -0,0 +1,2 @@ +#PEAR_Config 0.9 +a:5:{s:8:"test_dir";s:7:"./tests";s:8:"data_dir";s:6:"./data";s:7:"php_dir";s:1:".";s:7:"doc_dir";s:6:"./docs";s:10:"__channels";a:2:{s:12:"pecl.php.net";a:0:{}s:5:"__uri";a:0:{}}} \ No newline at end of file diff --git a/instmess/lib/utf8/utf8_char2byte_pos.php b/instmess/lib/utf8/utf8_char2byte_pos.php new file mode 100644 index 0000000..cd1b6ae --- /dev/null +++ b/instmess/lib/utf8/utf8_char2byte_pos.php @@ -0,0 +1,47 @@ + + */ +if (!function_exists('utf8_char2byte_pos')) { + function utf8_char2byte_pos($str,$pos) { + $n = 0; // number of characters found + $p = abs($pos); // number of characters wanted + + if ($pos >= 0) { + $i = 0; + $d = 1; + } else { + $i = strlen($str)-1; + $d = -1; + } + + for( ; strlen($str{$i}) && $n<$p; $i+=$d) { + $c = (int)ord($str{$i}); + if (!($c & 0x80)) // single-byte (0xxxxxx) + $n++; + elseif (($c & 0xC0) == 0xC0) // multi-byte starting byte (11xxxxxx) + $n++; + } + if (!strlen($str{$i})) return false; // offset beyond string length + + if ($pos >= 0) { + // skip trailing multi-byte data bytes + while ((ord($str{$i}) & 0x80) && !(ord($str{$i}) & 0x40)) { $i++; } + } else { + // correct offset + $i++; + } + + return $i; + } +} + +?> \ No newline at end of file diff --git a/instmess/lib/utf8/utf8_strlen.php b/instmess/lib/utf8/utf8_strlen.php new file mode 100644 index 0000000..764f335 --- /dev/null +++ b/instmess/lib/utf8/utf8_strlen.php @@ -0,0 +1,26 @@ + + */ +if (!function_exists('utf8_strlen')) { + function utf8_strlen($str) { + $n=0; + for($i=0; isset($str{$i}) && strlen($str{$i})>0; $i++) { + $c = ord($str{$i}); + if (!($c & 0x80)) // single-byte (0xxxxxx) + $n++; + elseif (($c & 0xC0) == 0xC0) // multi-byte starting byte (11xxxxxx) + $n++; + } + return $n; + } +} + +?> \ No newline at end of file diff --git a/instmess/lib/utf8/utf8_substr.php b/instmess/lib/utf8/utf8_substr.php new file mode 100644 index 0000000..214d3c7 --- /dev/null +++ b/instmess/lib/utf8/utf8_substr.php @@ -0,0 +1,43 @@ + + */ +if (!function_exists('utf8_substr')) { + function utf8_substr($str,$start,$len=null) { + if (!strcmp($len,'0')) return ''; + + $byte_start = @utf8_char2byte_pos($str,$start); + if ($byte_start === false) { + if ($start > 0) { + return false; // $start outside string length + } else { + $start = 0; + } + } + + $str = substr($str,$byte_start); + + if ($len!=null) { + $byte_end = @utf8_char2byte_pos($str,$len); + if ($byte_end === false) // $len outside actual string length + return $len<0 ? '' : $str; // When length is less than zero and exceeds, then we return blank string. + else + return substr($str,0,$byte_end); + } + else return $str; + } +} + +?> \ No newline at end of file diff --git a/instmess/misc/bulle.png b/instmess/misc/bulle.png new file mode 100644 index 0000000..fd28809 Binary files /dev/null and b/instmess/misc/bulle.png differ diff --git a/instmess/misc/checkmd5 b/instmess/misc/checkmd5 new file mode 100644 index 0000000..4e10eda --- /dev/null +++ b/instmess/misc/checkmd5 @@ -0,0 +1,55 @@ +#!/bin/sh + +if [ $# -le 1 ] +then + echo "Usage: `basename $0` phpfreechat_path checkmd5_output_filename" + echo "exempel: `basename $0` ~/pfc/misc/phpfreechat ~/pfc/misc/phpfreechat/checkmd5.php" + exit; +fi + +PFC_PATH=$1 +DST=$2 +TMP=/tmp/checkmd5.php + +if ( test -f $DST ) +then + echo "$DST should not exist. Please delete this file." + exit; +fi + +if ( test ! -f $PFC_PATH/version.txt ) +then + echo "$PFC_PATH/version.txt doesn't exist." + exit; +fi + +echo "--> Creating $DST" + +cd $PFC_PATH +echo " $TMP +echo '$files_ok = array();' >> $TMP +echo '$files_ko = array();' >> $TMP +for f in `find . -type f` +do + if ( test $f != "./index.php" ) + then + sum=`md5sum $f | sed "s/\s.*$//g"` + echo 'if (md5(file_get_contents("'$f'")) == "'$sum'")' >> $TMP + echo ' $files_ok[] = "ok - '$f'\n";' >> $TMP + echo 'else' >> $TMP + echo ' $files_ko[] = "corrupted - '$f' (please replace this file by a correct one)\n";' >> $TMP + fi +done + +echo 'echo "

Checking phpfreechat files validity

";' >> $TMP +echo 'echo "
\n";' >> $TMP
+echo '$arr = array_merge($files_ko,$files_ok);' >> $TMP
+echo 'foreach($arr as $file)' >> $TMP
+echo '  echo $file;' >> $TMP
+echo 'echo "
\n";' >> $TMP +echo "?>" >> $TMP +cd - >/dev/null + +cp $TMP $DST + +echo "---> Done, $DST created" diff --git a/instmess/misc/clock-off.png b/instmess/misc/clock-off.png new file mode 100644 index 0000000..b818d19 Binary files /dev/null and b/instmess/misc/clock-off.png differ diff --git a/instmess/misc/clock-on.png b/instmess/misc/clock-on.png new file mode 100644 index 0000000..983ed47 Binary files /dev/null and b/instmess/misc/clock-on.png differ diff --git a/instmess/misc/color-off.png b/instmess/misc/color-off.png new file mode 100644 index 0000000..ac486ea Binary files /dev/null and b/instmess/misc/color-off.png differ diff --git a/instmess/misc/color-on.png b/instmess/misc/color-on.png new file mode 100644 index 0000000..d665f0c Binary files /dev/null and b/instmess/misc/color-on.png differ diff --git a/instmess/misc/createwebinstaller.php b/instmess/misc/createwebinstaller.php new file mode 100644 index 0000000..8c1cfb7 --- /dev/null +++ b/instmess/misc/createwebinstaller.php @@ -0,0 +1,21 @@ +dataDir($phpinstaller_path.'/engine_data'); +$phpi->appName = 'phpFreeChat'; +$phpi->appVersion = $version; +$phpi->addMetaFile('ss',$phpinstaller_path.'/createinstaller/data/installer.css','text/css') + or die('Can not find stylesheet'); +$phpi->ignore[] = '.svn'; +$phpi->addPage('Pre-Install Check',file_get_contents($phpinstaller_path.'/createinstaller/data/precheck.inc')); +$phpi->addInstallerPages(); +$phpi->addPath($pfcpath); +$phpi->generate($archivename); + +?> \ No newline at end of file diff --git a/instmess/misc/doc-archi1.svg b/instmess/misc/doc-archi1.svg new file mode 100644 index 0000000..7543b6e --- /dev/null +++ b/instmess/misc/doc-archi1.svg @@ -0,0 +1,950 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + Command + + + + + + + + + + + HTTP(AJAX) + PHP + APACHE + WEB BROWSER(XHTML+JS) + + + + Parser + Proxy chain + + + connect + + + + join + + + + nick + + + + /connect + + + + /join myroom + + + + /nick newnick + + + + + + + + + + censor + + log + yourproxy + + + ... + + ... + ... + + + yourcmd + + + + + + Command + + + + Server + + + + Client + + + + + + + Container + + + + + + Container + + + + pfcContainer + + ->readMeta(...)->writeMeta(...)->rmMeta(...) + + + + file + + ->readMeta(...)->writeMeta(...)->rmMeta(...) + + + + + + mysql + + ->readMeta(...)->writeMeta(...)->rmMeta(...) + + + + + yourcontainer + + ->readMeta(...)->writeMeta(...)->rmMeta(...) + + + + response + + diff --git a/instmess/misc/generate-doc.inc.php b/instmess/misc/generate-doc.inc.php new file mode 100644 index 0000000..7c1613e --- /dev/null +++ b/instmess/misc/generate-doc.inc.php @@ -0,0 +1,82 @@ + 'tcp://proxyout.inist.fr:8080', 'request_fulluri' => true ); + $ct = stream_context_create($ct_params); + $data = file_get_contents($f, false, $ct); + + $offset = 0; + if (preg_match('/class pfcGlobalConfig/',$data,$matches, PREG_OFFSET_CAPTURE, $offset)) + { + $offset_start = $matches[0][1]; + } + if (preg_match('/function pfcGlobalConfig/', $data, $matches, PREG_OFFSET_CAPTURE, $offset)) + { + $offset_end = $matches[0][1]; + } + + $offset = $offset_start; + $plist = array(); + $continue = true; + while ($offset < $offset_end) + { + $p = array(); + + // search for the begining of the description + if (preg_match('/\/\*\*/', $data, $matches1, PREG_OFFSET_CAPTURE, $offset)) + $offset1 = $matches1[0][1]; + else + $offset = $offset_end; + + // search for the end of the description + if ($offset1 < $offset_end && + preg_match('/\*\//', $data, $matches3, PREG_OFFSET_CAPTURE, $offset)) + { + $offset3 = $matches3[0][1]; + + // search for the parameter description + $offset2 = $offset; + $p['desc'] = ''; + while($offset2 < $offset3) + { + if (preg_match('/\s+\*\s+(.*)/', $data, $matches2, PREG_OFFSET_CAPTURE, $offset)) + { + $offset2 = $matches2[1][1]; + if ($offset2 < $offset3) + { + $offset = $offset2; + $p['desc'] .= ' '.$matches2[1][0]; + } + } + else + break; + } + $p['desc'] = trim($p['desc']); + + // search for the parameter name/default value + if (preg_match('/var\s+\$([a-z_]+)\s+=\s+(.*);/is', $data, $matches4, PREG_OFFSET_CAPTURE, $offset)) + { + $offset = $matches4[1][1]; + $p['name'] = $matches4[1][0]; + $p['value'] = $matches4[2][0]; + } + else + $offset = $offset_end; + } + else + $offset = $offset_end; + + if (count($p) > 0) $plist[] = $p; + } + return $plist; +} + +?> diff --git a/instmess/misc/i18n_update.php b/instmess/misc/i18n_update.php new file mode 100644 index 0000000..560313b --- /dev/null +++ b/instmess/misc/i18n_update.php @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/instmess/misc/login.png b/instmess/misc/login.png new file mode 100644 index 0000000..0a1643f Binary files /dev/null and b/instmess/misc/login.png differ diff --git a/instmess/misc/login.svg b/instmess/misc/login.svg new file mode 100644 index 0000000..98b6407 --- /dev/null +++ b/instmess/misc/login.svg @@ -0,0 +1,134 @@ + + + + + + + + Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004) + + + + + + + + + + + + + + Danny Allen + + + + + Danny Allen + + + + image/svg+xml + + + + + en + + + + + + + + + + + + + + + + diff --git a/instmess/misc/logo.svg b/instmess/misc/logo.svg new file mode 100644 index 0000000..163797f --- /dev/null +++ b/instmess/misc/logo.svg @@ -0,0 +1,174 @@ + + + + + + + + + image/svg+xml + + + + + + + + + phpFreeChat + + + + + phpFreeChat + + + + + + diff --git a/instmess/misc/logo_80x15.gif b/instmess/misc/logo_80x15.gif new file mode 100644 index 0000000..cbe1381 Binary files /dev/null and b/instmess/misc/logo_80x15.gif differ diff --git a/instmess/misc/logo_80x15.png b/instmess/misc/logo_80x15.png new file mode 100644 index 0000000..2fec18b Binary files /dev/null and b/instmess/misc/logo_80x15.png differ diff --git a/instmess/misc/logo_88x31.gif b/instmess/misc/logo_88x31.gif new file mode 100644 index 0000000..e8b183a Binary files /dev/null and b/instmess/misc/logo_88x31.gif differ diff --git a/instmess/misc/logo_88x31.png b/instmess/misc/logo_88x31.png new file mode 100644 index 0000000..78c0895 Binary files /dev/null and b/instmess/misc/logo_88x31.png differ diff --git a/instmess/misc/logout.png b/instmess/misc/logout.png new file mode 100644 index 0000000..b4d43b3 Binary files /dev/null and b/instmess/misc/logout.png differ diff --git a/instmess/misc/logout.svg b/instmess/misc/logout.svg new file mode 100644 index 0000000..8aab4db --- /dev/null +++ b/instmess/misc/logout.svg @@ -0,0 +1,118 @@ + + + + + + + + Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004) + + + + + + + + + + + + + + Danny Allen + + + + + Danny Allen + + + + image/svg+xml + + + + + en + + + + + + + + + + + + + + diff --git a/instmess/misc/png2gif.sh b/instmess/misc/png2gif.sh new file mode 100644 index 0000000..47d5d82 --- /dev/null +++ b/instmess/misc/png2gif.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +for img in `ls *.png` +do + imgbase=`echo $img | sed "s/\.png//g"` + convert $img $imgbase.gif +done \ No newline at end of file diff --git a/instmess/misc/sendSource b/instmess/misc/sendSource new file mode 100644 index 0000000..6ee35f4 --- /dev/null +++ b/instmess/misc/sendSource @@ -0,0 +1,10 @@ +#!/bin/sh + +NAME_TGZ=phpfreechat-`cat ../version.txt`.tar.gz +NAME_ZIP=phpfreechat-`cat ../version.txt`.zip +NAME_SETUP=phpfreechat-`cat ../version.txt`-setup.php + +#scp ./$NAME_ZIP ./$NAME_TGZ micropolia@phpfreechat.net:/home/micropolia/svn/phpfreechat/prod/www/download/ +lftp -c "mput -O ftp://upload.sourceforge.net/incoming/ $NAME_TGZ" +lftp -c "mput -O ftp://upload.sourceforge.net/incoming/ $NAME_ZIP" +lftp -c "mput -O ftp://upload.sourceforge.net/incoming/ $NAME_SETUP" diff --git a/instmess/misc/tabs.svg b/instmess/misc/tabs.svg new file mode 100644 index 0000000..fec3ca0 --- /dev/null +++ b/instmess/misc/tabs.svg @@ -0,0 +1,582 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/instmess/misc/tarSource b/instmess/misc/tarSource new file mode 100644 index 0000000..e24b1d9 --- /dev/null +++ b/instmess/misc/tarSource @@ -0,0 +1,64 @@ +#!/bin/sh + +NAME=phpfreechat-`cat ../version.txt` +PFCSETUPNAME=pfcsetup-`cat ../version.txt` + +echo "-- creating archive $NAME --" +echo "-> copying files" +rm -rf ./$NAME +svn export .. ./$NAME +rm -rf ./$NAME/contrib +rm -rf ./$NAME/admin +rm -rf ./$NAME/lib/pear/.registry + +#echo "-> downloading documentation" +#wget http://www.phpfreechat.net/pages/fr/install.html -q -O ./$NAME/install.fr.html +#wget http://www.phpfreechat.net/pages/en/install.html -q -O ./$NAME/install.en.html +#wget http://www.phpfreechat.net/pages/fr/faq.html -q -O ./$NAME/faq.fr.html +#wget http://www.phpfreechat.net/pages/en/faq.html -q -O ./$NAME/faq.en.html +#wget http://www.phpfreechat.net/pages/fr/overview.html -q -O ./$NAME/overview.fr.html +#wget http://www.phpfreechat.net/pages/en/overview.html -q -O ./$NAME/overview.en.html +#wget http://www.phpfreechat.net/pages/ar/overview.html -q -O ./$NAME/overview.ar.html +#wget http://www.phpfreechat.net/pages/es/overview.html -q -O ./$NAME/overview.es.html +#wget http://www.phpfreechat.net/pages/zh/overview.html -q -O ./$NAME/overview.zh.html +#wget http://www.phpfreechat.net/pages/fr/customize.html -q -O ./$NAME/customize.fr.html +#wget http://www.phpfreechat.net/pages/en/customize.html -q -O ./$NAME/customize.en.html +#wget http://www.phpfreechat.net/pages/fr/changelog.html -q -O ./$NAME/changelog.fr.html +#wget http://www.phpfreechat.net/pages/en/changelog.html -q -O ./$NAME/changelog.en.html + +echo "-> creating checkmd5.php file" +./checkmd5 ./$NAME ./$NAME/checkmd5.php + +echo "-> creating $NAME.tar.gz" +tar czfp $NAME.tar.gz ./$NAME +echo "-> creating $NAME.zip" +zip -Tq9r $NAME.zip ./$NAME +echo "-> creating $NAME-setup.php" +php ./createwebinstaller.php `cat ../version.txt` > /dev/null + +#echo "-> creating $PFCSETUPNAME.tar.gz" +#rm -rf ./$PFCSETUPNAME +#svn export ../contrib/pfcInstaller2 $PFCSETUPNAME +#rm -rf ./$PFCSETUPNAME/.registry +#rm -rf ./$PFCSETUPNAME/.depdblock +#rm -rf ./$PFCSETUPNAME/.lock +#rm -rf ./$PFCSETUPNAME/.channels +#rm -rf ./$PFCSETUPNAME/tmp +#rm -rf ./$PFCSETUPNAME/bin +#rm -rf ./$PFCSETUPNAME/PEAR +#rm -rf ./$PFCSETUPNAME/System.php +#rm -rf ./$PFCSETUPNAME/peclcmd.php +#rm -rf ./$PFCSETUPNAME/pearcmd.php +#rm -rf ./$PFCSETUPNAME/pear.sh +#rm -rf ./$PFCSETUPNAME/pearrc +#rm -rf ./$PFCSETUPNAME/OS +#echo "$NAME.tar.gz" > $PFCSETUPNAME/archivename +#echo `pwd` >> $PFCSETUPNAME/mirrors +#tar czfp $PFCSETUPNAME.tar.gz ./$PFCSETUPNAME +#echo "-> creating $PFCSETUPNAME.zip" +#zip -Tq9r $PFCSETUPNAME.zip ./$PFCSETUPNAME +#rm -rf ./$PFCSETUPNAME + +echo "-> removing temporary files" +rm -rf ./$NAME +echo "-- creating archive $NAME, done --" diff --git a/instmess/src/commands/asknick.class.php b/instmess/src/commands/asknick.class.php new file mode 100644 index 0000000..eb332df --- /dev/null +++ b/instmess/src/commands/asknick.class.php @@ -0,0 +1,42 @@ +frozen_nick) + { + // assign a random nick + $cmdp = $p; + $cmdp["param"] = $nicktochange."".rand(1,1000); + $cmd =& pfcCommand::Factory("nick"); + $cmd->run($xml_reponse, $cmdp); + } + else + { + if ($nicktochange == "") + { + $nicktochange = $u->nick; + $msg = _pfc("Please enter your nickname"); + } + else + $msg = "'".$nicktochange."' is used, please choose another nickname."; + $xml_reponse->script("var newnick = prompt('".addslashes($msg)."', '".addslashes($nicktochange)."'); if (newnick) pfc.sendRequest('/nick \"'+newnick+'\"');"); + } + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/ban.class.php b/instmess/src/commands/ban.class.php new file mode 100644 index 0000000..b425e8a --- /dev/null +++ b/instmess/src/commands/ban.class.php @@ -0,0 +1,73 @@ + 2) + for ($x=2;$xchannels[$recipientid]["name"]; + + if ($nick == '') + { + // error + $cmdp = $p; + $cmdp["param"] = _pfc("Missing parameter"); + $cmdp["param"] .= " (".$this->usage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + + $ct =& pfcContainer::Instance(); + $nickidtoban = $ct->getNickId($nick); + + // notify all the channel + $cmdp = $p; + $cmdp["param"] = _pfc("%s banished from %s by %s", $nick, $channame, $sender); + $cmdp["flag"] = 4; + $cmd =& pfcCommand::Factory("notice"); + $cmd->run($xml_reponse, $cmdp); + + // kick the user (maybe in the future, it will exists a /kickban command) + $cmdp = $p; + $cmdp["params"] = array(); + $cmdp["params"][] = $nick; // nickname to kick + $cmdp["params"][] = $reason; // reason + $cmd =& pfcCommand::Factory("kick"); + $cmd->run($xml_reponse, $cmdp); + + + // update the recipient banlist + $banlist = $ct->getChanMeta($recipient, 'banlist_nickid'); + if ($banlist == NULL) + $banlist = array(); + else + $banlist = unserialize($banlist); + $banlist[] = $nickidtoban; // append the nickid to the banlist + $ct->setChanMeta($recipient, 'banlist_nickid', serialize($banlist)); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/banlist.class.php b/instmess/src/commands/banlist.class.php new file mode 100644 index 0000000..09b3f62 --- /dev/null +++ b/instmess/src/commands/banlist.class.php @@ -0,0 +1,46 @@ + + */ +class pfcCommand_banlist extends pfcCommand +{ + var $desc = "This command list the banished users on the given channel"; + + function run(&$xml_reponse, $p) + { + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + + $ct =& pfcContainer::Instance(); + $banlist = $ct->getChanMeta($p["recipient"], 'banlist_nickid'); + + if ($banlist == NULL) $banlist = array(); else $banlist = unserialize($banlist); + $msg = ""; + $msg .= "

"._pfc("The banished user list is:")."

"; + if (count($banlist)>0) + { + $msg .= "
    "; + foreach($banlist as $b) + { + $n = $ct->getNickname($b); + $msg .= "
  • ".$n."
  • "; + } + $msg .= "
"; + } + else + { + $msg .= "

("._pfc("Empty").")

"; + } + $msg .= "

"._pfc("'/unban {nickname}' will unban the user identified by {nickname}")."

"; + $msg .= "

"._pfc("'/unban all' will unban all the users on this channel")."

"; + + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', '".addslashes($msg)."');"); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/clear.class.php b/instmess/src/commands/clear.class.php new file mode 100644 index 0000000..b53ba27 --- /dev/null +++ b/instmess/src/commands/clear.class.php @@ -0,0 +1,16 @@ +script("pfc.handleResponse('".$this->name."', 'ok', '');"); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/connect.class.php b/instmess/src/commands/connect.class.php new file mode 100644 index 0000000..1595245 --- /dev/null +++ b/instmess/src/commands/connect.class.php @@ -0,0 +1,138 @@ +_resetChannelIdentifier($clientid); + + // check if the user is alone on the server, and give it the admin status if yes + $isadmin = $ct->getUserMeta($u->nickid, 'isadmin'); + if ($isadmin == NULL) + $isadmin = $c->isadmin; + if ($c->firstisadmin && !$isadmin) + { + $users = $ct->getOnlineNick(NULL); + if (isset($users["nickid"]) && + (count($users["nickid"]) == 0 || (count($users["nickid"]) == 1 && $users["nickid"][0] == $u->nickid))) + $isadmin = true; + } + + // create the nickid and setup some user meta + $nickid = $u->nickid; + $ct->joinChan($nickid, NULL); // join the server + // store the user ip + $ip = ( $c->get_ip_from_xforwardedfor && isset($_SERVER["HTTP_X_FORWARDED_FOR"])) ? + $_SERVER["HTTP_X_FORWARDED_FOR"] : + $_SERVER["REMOTE_ADDR"]; + if ($ip == "::1") $ip = "127.0.0.1"; // fix for konqueror & localhost + $ct->setUserMeta($nickid, 'ip', $ip); + // store the admin flag + $ct->setUserMeta($nickid, 'isadmin', $isadmin); + // store the customized nick metadata + foreach($c->nickmeta as $k => $v) + $ct->setUserMeta($nickid, $k, $v); + + // run the /nick command to assign the user nick + $cmdp = array(); + $cmdp["param"] = $nick; + $cmd =& pfcCommand::Factory('nick'); + $ret = $cmd->run($xml_reponse, $cmdp); + if ($ret) + { + $chanlist = (count($u->channels) == 0) ? $c->channels : $u->getChannelNames(); + for($i = 0 ; $i < count($chanlist) ; $i++) + { + $cmdp = array(); + $cmdp["param"] = $chanlist[$i]; + $cmd =& pfcCommand::Factory( $i < count($chanlist)-1 || !$joinoldchan ? 'join2' : 'join' ); + $cmd->run($xml_reponse, $cmdp); + } + + $pvlist = (count($u->privmsg) == 0) ? $c->privmsg : $u->getPrivMsgNames(); + for($i = 0 ; $i < count($pvlist) ; $i++) + { + $cmdp = array(); + $cmdp["params"][0] = $pvlist[$i]; + $cmd =& pfcCommand::Factory( $i < count($pvlist)-1 || !$joinoldchan ? 'privmsg2' : 'privmsg' ); + $cmd->run($xml_reponse, $cmdp); + } + + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', Array('".addslashes($nick)."'));"); + } + else + { + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ko', Array('".addslashes($nick)."'));"); + } + } + + /** + * reset the channel identifiers + */ + function _resetChannelIdentifier($clientid) + { + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + $ct =& pfcContainer::Instance(); + + // reset the channel identifiers + require_once(dirname(__FILE__)."/join.class.php"); + $channels = array(); + if (count($u->channels) == 0) + $channels = $c->channels; + else + foreach($u->channels as $chan) + $channels[] = $chan["name"]; + foreach($channels as $channame) + { + $chanrecip = pfcCommand_join::GetRecipient($channame); + $chanid = pfcCommand_join::GetRecipientId($channame); + // reset the fromid flag + $from_id_sid = "pfc_from_id_".$c->getId()."_".$clientid."_".$chanid; + $from_id = $ct->getLastId($chanrecip)-$c->max_msg+1; + $_SESSION[$from_id_sid] = ($from_id<0) ? 0 : $from_id; + // reset the oldmsg flag + $oldmsg_sid = "pfc_oldmsg_".$c->getId()."_".$clientid."_".$chanid; + $_SESSION[$oldmsg_sid] = true; + } + // reset the private messages identifiers + if (count($u->privmsg) > 0) + { + foreach($u->privmsg as $recipientid2 => $pv) + { + $recipient2 = $pv['recipient']; + // reset the fromid flag + $from_id_sid = "pfc_from_id_".$c->getId()."_".$clientid."_".$recipientid2; + $from_id = $ct->getLastId($recipient2)-$c->max_msg+1; + $_SESSION[$from_id_sid] = ($from_id<0) ? 0 : $from_id; + // reset the oldmsg flag + $oldmsg_sid = "pfc_oldmsg_".$c->getId()."_".$clientid."_".$recipientid2; + $_SESSION[$oldmsg_sid] = true; + } + } + } + +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/debug.class.php b/instmess/src/commands/debug.class.php new file mode 100644 index 0000000..1a270f0 --- /dev/null +++ b/instmess/src/commands/debug.class.php @@ -0,0 +1,38 @@ +script("pfc.handleResponse('".$this->name."', 'ok', '".$msg."');"); + } + + if ($p["param"] == "globalconfig") + { + $msg = ""; + $msg .= var_export($c, true); + $msg = str_replace("\n","",addslashes(nl2br($msg))); + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', '".$msg."');"); + } + if ($p["param"] == "phpserver") + { + $msg = ""; + $msg .= var_export($_SERVER, true); + $msg = str_replace("\n","",addslashes(nl2br($msg))); + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', '".$msg."');"); + } + + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/deop.class.php b/instmess/src/commands/deop.class.php new file mode 100644 index 0000000..ee75299 --- /dev/null +++ b/instmess/src/commands/deop.class.php @@ -0,0 +1,35 @@ +usage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + + // just change the "isadmin" meta flag + $nicktodeop = trim($p["param"]); + $nicktodeopid = $ct->getNickId($nicktodeop); + $ct->setUserMeta($nicktodeopid, 'isadmin', false); + + $this->forceWhoisReload($nicktodeopid); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/error.class.php b/instmess/src/commands/error.class.php new file mode 100644 index 0000000..0230921 --- /dev/null +++ b/instmess/src/commands/error.class.php @@ -0,0 +1,23 @@ + $e) { $error_ids .= ",'".$k."'"; $error_str.= $e." "; } + $error_ids = substr($error_ids,1); + $xml_reponse->script("pfc.setError('".addslashes(stripslashes($error_str))."', Array(".$error_ids."));"); + } + else + $xml_reponse->script("pfc.setError('".addslashes(stripslashes($errors))."', Array());"); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/getnewmsg.class.php b/instmess/src/commands/getnewmsg.class.php new file mode 100644 index 0000000..80644c1 --- /dev/null +++ b/instmess/src/commands/getnewmsg.class.php @@ -0,0 +1,119 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once(dirname(__FILE__)."/../pfccommand.class.php"); + +class pfcCommand_getnewmsg extends pfcCommand +{ + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + + $c =& pfcGlobalConfig::Instance(); + // do nothing if the recipient is not defined + if ($recipient == "") return; + + // check this methode is not being called + if( isset($_SESSION["pfc_lock_readnewmsg_".$c->getId()."_".$clientid]) ) + { + // kill the lock if it has been created more than 10 seconds ago + $last_10sec = time()-10; + $last_lock = $_SESSION["pfc_lock_readnewmsg_".$c->getId()."_".$clientid]; + if ($last_lock < $last_10sec) $_SESSION["pfc_lock_".$c->getId()."_".$clientid] = 0; + if ( $_SESSION["pfc_lock_readnewmsg_".$c->getId()."_".$clientid] != 0 ) exit; + } + // create a new lock + $_SESSION["pfc_lock_readnewmsg_".$c->getId()."_".$clientid] = time(); + + // read the last from_id value + $container =& pfcContainer::Instance(); + $from_id_sid = "pfc_from_id_".$c->getId()."_".$clientid."_".$recipientid; + + $from_id = 0; + if (isset($_SESSION[$from_id_sid])) + $from_id = $_SESSION[$from_id_sid]; + else + { + $from_id = $container->getLastId($recipient) - $c->max_msg - 1; + if ($from_id < 0) $from_id = 0; + } + // check if this is the first time you get messages + $oldmsg_sid = "pfc_oldmsg_".$c->getId()."_".$clientid."_".$recipientid; + $oldmsg = false; + if (isset($_SESSION[$oldmsg_sid])) + { + unset($_SESSION[$oldmsg_sid]); + $oldmsg = true; + } + + $new_msg = $container->read($recipient, $from_id); + $new_from_id = $new_msg["new_from_id"]; + $data = $new_msg["data"]; + + // transform new message in html format + $js = array(); + $data_sent = false; + foreach ($data as $d) + { + $m_id = $d["id"]; + $m_date = date($c->date_format, $d["timestamp"] + $c->time_offset); + $m_time = date($c->time_format, $d["timestamp"] + $c->time_offset); + $m_sender = $d["sender"]; + $m_recipientid = $recipientid; + $m_cmd = $d["cmd"]; + $m_param = phpFreeChat::PostFilterMsg(pfc_make_hyperlink($d["param"])); + $js[] = array($m_id, + $m_date, + $m_time, + $m_sender, + $m_recipientid, + $m_cmd, + $m_param, + date($c->date_format, time() + $c->time_offset) == $m_date ? 1 : 0, // posted today ? + $oldmsg ? 1 : 0); // is it a new message in the current session or is it a part of the history + $data_sent = true; + } + if (count($js) != 0) + { + require_once dirname(__FILE__).'/../pfcjson.class.php'; + $json = new pfcJSON(); + $js = $json->encode($js); + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', (".$js."));"); + } + + if ($data_sent) + { + // store the new msg id + $_SESSION[$from_id_sid] = $new_from_id; + } + + // remove the lock + $_SESSION["pfc_lock_readnewmsg_".$c->getId()."_".$clientid] = 0; + + } +} + +?> diff --git a/instmess/src/commands/help.class.php b/instmess/src/commands/help.class.php new file mode 100644 index 0000000..0676701 --- /dev/null +++ b/instmess/src/commands/help.class.php @@ -0,0 +1,39 @@ +"; + $str .= implode("
", $cmdlist); + + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', '".$str."');"); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/identify.class.php b/instmess/src/commands/identify.class.php new file mode 100644 index 0000000..9153cd6 --- /dev/null +++ b/instmess/src/commands/identify.class.php @@ -0,0 +1,75 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once(dirname(__FILE__)."/../pfccommand.class.php"); + +/** + * pfcCommand_identify + * this command will identify the user admin rights + * @author Stephane Gully + */ +class pfcCommand_identify extends pfcCommand +{ + var $usage = "/identify {password}"; + + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + + $password = trim($param); + $isadmin = false; + +// $xml_reponse->script("alert('sender=".$sender."');"); +// $xml_reponse->script("alert('password=".$password."');"); +// $xml_reponse->script("alert('admins=".var_export($c->admins, true)."');"); + + if( isset($c->admins[$sender]) && + $c->admins[$sender] == $password ) + $isadmin = true; + + $msg = ""; + if ($isadmin) + { + // ok the current user is an admin, just save the isadmin flag in the metadata + $ct =& pfcContainer::Instance(); + $ct->setUserMeta($u->nickid, 'isadmin', $isadmin); + $this->forceWhoisReload($u->nickid); + + $msg .= _pfc("Succesfully identified"); + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', '".$msg."');"); + } + else + { + $msg .= _pfc("Identification failure"); + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ko', '".$msg."');"); + } + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/init.class.php b/instmess/src/commands/init.class.php new file mode 100644 index 0000000..7455833 --- /dev/null +++ b/instmess/src/commands/init.class.php @@ -0,0 +1,25 @@ +run($xml_reponse, $p); + + $u->destroy(); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/invite.class.php b/instmess/src/commands/invite.class.php new file mode 100644 index 0000000..b95fa80 --- /dev/null +++ b/instmess/src/commands/invite.class.php @@ -0,0 +1,97 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once(dirname(__FILE__)."/../pfccommand.class.php"); +require_once(dirname(__FILE__)."/../commands/join.class.php"); + +/** + * /invite command + * + * Invites other users into a channel + * Currently this is implemented as a "autojoin", so the invited user joins automatically. + * The parameter "target channel" is optional, if not set it defaults to the current channel + * + * @author Benedikt Hallinger + * @author Stephane Gully + */ +class pfcCommand_invite extends pfcCommand +{ + var $usage = "/invite {nickname to invite} [ {target channel} ]"; + + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $params = $p["params"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + + $c =& pfcGlobalConfig::Instance(); // pfcGlobalConfig + $u =& pfcUserConfig::Instance(); // pfcUserConfig + $ct =& pfcContainer::Instance(); // Connection to the chatbackend + + $nicktoinvite = isset($params[0]) ? $params[0] : ''; + $channeltarget = isset($params[1]) ? $params[1] : $u->channels[$recipientid]["name"]; // Default: current channel + + if ($nicktoinvite == '' || $channeltarget == '') + { + // Parameters are not ok + $cmdp = $p; + $cmdp["params"] = array(); + $cmdp["param"] = _pfc("Missing parameter"); + $cmdp["param"] .= " (".$this->usage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + + // check that the inviter is already in the channeltarget + if (!$ct->isNickOnline(pfcCommand_join::GetRecipient($channeltarget),$u->nickid)) + { + $cmdp = $p; + $cmdp["params"] = array(); + $cmdp["param"] = _pfc("You must join %s to invite users in this channel",$channeltarget); + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + + // inviting a user: just add a join command to play to the aimed user metadata. + $nicktoinvite_id = $ct->getNickId($nicktoinvite); + $cmdstr = 'join2'; + $cmdp = array(); + $cmdp['param'] = $channeltarget; // channel target name + $cmdp['params'][] = $channeltarget; // channel target name + pfcCommand::AppendCmdToPlay($nicktoinvite_id, $cmdstr, $cmdp); + + // notify the aimed channel that a user has been invited + $cmdp = array(); + $cmdp["param"] = _pfc("%s was invited by %s",$nicktoinvite,$sender); + $cmdp["flag"] = 1; + $cmdp["recipient"] = pfcCommand_join::GetRecipient($channeltarget); + $cmdp["recipientid"] = pfcCommand_join::GetRecipientId($channeltarget); + $cmd =& pfcCommand::Factory("notice"); + $cmd->run($xml_reponse, $cmdp); + } +} +?> diff --git a/instmess/src/commands/join.class.php b/instmess/src/commands/join.class.php new file mode 100644 index 0000000..ab02452 --- /dev/null +++ b/instmess/src/commands/join.class.php @@ -0,0 +1,80 @@ +usage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + + if(!isset($u->channels[$chanid])) + { + if ($c->max_channels <= count($u->channels)) + { + // the maximum number of joined channels has been reached + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'max_channels', Array());"); + return; + } + + $u->channels[$chanid]["recipient"] = $chanrecip; + $u->channels[$chanid]["name"] = $channame; + $u->saveInCache(); + + // show a join message + $cmdp = $p; + $cmdp["param"] = _pfc("%s joins %s",$u->getNickname(), $channame); + $cmdp["recipient"] = $chanrecip; + $cmdp["recipientid"] = $chanid; + $cmdp["flag"] = 2; + $cmd =& pfcCommand::Factory("notice"); + $cmd->run($xml_reponse, $cmdp); + } + + // register the user (and his metadata) in the channel + $ct =& pfcContainer::Instance(); + // $ct->createNick($chanrecip, $u->nick, $u->nickid); + $ct->joinChan($u->nickid, $chanrecip); + $this->forceWhoisReload($u->nickid); + + // return ok to the client + // then the client will create a new tab + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', Array('".$chanid."','".addslashes($channame)."'));"); + } + + function GetRecipient($channame) + { + return "ch_".$channame; + } + + function GetRecipientId($channame) + { + return md5(pfcCommand_join::GetRecipient($channame)); + } + +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/join2.class.php b/instmess/src/commands/join2.class.php new file mode 100644 index 0000000..fb391ba --- /dev/null +++ b/instmess/src/commands/join2.class.php @@ -0,0 +1,34 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once(dirname(__FILE__)."/join.class.php"); + +/** + * Same as join command but open the tab in the background + * + * @author Stephane Gully + */ +class pfcCommand_join2 extends pfcCommand_join +{ +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/kick.class.php b/instmess/src/commands/kick.class.php new file mode 100644 index 0000000..bb9a683 --- /dev/null +++ b/instmess/src/commands/kick.class.php @@ -0,0 +1,55 @@ + 2) + for ($x=2;$xusage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + + // kicking a user just add a command to play to the aimed user metadata. + $ct =& pfcContainer::Instance(); + $otherid = $ct->getNickId($nick); + $channame = $u->channels[$recipientid]["name"]; + $cmdstr = 'leave'; + $cmdp = array(); + $cmdp['flag'] = 4; + $cmdp['params'][] = 'ch'; + $cmdp['params'][] = $channame; // channel name + $cmdp['params'][] = _pfc("kicked from %s by %s - reason: %s", $channame, $sender, $reason); // reason + pfcCommand::AppendCmdToPlay($otherid, $cmdstr, $cmdp); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/leave.class.php b/instmess/src/commands/leave.class.php new file mode 100644 index 0000000..741556c --- /dev/null +++ b/instmess/src/commands/leave.class.php @@ -0,0 +1,145 @@ +usage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + + + // get the recipientid to close (a pv or a channel) + $id = ''; + if ($type == 'ch') + { + if ($name == '') + $id = $recipientid; + else + $id = pfcCommand_join::GetRecipientId($name); + } + else if ($type == 'pv') + { + // pv + $pvnickid = $ct->getNickId($name); + $nickid = $u->nickid; + if ($pvnickid != '') + { + // generate a pvid from the two nicknames ids + $a = array($pvnickid, $nickid); sort($a); + $pvrecipient = "pv_".$a[0]."_".$a[1]; + $id = md5($pvrecipient); + } + } + else + $id = $recipientid; + + + + $leavech = false; + $leavepv = false; + $leave_recip = ''; + $leave_id = ''; + + // save the new channel list in the session + if ( isset($u->channels[$id]) ) + { + $leave_recip = $u->channels[$id]["recipient"]; + $leave_id = $id; + unset($u->channels[$id]); + $u->saveInCache(); + $leavech = true; + } + + // save the new private messages list in the session + if ( isset($u->privmsg[$id]) ) + { + $leave_recip = $u->privmsg[$id]["recipient"]; + $leave_id = $id; + unset($u->privmsg[$id]); + $u->saveInCache(); + $leavepv = true; + } + + if($leavepv || $leavech) + { + // if ($leavech) + { + // show a leave message with the showing the reason if present + $cmdp = $p; + $cmdp["recipient"] = $leave_recip; + $cmdp["recipientid"] = $leave_id; + $cmdp["flag"] = $flag; + $cmdp["param"] = _pfc("%s quit",$u->getNickname()); + if ($reason != "") $cmdp["param"] .= " (".$reason.")"; + $cmd =& pfcCommand::Factory("notice"); + $cmd->run($xml_reponse, $cmdp); + } + + // remove the nickname from the channel/pv + $ct->removeNick($leave_recip, $u->nickid); + + // reset the sessions indicators + $chanrecip = $leave_recip; + $chanid = $leave_id; + // reset the fromid flag + $from_id_sid = "pfc_from_id_".$c->getId()."_".$clientid."_".$chanid; + $from_id = $ct->getLastId($chanrecip)-$c->max_msg; + $_SESSION[$from_id_sid] = ($from_id<0) ? 0 : $from_id; + // reset the oldmsg flag + $oldmsg_sid = "pfc_oldmsg_".$c->getId()."_".$clientid."_".$chanid; + $_SESSION[$oldmsg_sid] = true; + + // if the /leave command comes from a cmdtoplay then show the reason to the user (ex: kick or ban reason) + if ($p['cmdtoplay']) + { + $cmdp = $p; + $cmdp["param"] = $reason; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + } + + // return ok to the client + // then the client will remove the channel' tab + $xml_reponse->script("pfc.handleResponse('leave', 'ok', '".$id."');"); + } + else + { + // error + $cmdp = $p; + $cmdp["param"] = _pfc("Missing parameter"); + $cmdp["param"] .= " (".$this->usage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + } + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/me.class.php b/instmess/src/commands/me.class.php new file mode 100644 index 0000000..4cdb015 --- /dev/null +++ b/instmess/src/commands/me.class.php @@ -0,0 +1,37 @@ +usage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + + $msg = phpFreeChat::PreFilterMsg($param); + $ct->write($recipient, $sender, $this->name, $msg); + } +} + +?> diff --git a/instmess/src/commands/nick.class.php b/instmess/src/commands/nick.class.php new file mode 100644 index 0000000..4e2380b --- /dev/null +++ b/instmess/src/commands/nick.class.php @@ -0,0 +1,92 @@ +usage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return false; + } + + $newnick = phpFreeChat::FilterNickname($param); + $oldnick = $ct->getNickname($u->nickid); + + $newnickid = $ct->getNickId($newnick); + $oldnickid = $u->nickid; + + // new nickname is undefined (not used) and + // current nickname (oldnick) is mine and + // oldnick is different from new nick + // -> this is a nickname change + if ($oldnick != $newnick && + $oldnick != '') + { + // really change the nick (rename it) + $ct->changeNick($newnick, $oldnick); + $u->nick = $newnick; + $u->saveInCache(); + $this->forceWhoisReload($u->nickid); + + // notify all the joined channels/privmsg + $cmdp = $p; + $cmdp["param"] = _pfc("%s changes his nickname to %s",$oldnick,$newnick); + $cmdp["flag"] = 1; + $cmd =& pfcCommand::Factory("notice"); + foreach($u->channels as $id => $chan) + { + $cmdp["recipient"] = $chan["recipient"]; + $cmdp["recipientid"] = $id; + $cmd->run($xml_reponse, $cmdp); + } + foreach( $u->privmsg as $id => $pv ) + { + $cmdp["recipient"] = $pv["recipient"]; + $cmdp["recipientid"] = $id; + $cmd->run($xml_reponse, $cmdp); + } + $xml_reponse->script("pfc.handleResponse('nick', 'changed', '".addslashes($newnick)."');"); + return true; + } + + // new nickname is undefined (not used) + // -> this is a first connection (this piece of code is called by /connect command) + if ($newnickid == '') + { + // this is a first connection : create the nickname on the server + $ct->createNick($u->nickid, $newnick); + $u->nick = $newnick; + $u->saveInCache(); + + $this->forceWhoisReload($u->nickid); + + $xml_reponse->script("pfc.handleResponse('nick', 'connected', '".addslashes($newnick)."');"); + + return true; + } + + return false; + } +} + +?> diff --git a/instmess/src/commands/notice.class.php b/instmess/src/commands/notice.class.php new file mode 100644 index 0000000..45e4e22 --- /dev/null +++ b/instmess/src/commands/notice.class.php @@ -0,0 +1,38 @@ +shownotice > 0 && + ($c->shownotice & $flag) == $flag) + { + $msg = phpFreeChat::FilterSpecialChar($msg); + $nick = $ct->getNickname($u->nickid); + $res = $ct->write($recipient, $nick, "notice", $msg); + if (is_array($res)) + { + $cmdp = $p; + $cmdp["param"] = implode(",",$res); + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + } + } +} + +?> diff --git a/instmess/src/commands/op.class.php b/instmess/src/commands/op.class.php new file mode 100644 index 0000000..0c1d726 --- /dev/null +++ b/instmess/src/commands/op.class.php @@ -0,0 +1,41 @@ +usage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + + // just change the "isadmin" meta flag + $nicktoop = trim($param); + $nicktoopid = $ct->getNickId($nicktoop); + $ct->setUserMeta($nicktoopid, 'isadmin', true); + + $this->forceWhoisReload($nicktoopid); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/privmsg.class.php b/instmess/src/commands/privmsg.class.php new file mode 100644 index 0000000..bf4e41c --- /dev/null +++ b/instmess/src/commands/privmsg.class.php @@ -0,0 +1,119 @@ +usage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return false; + } + + // check the pvname exists on the server + $pvname = ''; + $pvnickid = ''; + if ($this->name == 'privmsg2') + { + $pvnickid = $params[0]; + $pvname = $ct->getNickname($pvnickid); + } + else + { + $pvname = $params[0]; + $pvnickid = $ct->getNickId($pvname); + } + $nickid = $u->nickid; + $nick = $ct->getNickname($u->nickid); + + // error: can't speak to myself + if ($pvnickid == $nickid) + { + $xml_reponse->script("pfc.handleResponse('".$this->name."','speak_to_myself');"); + return; + } + + //$this->trace($xml_reponse, $this->name, $sender); + + // error: can't speak to unknown + if ($pvnickid == '') + { + // remove this old pv from the privmsg list + $pvid_to_remove = ""; + foreach( $u->privmsg as $pv_k => $pv_v ) + { + if ($pv_v["name"] == $pvname) + $pvid_to_remove = $pv_k; + } + if ($pvid_to_remove != "") + { + unset($u->privmsg[$pvid_to_remove]); + $u->saveInCache(); + } + + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'unknown', Array('".addslashes($pvname)."','speak to unknown'));"); + return; + } + + // generate a pvid from the two nicknames ids + $a = array($pvnickid, $nickid); sort($a); + $pvrecipient = "pv_".$a[0]."_".$a[1]; + $pvrecipientid = md5($pvrecipient); + + // $xml_reponse->script("alert('privmsg: pvnickid=".$pvnickid."');"); + // $xml_reponse->script("alert('privmsg: pvname=".$pvname." pvrecipient=".$pvrecipient."');"); + + // update the private message list + // in the sessions + if (!isset($u->privmsg[$pvrecipientid])) + { + if ($c->max_privmsg <= count($u->privmsg)) + { + // the maximum number of private messages has been reached + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'max_privmsg', Array());"); + return; + } + + $u->privmsg[$pvrecipientid]["recipient"] = $pvrecipient; + $u->privmsg[$pvrecipientid]["name"] = $pvname; + $u->privmsg[$pvrecipientid]["pvnickid"] = $pvnickid; + $u->saveInCache(); + + // reset the message id indicator + // i.e. be ready to re-get all last posted messages + $from_id_sid = "pfc_from_id_".$c->getId()."_".$clientid."_".$pvrecipientid; + $from_id = $ct->getLastId($pvrecipient)-$c->max_msg-1; + $_SESSION[$from_id_sid] = ($from_id<0) ? 0 : $from_id; + } + + // register the user (and his metadata) in this pv + // $ct->createNick($pvrecipient, $u->nick, $u->nickid); + $ct->joinChan($nickid, $pvrecipient); + $this->forceWhoisReload($nickid); + + // return ok to the client + // then the client will create a new tab + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', Array('".$pvrecipientid."','".addslashes($pvname)."'));"); + } +} + +?> diff --git a/instmess/src/commands/privmsg2.class.php b/instmess/src/commands/privmsg2.class.php new file mode 100644 index 0000000..f05eecd --- /dev/null +++ b/instmess/src/commands/privmsg2.class.php @@ -0,0 +1,35 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once(dirname(__FILE__)."/privmsg.class.php"); + +/** + * Same as privmsg command but open the tab in the background + * + * @author Stephane Gully + */ +class pfcCommand_privmsg2 extends pfcCommand_privmsg +{ + var $usage = "/privmsg2 {nicknameid}"; +} + +?> diff --git a/instmess/src/commands/quit.class.php b/instmess/src/commands/quit.class.php new file mode 100644 index 0000000..cbdd8b8 --- /dev/null +++ b/instmess/src/commands/quit.class.php @@ -0,0 +1,56 @@ +getNickname($u->nickid); + + // leave the channels + foreach( $u->channels as $id => $chandetail ) + if ($ct->removeNick($chandetail["recipient"], $u->nickid)) + { + $cmdp = $p; + $cmdp["param"] = $id; + $cmdp["recipient"] = $chandetail["recipient"]; + $cmdp["recipientid"] = $id; + $cmd =& pfcCommand::Factory("leave"); + $cmd->run($xml_reponse, $cmdp); + } + // leave the private messages + foreach( $u->privmsg as $id => $pvdetail ) + if ($ct->removeNick($pvdetail["recipient"], $u->nickid)) + { + $cmdp = $p; + $cmdp["param"] = $id; + $cmdp["recipient"] = $pvdetail["recipient"]; + $cmdp["recipientid"] = $id; + $cmd =& pfcCommand::Factory("leave"); + $cmd->run($xml_reponse, $cmdp); + } + // leave the server + $ct->removeNick(NULL, $u->nickid); + + /* + // then set the chat inactive + $u->active = false; + $u->saveInCache(); + */ + + $xml_reponse->script("pfc.handleResponse('quit', 'ok', '');"); + } +} + +?> diff --git a/instmess/src/commands/redirect.class.php b/instmess/src/commands/redirect.class.php new file mode 100644 index 0000000..3a55697 --- /dev/null +++ b/instmess/src/commands/redirect.class.php @@ -0,0 +1,34 @@ +usage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + + $xml_reponse->redirect($param); + } +} + +?> diff --git a/instmess/src/commands/rehash.class.php b/instmess/src/commands/rehash.class.php new file mode 100644 index 0000000..7863745 --- /dev/null +++ b/instmess/src/commands/rehash.class.php @@ -0,0 +1,35 @@ + + */ +class pfcCommand_rehash extends pfcCommand +{ + var $desc = "This command deletes the cached configuration. Uses it to take into account new parameters."; + + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + + $c =& pfcGlobalConfig::Instance(); + // just destroy the cache + // do not synchronizeWithCache() because it will reload the same parameters as the current one + // the right way is to wait for the next page reload and the new parameters will be taken into account + $destroyed = $c->destroyCache(); + + if ($destroyed) + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', '');"); + else + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ko', '');"); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/send.class.php b/instmess/src/commands/send.class.php new file mode 100644 index 0000000..4515c00 --- /dev/null +++ b/instmess/src/commands/send.class.php @@ -0,0 +1,90 @@ +getNickname($u->nickid); //phpFreeChat::FilterSpecialChar($sender); + $text = phpFreeChat::PreFilterMsg($param); + + // $offline = $container->getMeta("offline", "nickname", $u->privmsg[$recipientid]["name"]); + + // if this channel is a pv (one to one channel), + // first of all, check if the other user is connected + // if he is not connected anymore, display an error + $can_send = true; + if (isset($u->privmsg[$recipientid])) + { + $pvnickid = $u->privmsg[$recipientid]["pvnickid"]; + $pvnick = $ct->getNickname($pvnickid);//$u->privmsg[$recipientid]["name"]; + // $pvnickid = $ct->getNickId($pvnick); + + // now check if this user is currently online + $onlineusers = $ct->getOnlineNick(NULL); + if (!in_array($pvnickid, $onlineusers["nickid"])) + { + // send an error because the user is not online + $cmdp = $p; + $cmdp["param"] = _pfc("Can't send the message, %s is offline", $pvnick); + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + $can_send = false; + } + } + + + // check the sent text is not empty and the user has a none empty nickname + $errors = array(); + if ($text == "") $errors["pfc_words"] = _pfc("Text cannot be empty"); + if ($nick == "") $errors["pfc_handle"] = _pfc("Please enter your nickname"); + if (count($errors) > 0) + { + // an error occured, just ignore the message and display errors + $cmdp = $p; + $cmdp["param"] = $errors; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + if (isset($errors["pfc_handle"])) // the nick is empty so give it focus + $xml_reponse->script("$('pfc_handle').focus();"); + $can_send = false; + } + + + // Now send the message if there is no errors + if ($can_send) + { + $msgid = $ct->write($recipient, $nick, "send", $text); + if (is_array($msgid)) + { + $cmdp = $p; + $cmdp["param"] = implode(",",$msgid); + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + + // a message has been posted so : + // - clear errors + // - give focus to "words" field + // @todo move this code in the handleResponse function + $xml_reponse->script("pfc.clearError(Array('pfc_words"."','pfc_handle"."'));"); + $xml_reponse->script("$('pfc_words').focus();"); + } + + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', '');"); + } +} + +?> diff --git a/instmess/src/commands/unban.class.php b/instmess/src/commands/unban.class.php new file mode 100644 index 0000000..6d0a79b --- /dev/null +++ b/instmess/src/commands/unban.class.php @@ -0,0 +1,71 @@ +getNickId($nick); + + if ($nick == "") + { + // error + $cmdp = $p; + $cmdp["param"] = _pfc("Missing parameter"); + $cmdp["param"] .= " (".$this->usage.")"; + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return; + } + + + $updated = false; + $msg = "

"._pfc("Nobody has been unbanished")."

"; + + // update the recipient banlist + $banlist = $ct->getChanMeta($recipient, 'banlist_nickid'); + if ($banlist == NULL) + $banlist = array(); + else + $banlist = unserialize($banlist); + $nb = count($banlist); + + if (in_array($nickid, $banlist)) + { + $banlist = array_diff($banlist, array($nickid)); + $ct->setChanMeta($recipient, 'banlist_nickid', serialize($banlist)); + $updated = true; + $msg = "

"._pfc("%s has been unbanished", $nick)."

"; + } + else if ($nick == "all") // @todo move the "/unban all" command in another command /unbanall + { + $banlist = array(); + $ct->setChanMeta($recipient, 'banlist_nickid', serialize($banlist)); + $updated = true; + $msg = "

"._pfc("%s users have been unbanished", $nb)."

"; + } + + if ($updated) + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', '".$msg."');"); + else + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ko', '".$msg."');"); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/update.class.php b/instmess/src/commands/update.class.php new file mode 100644 index 0000000..a08ff0d --- /dev/null +++ b/instmess/src/commands/update.class.php @@ -0,0 +1,83 @@ +nick != '' && !$u->isOnline()) + { + $cmd =& pfcCommand::Factory("connect"); + $cmdp = $p; + $cmdp['params'] = array($u->nick); + $cmdp['getoldmsg'] = false; + $cmdp['joinoldchan'] = false; + $cmd->run($xml_reponse, $cmdp); + } + + // do not update if user isn't active (didn't connect) + if ($u->isOnline()) + { + $cmdp = $p; + + // update the user nickname timestamp on the server + $cmd =& pfcCommand::Factory("updatemynick"); + $cmdp["recipient"] = NULL; + $cmdp["recipientid"] = NULL; + $cmd->run($xml_reponse, $cmdp); + + // get other online users on each channels + $cmd =& pfcCommand::Factory("who2"); + foreach( $u->channels as $id => $chan ) + { + $cmdp["recipient"] = $chan["recipient"]; + $cmdp["recipientid"] = $id; + $cmdp["param"] = ''; // don't forward the parameter because it will be interpreted as a channel name + $cmd->run($xml_reponse, $cmdp); + } + foreach( $u->privmsg as $id => $pv ) + { + $cmdp["recipient"] = $pv["recipient"]; + $cmdp["recipientid"] = $id; + $cmdp["param"] = ''; // don't forward the parameter because it will be interpreted as a channel name + $cmd->run($xml_reponse, $cmdp); + } + + // get new message posted on each channels + $cmd =& pfcCommand::Factory("getnewmsg"); + foreach( $u->channels as $id => $chan ) + { + $cmdp["recipient"] = $chan["recipient"]; + $cmdp["recipientid"] = $id; + $cmd->run($xml_reponse, $cmdp); + } + foreach( $u->privmsg as $id => $pv ) + { + $cmdp["recipient"] = $pv["recipient"]; + $cmdp["recipientid"] = $id; + $cmd->run($xml_reponse, $cmdp); + } + + $xml_reponse->script("pfc.handleResponse('update', 'ok', '');"); + } + else + { + $xml_reponse->script("pfc.handleResponse('update', 'ko', '');"); + } + + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/updatemynick.class.php b/instmess/src/commands/updatemynick.class.php new file mode 100644 index 0000000..36f9c00 --- /dev/null +++ b/instmess/src/commands/updatemynick.class.php @@ -0,0 +1,23 @@ +updateNick($u->nickid); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/version.class.php b/instmess/src/commands/version.class.php new file mode 100644 index 0000000..aeb7a0d --- /dev/null +++ b/instmess/src/commands/version.class.php @@ -0,0 +1,24 @@ +script("pfc.handleResponse('".$this->name."', 'ok', '".$c->version."');"); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/who.class.php b/instmess/src/commands/who.class.php new file mode 100644 index 0000000..852c1ad --- /dev/null +++ b/instmess/src/commands/who.class.php @@ -0,0 +1,72 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once(dirname(__FILE__)."/../pfccommand.class.php"); + +class pfcCommand_who extends pfcCommand +{ + var $usage = "/who channel"; + + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + + if ($param != "") + { + require_once dirname(__FILE__)."/join.class.php"; + $recipient = pfcCommand_join::GetRecipient($param); + $recipientid = pfcCommand_join::GetRecipientId($param); + } + + $chanmeta = $this->_getChanMeta($recipient, $recipientid); + + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', ".$chanmeta.");"); + } + + function _getChanMeta($recipient, $recipientid) + { + $c =& pfcGlobalConfig::Instance(); + $ct =& pfcContainer::Instance(); + $chanmeta = array(); + $chanmeta['chan'] = $recipient; + $chanmeta['chanid'] = $recipientid; + $chanmeta['meta'] = $ct->getAllUserMeta($chanmeta['chan']); + $users = $ct->getOnlineNick($chanmeta['chan']); + $chanmeta['meta']['users'] = array(); + $chanmeta['meta']['users']['nick'] = $users['nick']; + $chanmeta['meta']['users']['nickid'] = $users['nickid']; + + require_once dirname(__FILE__).'/../pfcjson.class.php'; + $json = new pfcJSON(); + $js = $json->encode($chanmeta); + return $js; + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/who2.class.php b/instmess/src/commands/who2.class.php new file mode 100644 index 0000000..3fb525c --- /dev/null +++ b/instmess/src/commands/who2.class.php @@ -0,0 +1,66 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once(dirname(__FILE__)."/who.class.php"); + +class pfcCommand_who2 extends pfcCommand_who +{ + + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + + if ($param != "") + { + require_once dirname(__FILE__)."/join.class.php"; + $recipient = pfcCommand_join::GetRecipient($param); + $recipientid = pfcCommand_join::GetRecipientId($param); + } + + $chanmeta = $this->_getChanMeta($recipient, $recipientid); + + //if (preg_match("/^pv_/", $recipient)) + //$this->trace($xml_reponse, 'who2', $recipient); + + // check if info didn't change since last call + $sid = "pfc_who2_".$c->getId()."_".$clientid."_".$recipientid; + if (isset($_SESSION[$sid]) && $chanmeta == $_SESSION[$sid]) + { + // do not send the response to save bandwidth + //$xml_reponse->script("pfc.handleResponse('".$this->name."', 'unchanged', '');"); + } + else + { + $_SESSION[$sid] = $chanmeta; + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', ".$chanmeta.");"); + } + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/whois.class.php b/instmess/src/commands/whois.class.php new file mode 100644 index 0000000..ba6edb5 --- /dev/null +++ b/instmess/src/commands/whois.class.php @@ -0,0 +1,81 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once(dirname(__FILE__)."/../pfccommand.class.php"); + +class pfcCommand_whois extends pfcCommand +{ + var $usage = "/whois nickname"; + + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $params = $p["params"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + $ct =& pfcContainer::Instance(); + + // $xml_reponse->script("trace('".implode(',',$params)."');"); + // return; + + // get the nickid from the parameters + // if the run command is whois2 then the parameter is a nickid + $nickid = ''; + if ($this->name == 'whois2') + $nickid = $params[0]; + else + $nickid = $ct->getNickId($params[0]); + + if ($nickid) + { + $usermeta = $ct->getAllUserMeta($nickid); + $usermeta['nickid'] = $nickid; + unset($usermeta['cmdtoplay']); // used internaly + + // remove private usermeta from the list if the client is not admin + $isadmin = $ct->getUserMeta($u->nickid, 'isadmin'); + if (!$isadmin) + foreach($c->nickmeta_private as $nmp) + unset($usermeta[$nmp]); + + // sort the list + $nickmeta_sorted = array(); + $order = array_merge(array_diff(array_keys($usermeta),array_keys($c->nickmeta)),array_keys($c->nickmeta)); + foreach($order as $o) $nickmeta_sorted[$o] = $usermeta[$o]; + $usermeta = $nickmeta_sorted; + + require_once dirname(__FILE__).'/../pfcjson.class.php'; + $json = new pfcJSON(); + $js = $json->encode($usermeta); + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ok', ".$js.");"); + } + else + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'ko','".$param."');"); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/commands/whois2.class.php b/instmess/src/commands/whois2.class.php new file mode 100644 index 0000000..55f5c06 --- /dev/null +++ b/instmess/src/commands/whois2.class.php @@ -0,0 +1,29 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once(dirname(__FILE__)."/whois.class.php"); + +class pfcCommand_whois2 extends pfcCommand_whois +{ +} + +?> \ No newline at end of file diff --git a/instmess/src/containers/file.class.php b/instmess/src/containers/file.class.php new file mode 100644 index 0000000..3329f5d --- /dev/null +++ b/instmess/src/containers/file.class.php @@ -0,0 +1,301 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once dirname(__FILE__)."/../pfccontainerinterface.class.php"; + +/** + * pfcContainer_File is a concret container which stock data into files + * + * @author Stephane Gully + */ +class pfcContainer_File extends pfcContainerInterface +{ + var $_users = array("nickid" => array(), + "timestamp" => array()); + var $_meta = array(); + + function pfcContainer_File() + { + pfcContainerInterface::pfcContainerInterface(); + } + + function loadPaths(&$c) + { + if (!isset($c->container_cfg_chat_dir) || $c->container_cfg_chat_dir == '') + $c->container_cfg_chat_dir = $c->data_private_path."/chat"; + if (!isset($c->container_cfg_server_dir) || $c->container_cfg_server_dir == '') + $c->container_cfg_server_dir = $c->container_cfg_chat_dir."/s_".$c->serverid; + } + + function getDefaultConfig() + { + $cfg = pfcContainerInterface::getDefaultConfig(); + $cfg["chat_dir"] = ''; // will be generated from the other parameters into the init step + $cfg["server_dir"] = ''; // will be generated from the other parameters into the init step + return $cfg; + } + + function init(&$c) + { + $errors = pfcContainerInterface::init($c); + + // generate the container parameters from other config parameters + $this->loadPaths($c); + + $errors = array_merge($errors, @test_writable_dir($c->container_cfg_chat_dir, "container_cfg_chat_dir")); + $errors = array_merge($errors, @test_writable_dir($c->container_cfg_server_dir, "container_cfg_chat_dir/serverid")); + + // test the filemtime php function because it doesn't work on special filesystem + // example : NFS, VZFS + $filename = $c->data_private_path.'/filemtime.test'; + $timetowait = 2; + if (is_writable(dirname($filename))) + { + file_put_contents($filename,'some-data1-'.time()); + clearstatcache(); + $time1 = filemtime($filename); + sleep($timetowait); + file_put_contents($filename,'some-data2-'.time()); + clearstatcache(); + $time2 = filemtime($filename); + unlink($filename); + if ($time2-$time1 != $timetowait) + $errors[] = "filemtime php fuction is not usable on your filesystem. Please do not use the 'file' container (try the 'mysql' container) or swith to another filesystem type."; + } + + // test the LOCK_EX feature because it doesn't work on special filsystem like NFS + $filename = $c->data_private_path.'/filemtime.test'; + if (is_writable(dirname($filename))) + { + $data1 = time(); + file_put_contents($filename, $data1, LOCK_EX); + $data2 = file_get_contents($filename); + if ($data1 != $data2) + $errors[] = "LOCK_EX feature is not usable on your filesystem. Please do not use the 'file' container (try the 'mysql' container) or swith to another filesystem type."; + } + + return $errors; + } + + function setMeta($group, $subgroup, $leaf, $leafvalue = NULL) + { + $c =& pfcGlobalConfig::Instance(); + + // create directories + $dir_base = $c->container_cfg_server_dir; + $dir = $dir_base.'/'.$group.'/'.$subgroup; + if (!is_dir($dir)) mkdir_r($dir); + + // create or replace metadata file + $leaffilename = $dir."/".$leaf; + $leafexists = file_exists($leaffilename); + if ($leafvalue == NULL) + { + @unlink($leaffilename); + touch($leaffilename); + } + else + { + file_put_contents($leaffilename, $leafvalue, LOCK_EX); + } + + // store the value in the memory cache + //@todo + // $this->_meta[$enc_type][$enc_subtype][$enc_key] = $value; + + if ($leafexists) + return 1; // value overwritten + else + return 0; // value created + } + + function getMeta($group, $subgroup = null, $leaf = null, $withleafvalue = false) + { + $c =& pfcGlobalConfig::Instance(); + + // read data from metadata file + $ret = array(); + $ret["timestamp"] = array(); + $ret["value"] = array(); + $dir_base = $c->container_cfg_server_dir; + + $dir = $dir_base.'/'.$group; + if ($subgroup == NULL) + { + if (is_dir($dir)) + { + $dh = opendir($dir); + while (false !== ($file = readdir($dh))) + { + if ($file == "." || $file == "..") continue; // skip . and .. generic files + $ret["timestamp"][] = filemtime($dir.'/'.$file); + $ret["value"][] = $file; + } + closedir($dh); + } + return $ret; + } + + $dir .= '/'.$subgroup; + + if ($leaf == NULL) + { + if (is_dir($dir)) + { + $dh = opendir($dir); + while (false !== ($file = readdir($dh))) + { + if ($file == "." || $file == "..") continue; // skip . and .. generic files + $ret["timestamp"][] = filemtime($dir.'/'.$file); + $ret["value"][] = $file; + } + closedir($dh); + } + + return $ret; + } + + $leaffilename = $dir."/".$leaf; + + if (!file_exists($leaffilename)) return $ret; + if ($withleafvalue) + $ret["value"][] = file_get_contents_flock($leaffilename); + else + $ret["value"][] = NULL; + $ret["timestamp"][] = filemtime($leaffilename); + + return $ret; + } + + function incMeta($group, $subgroup, $leaf) + { + $c =& pfcGlobalConfig::Instance(); + + // create directories + $dir_base = $c->container_cfg_server_dir; + $dir = $dir_base.'/'.$group.'/'.$subgroup; + if (!is_dir($dir)) mkdir_r($dir); + + // create or replace metadata file + $leaffilename = $dir."/".$leaf; + + // create return array + $ret = array(); + $ret["timestamp"] = array(); + $ret["value"] = array(); + + // read and increment data from metadata file + clearstatcache(); + if (file_exists($leaffilename)) + { + $fh = fopen($leaffilename, 'r+'); + for($i = 0; $i < 10; $i++) // Try 10 times until an exclusive lock can be obtained + { + if (flock($fh, LOCK_EX)) + { + $leafvalue = chop(fread($fh, filesize($leaffilename))); + $leafvalue++; + rewind($fh); + fwrite($fh, $leafvalue); + fflush($fh); + ftruncate($fh, ftell($fh)); + flock($fh, LOCK_UN); + break; + } + // If flock is working properly, this will never be reached + $delay = rand(0, pow(2, ($i+1)) - 1) * 5000; // Exponential backoff + usleep($delay); + } + fclose($fh); + } + else + { + $leafvalue="1"; + file_put_contents($leaffilename, $leafvalue, LOCK_EX); + } + + $ret["value"][] = $leafvalue; + $ret["timestamp"][] = filemtime($leaffilename); + + return $ret; + } + + function rmMeta($group, $subgroup = null, $leaf = null) + { + $c =& pfcGlobalConfig::Instance(); + + $dir = $c->container_cfg_server_dir; + + if ($group == NULL) + { + rm_r($dir); + return true; + } + + $dir .= '/'.$group; + + if ($subgroup == NULL) + { + rm_r($dir); + return true; + } + + $dir .= '/'.$subgroup; + + if ($leaf == NULL) + { + rm_r($dir); + return true; + } + + $leaffilename = $dir.'/'.$leaf; + if (!file_exists($leaffilename)) return false; + unlink($leaffilename); + + // check that the directory is empty or not + // remove it if it doesn't contains anything + $dh = opendir($dir); + readdir($dh); readdir($dh); // skip . and .. directories + $isnotempty = readdir($dh); + closedir($dh); + if ($isnotempty === false) rmdir($dir); + + return true; + } + + /** + * Used to encode UTF8 strings to ASCII filenames + */ + function encode($str) + { + return urlencode($str); + } + + /** + * Used to decode ASCII filenames to UTF8 strings + */ + function decode($str) + { + return urldecode($str); + } +} +?> diff --git a/instmess/src/containers/mysql.class.php b/instmess/src/containers/mysql.class.php new file mode 100644 index 0000000..60f797d --- /dev/null +++ b/instmess/src/containers/mysql.class.php @@ -0,0 +1,319 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once dirname(__FILE__)."/../pfccontainer.class.php"; + +/** + * pfcContainer_Mysql is a concret container which store data into mysql + * + * Because of the new storage functions (setMeta, getMeta, rmMeta) + * everything can be stored in just one single table. + * Using type "HEAP" or "MEMORY" mysql loads this table into memory making it very fast + * There is no routine to create the table if it does not exists so you have to create it by hand + * Replace the database login info at the top of pfcContainer_mysql class with your own + * You also need some config lines in your chat index file: + * $params["container_type"] = "mysql"; + * $params["container_cfg_mysql_host"] = "localhost"; + * $params["container_cfg_mysql_port"] = 3306; + * $params["container_cfg_mysql_database"] = "phpfreechat"; + * $params["container_cfg_mysql_table"] = "phpfreechat"; + * $params["container_cfg_mysql_username"] = "root"; + * $params["container_cfg_mysql_password"] = ""; + * + * Advanced parameters are : + * $params["container_cfg_mysql_fieldtype_server"] = 'varchar(32)'; + * $params["container_cfg_mysql_fieldtype_group"] = 'varchar(64)'; + * $params["container_cfg_mysql_fieldtype_subgroup"] = 'varchar(64)'; + * $params["container_cfg_mysql_fieldtype_leaf"] = 'varchar(64)'; + * $params["container_cfg_mysql_fieldtype_leafvalue"] = 'text'; + * $params["container_cfg_mysql_fieldtype_timestamp"] = 'int(11)'; + * $params["container_cfg_mysql_engine"] = 'InnoDB'; + * + * + * @author Stephane Gully + * @author HenkBB + */ +class pfcContainer_Mysql extends pfcContainerInterface +{ + var $_db = null; + var $_sql_create_table = " + CREATE TABLE IF NOT EXISTS `%table%` ( + `server` %fieldtype_server% NOT NULL default '', + `group` %fieldtype_group% NOT NULL default '', + `subgroup` %fieldtype_subgroup% NOT NULL default '', + `leaf` %fieldtype_leaf% NOT NULL default '', + `leafvalue` %fieldtype_leafvalue% NOT NULL, + `timestamp` %fieldtype_timestamp% NOT NULL default 0, + PRIMARY KEY (`server`,`group`,`subgroup`,`leaf`), + INDEX (`server`,`group`,`subgroup`,`timestamp`) +) ENGINE=%engine%;"; + + function pfcContainer_Mysql() + { + pfcContainerInterface::pfcContainerInterface(); + } + + function getDefaultConfig() + { + $cfg = pfcContainerInterface::getDefaultConfig(); + $cfg["mysql_host"] = 'localhost'; + $cfg["mysql_port"] = 3306; + $cfg["mysql_database"] = 'phpfreechat'; + $cfg["mysql_table"] = 'phpfreechat'; + $cfg["mysql_username"] = 'root'; + $cfg["mysql_password"] = ''; + // advanced parameters (don't touch if you don't know what your are doing) + $cfg["mysql_fieldtype_server"] = 'varchar(32)'; + $cfg["mysql_fieldtype_group"] = 'varchar(64)'; + $cfg["mysql_fieldtype_subgroup"] = 'varchar(128)'; + $cfg["mysql_fieldtype_leaf"] = 'varchar(128)'; + $cfg["mysql_fieldtype_leafvalue"] = 'text'; + $cfg["mysql_fieldtype_timestamp"] = 'int(11)'; + $cfg["mysql_engine"] = 'InnoDB'; + return $cfg; + } + + function init(&$c) + { + $errors = pfcContainerInterface::init($c); + + // connect to the db + $db = $this->_connect($c); + if ($db === FALSE) + { + $errors[] = _pfc("Mysql container: connect error"); + return $errors; + } + + // create the db if it doesn't exists + $db_exists = false; + $db_list = mysql_list_dbs($db); + while (!$db_exists && $row = mysql_fetch_object($db_list)) + $db_exists = ($c->container_cfg_mysql_database == $row->Database); + if (!$db_exists) + { + $query = 'CREATE DATABASE '.$c->container_cfg_mysql_database; + $result = mysql_query($query, $db); + if ($result === FALSE) + { + $errors[] = _pfc("Mysql container: create database error '%s'",mysql_error($db)); + return $errors; + } + mysql_select_db($c->container_cfg_mysql_database, $db); + } + + // create the table if it doesn't exists + $query = $this->_sql_create_table; + $query = str_replace('%engine%', $c->container_cfg_mysql_engine,$query); + $query = str_replace('%table%', $c->container_cfg_mysql_table,$query); + $query = str_replace('%fieldtype_server%', $c->container_cfg_mysql_fieldtype_server,$query); + $query = str_replace('%fieldtype_group%', $c->container_cfg_mysql_fieldtype_group,$query); + $query = str_replace('%fieldtype_subgroup%', $c->container_cfg_mysql_fieldtype_subgroup,$query); + $query = str_replace('%fieldtype_leaf%', $c->container_cfg_mysql_fieldtype_leaf,$query); + $query = str_replace('%fieldtype_leafvalue%', $c->container_cfg_mysql_fieldtype_leafvalue,$query); + $query = str_replace('%fieldtype_timestamp%', $c->container_cfg_mysql_fieldtype_timestamp,$query); + $result = mysql_query($query, $db); + if ($result === FALSE) + { + $errors[] = _pfc("Mysql container: create table error '%s'",mysql_error($db)); + return $errors; + } + return $errors; + } + + function _connect($c = null) + { + if (!$this->_db) + { + if ($c == null) $c =& pfcGlobalConfig::Instance(); + $this->_db = mysql_pconnect($c->container_cfg_mysql_host.':'.$c->container_cfg_mysql_port, + $c->container_cfg_mysql_username, + $c->container_cfg_mysql_password); + mysql_select_db($c->container_cfg_mysql_database, $this->_db); + } + return $this->_db; + } + + function setMeta($group, $subgroup, $leaf, $leafvalue = NULL) + { + $c =& pfcGlobalConfig::Instance(); + + $server = $c->serverid; + $db = $this->_connect(); + + if ($leafvalue == NULL){$leafvalue="";}; + + $sql_count = "SELECT COUNT(*) AS C FROM ".$c->container_cfg_mysql_table." WHERE `server`='$server' AND `group`='$group' AND `subgroup`='$subgroup' AND `leaf`='$leaf' LIMIT 1"; + $sql_insert="REPLACE INTO ".$c->container_cfg_mysql_table." (`server`, `group`, `subgroup`, `leaf`, `leafvalue`, `timestamp`) VALUES('$server', '$group', '$subgroup', '$leaf', '".addslashes($leafvalue)."', '".time()."')"; + $sql_update="UPDATE ".$c->container_cfg_mysql_table." SET `leafvalue`='".addslashes($leafvalue)."', `timestamp`='".time()."' WHERE `server`='$server' AND `group`='$group' AND `subgroup`='$subgroup' AND `leaf`='$leaf'"; + + $res = mysql_query($sql_count, $db); + $row = mysql_fetch_array($res, MYSQL_ASSOC); + if( $row['C'] == 0 ) + { + mysql_query($sql_insert, $db); + return 0; // value created + } + else + { + if ($sql_update != "") + { + mysql_query($sql_update, $db); + } + return 1; // value overwritten + } + } + + + function getMeta($group, $subgroup = null, $leaf = null, $withleafvalue = false) + { + $c =& pfcGlobalConfig::Instance(); + + $ret = array(); + $ret["timestamp"] = array(); + $ret["value"] = array(); + + $server = $c->serverid; + $db = $this->_connect(); + + $sql_where = ""; + $sql_group_by = ""; + $value = "leafvalue"; + + if ($group != NULL) + { + $sql_where .= " AND `group`='$group'"; + $value = "subgroup"; + $sql_group_by = "GROUP BY `$value`"; + } + + if ($subgroup != NULL) + { + $sql_where .= " AND `subgroup`='$subgroup'"; + $value = "leaf"; + $sql_group_by = ""; + } + + if ($leaf != NULL) + { + $sql_where .= " AND `leaf`='$leaf'"; + $value = "leafvalue"; + $sql_group_by = ""; + } + + $sql_select="SELECT `$value`, `timestamp` FROM ".$c->container_cfg_mysql_table." WHERE `server`='$server' $sql_where $sql_group_by ORDER BY timestamp"; + if ($sql_select != "") + { + $thisresult = mysql_query($sql_select, $db); + if (mysql_num_rows($thisresult)) + { + while ($regel = mysql_fetch_array($thisresult)) + { + $ret["timestamp"][] = $regel["timestamp"]; + if ($value == "leafvalue") + { + if ($withleafvalue) + $ret["value"][] = $regel[$value]; + else + $ret["value"][] = NULL; + } + else + $ret["value"][] = $regel[$value]; + } + + } + else + return $ret; + } + return $ret; + } + + + function incMeta($group, $subgroup, $leaf) + { + $c =& pfcGlobalConfig::Instance(); + + $server = $c->serverid; + $db = $this->_connect(); + $time = time(); + + // search for the existing leafvalue + $sql_count = "SELECT COUNT(*) AS C FROM ".$c->container_cfg_mysql_table." WHERE `server`='$server' AND `group`='$group' AND `subgroup`='$subgroup' AND `leaf`='$leaf' LIMIT 1"; + $res = mysql_query($sql_count, $db); + $row = mysql_fetch_array($res, MYSQL_ASSOC); + if( $row['C'] == 0 ) + { + $leafvalue = 1; + $sql_insert="REPLACE INTO ".$c->container_cfg_mysql_table." (`server`, `group`, `subgroup`, `leaf`, `leafvalue`, `timestamp`) VALUES('$server', '$group', '$subgroup', '$leaf', '".$leafvalue."', '".$time."')"; + mysql_query($sql_insert, $db); + } + else + { + $sql_update="UPDATE ".$c->container_cfg_mysql_table." SET `leafvalue`= LAST_INSERT_ID( leafvalue + 1 ), `timestamp`='".$time."' WHERE `server`='$server' AND `group`='$group' AND `subgroup`='$subgroup' AND `leaf`='$leaf'"; + mysql_query($sql_update, $db); + $res = mysql_query('SELECT LAST_INSERT_ID();', $db); + $row = mysql_fetch_array($res, MYSQL_ASSOC); + $leafvalue = $row['LAST_INSERT_ID()']; + } + + $ret["value"][] = $leafvalue; + $ret["timestamp"][] = $time; + + return $ret; + } + + + function rmMeta($group, $subgroup = null, $leaf = null) + { + $c =& pfcGlobalConfig::Instance(); + + $server = $c->serverid; + $db = $this->_connect(); + + $sql_delete = "DELETE FROM ".$c->container_cfg_mysql_table." WHERE `server`='$server'"; + + if($group != NULL) + $sql_delete .= " AND `group`='$group'"; + + if($subgroup != NULL) + $sql_delete .= " AND `subgroup`='$subgroup'"; + + if ($leaf != NULL) + $sql_delete .= " AND `leaf`='$leaf'"; + + mysql_query($sql_delete, $db); + return true; + } + + function encode($str) + { + return addslashes(urlencode($str)); + } + + function decode($str) + { + return urldecode(stripslashes($str)); + } + +} + +?> diff --git a/instmess/src/containers/oracle.class.php b/instmess/src/containers/oracle.class.php new file mode 100644 index 0000000..cd7785c --- /dev/null +++ b/instmess/src/containers/oracle.class.php @@ -0,0 +1,401 @@ + + * Modifications by Golemwashere + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/* +Oracle specific parameters: +$params["container_type"] = "oracle"; +$params["container_cfg_oracle_host"] = "localhost"; +$params["container_cfg_oracle_port"] = 1521; +$params["container_cfg_oracle_database"] = "XE"; +$params["container_cfg_oracle_table"] = "phpfreechat"; +$params["container_cfg_oracle_username"] = "orauser"; +$params["container_cfg_oracle_password"] = "orapw"; + +*/ + + +require_once dirname(__FILE__)."/../pfccontainer.class.php"; + +// include pear DB classes +require_once 'DB.php'; + +/** + * pfcContainer_Oracle is a concret container which store data into Oracle database + * + * + * @author Golemwashere + * @author Stephane Gully + * @author HenkBB + */ +class pfcContainer_Oracle extends pfcContainerInterface +{ + var $_db = null; + var $_sql_create_table = " + CREATE TABLE phpfreechat ( + server varchar2(200) NOT NULL default '', + groupg varchar2(200) NOT NULL default '', + subgroup varchar2(200) NOT NULL default '', + leaf varchar2(200) NOT NULL default '', + leafvalue varchar2(4000) NOT NULL, + timestampg number(20) NOT NULL default 0, +); + + PRIMARY KEY (server,groupg,subgroup,leaf); + INDEX (server,group,subgroupg,timestampg); + CREATE SEQUENCE phpfreechat_leafvalue_seq + + "; + + + function pfcContainer_Oracle() + { + pfcContainerInterface::pfcContainerInterface(); + } + + function getDefaultConfig() + { + $cfg = pfcContainerInterface::getDefaultConfig(); + $cfg["oracle_host"] = 'localhost'; + $cfg["oracle_port"] = 1521; + $cfg["oracle_database"] = 'XE'; + $cfg["oracle_table"] = 'phpfreechat'; + $cfg["oracle_username"] = 'phpfreechatuser'; + $cfg["oracle_password"] = 'freechatpass'; + return $cfg; + } + + function init(&$c) + { + + $errors = pfcContainerInterface::init($c); + + // connect to the db + $db = $this->_connect($c); + if ($db === FALSE) + { + $errors[] = _pfc("DB container: connect error"); + return $errors; + } + + // create the db if it doesn't exists + // golemwashere: commented out this part for now, DB must be manually created + /* + $db_exists = false; + $db_list = mysql_list_dbs($db); + while (!$db_exists && $row = mysql_fetch_object($db_list)) + $db_exists = ($c->container_cfg_mysql_database == $row->Database); + if (!$db_exists) + { + $query = 'CREATE DATABASE '.$c->container_cfg_mysql_database; + $result = mysql_query($query, $db); + if ($result === FALSE) + { + $errors[] = _pfc("Mysql container: create database error '%s'",mysql_error($db)); + return $errors; + } + mysql_select_db($c->container_cfg_mysql_database, $db); + } + + // create the table if it doesn't exists + $query = $this->_sql_create_table; + $query = str_replace('%engine%', $c->container_cfg_mysql_engine,$query); + $query = str_replace('%table%', $c->container_cfg_mysql_table,$query); + $query = str_replace('%fieldtype_server%', $c->container_cfg_mysql_fieldtype_server,$query); + $query = str_replace('%fieldtype_group%', $c->container_cfg_mysql_fieldtype_group,$query); + $query = str_replace('%fieldtype_subgroup%', $c->container_cfg_mysql_fieldtype_subgroup,$query); + $query = str_replace('%fieldtype_leaf%', $c->container_cfg_mysql_fieldtype_leaf,$query); + $query = str_replace('%fieldtype_leafvalue%', $c->container_cfg_mysql_fieldtype_leafvalue,$query); + $query = str_replace('%fieldtype_timestamp%', $c->container_cfg_mysql_fieldtype_timestamp,$query); + $result = mysql_query($query, $db); + if ($result === FALSE) + { + $errors[] = _pfc("Mysql container: create table error '%s'",mysql_error($db)); + return $errors; + } + return $errors; + */ + + } + + function _connect($c = null) + { + if (!$this->_db) + { + if ($c == null) $c =& pfcGlobalConfig::Instance(); + + $dsn = array( + 'phptype' => 'oci8', + 'username' => $c->container_cfg_oracle_username, + 'password' => $c->container_cfg_oracle_password, + 'hostspec' => '//'.$c->container_cfg_oracle_host.':'.$c->container_cfg_oracle_port.'/'.$c->container_cfg_oracle_database + ); + +$this->_db = DB::connect($dsn); +if (DB::isError($this->_db)) +{ + echo 'Cannot connect to database: ' . $this->_db->getMessage(); +} + + + + } + + + + return $this->_db; + } + + function setMeta($group, $subgroup, $leaf, $leafvalue = NULL) + { + $c =& pfcGlobalConfig::Instance(); + + $server = $c->serverid; + $db = $this->_connect(); + + if ($leafvalue == NULL){$leafvalue=" ";}; + # clean leafvalue: + $leafvalue=str_replace("'", "''", $leafvalue); + # GOLEMQUERY #1 + $sql_count = "SELECT COUNT(*) AS C FROM ".$c->container_cfg_oracle_table." WHERE server='$server' AND groupg='$group' AND subgroup='$subgroup' AND leaf='$leaf' and rownum <= 1"; + # GOLEMQUERY #2 + $sql_insert="INSERT INTO ".$c->container_cfg_oracle_table." (server, groupg, subgroup, leaf, leafvalue, timestampg) VALUES('$server', '$group', '$subgroup', '$leaf', '$leafvalue', trunc((to_number(cast((systimestamp AT TIME ZONE 'GMT') as date)-cast(TO_TIMESTAMP_TZ ('01-01-1970 00:00:00 GMT', 'DD-MM-YYYY HH24:MI:SS TZR') as date))*86400)))"; + # mysql was: + #$sql_update="UPDATE ".$c->container_cfg_mysql_table." SET `leafvalue`='".addslashes($leafvalue)."', `timestamp`='".time()."' WHERE `server`='$server' AND `group`='$group' AND `subgroup`='$subgroup' AND `leaf`='$leaf'"; + # GOLEMQUERY #3 + $sql_update="UPDATE ".$c->container_cfg_oracle_table." SET leafvalue='$leafvalue', timestampg= trunc((to_number(cast((systimestamp AT TIME ZONE 'GMT') as date)-cast(TO_TIMESTAMP_TZ ('01-01-1970 00:00:00 GMT', 'DD-MM-YYYY HH24:MI:SS TZR') as date))*86400)) WHERE server='$server' AND groupg='$group' AND subgroup='$subgroup' AND leaf='$leaf'"; + + if (DEBUGSQL) error_log("sql_count $sql_count"); + $res = $this->_db->query($sql_count); + if (DB::isError($res)) + { + error_log("sql_count error $sql_count " . $res->getMessage()); + } + + + $row = $res->fetchRow(DB_FETCHMODE_ASSOC); + +/* mysql was: + $res = mysql_query($sql_count, $db); + $row = mysql_fetch_array($res, MYSQL_ASSOC); +*/ + + if( $row['C'] == 0 ) + { + $res=$this->_db->query($sql_insert); + if (DB::isError($res)) { error_log("sql insert error: $sql_insert " . $res->getMessage()); } + if (DEBUGSQL) error_log("sql_insert: $sql_insert"); + return 0; // value created + } + else + { + if ($sql_update != "") + { + $res=$this->_db->query($sql_update); + if (DB::isError($res)) + { error_log("sql update error: $sql_update " . $res->getMessage()); } + if (DEBUGSQL) error_log("sql_update $sql_update"); + } + return 1; // value overwritten + } + } + + + function getMeta($group, $subgroup = null, $leaf = null, $withleafvalue = false) + { + $c =& pfcGlobalConfig::Instance(); + + $ret = array(); + $ret["timestamp"] = array(); + $ret["value"] = array(); + + $server = $c->serverid; + $db = $this->_connect(); + + $sql_where = ""; + $sql_group_by = ""; + $value = "leafvalue"; + + if ($group != NULL) + { + $sql_where .= " AND groupg='$group'"; + $value = "subgroup"; + #$sql_group_by = "GROUP BY '$value'"; + $sql_group_by = "GROUP BY $value"; + } + + if ($subgroup != NULL) + { + $sql_where .= " AND subgroup='$subgroup'"; + $value = "leaf"; + $sql_group_by = ""; + } + + if ($leaf != NULL) + { + $sql_where .= " AND leaf='$leaf'"; + $value = "leafvalue"; + $sql_group_by = ""; + } + + # GOLEMQUERY #4 + $sql_select="SELECT $value, timestampg FROM ".$c->container_cfg_oracle_table." WHERE server='$server' $sql_where $sql_group_by ORDER BY timestampg"; + if ($sql_select != "") + { + $thisresult = $this->_db->query($sql_select); + if (DEBUGSQL) error_log("sql_select: $sql_select"); + if (DB::isError($thisresult)) { error_log("sql_select error $sql_select " . $thisresult->getMessage()); } + + + #if (mysql_num_rows($thisresult)) + $this->_db->setOption('portability', DB_PORTABILITY_NUMROWS); + + #error_log("numrows $numrows"); + + if ($thisresult->numRows()) + { + #while ($regel = mysql_fetch_array($thisresult)) + while ($regel = $thisresult->fetchRow(DB_FETCHMODE_ASSOC)) + { + $ret["timestamp"][] = $regel["TIMESTAMPG"]; + if ($value == "leafvalue") + { + if ($withleafvalue) + $ret["value"][] = $regel[strtoupper($value)]; + else + $ret["value"][] = NULL; + } + else + $ret["value"][] = $regel[strtoupper($value)]; + } + + } + else + return $ret; + } + return $ret; + } + + + function incMeta($group, $subgroup, $leaf) + { + $c =& pfcGlobalConfig::Instance(); + + $server = $c->serverid; + $db = $this->_connect(); + $time = time(); + + // search for the existing leafvalue + # GOLEMQUERY #5 + $sql_count = "SELECT COUNT(*) AS C FROM ".$c->container_cfg_oracle_table." WHERE server='$server' AND groupg='$group' AND subgroup='$subgroup' AND leaf='$leaf' and rownum <= 1"; + $res = $this->_db->query($sql_count); + if (DB::isError($res)) { error_log("sql_count error $sql_count " . $res->getMessage()); } + if (DEBUGSQL) error_log("sql select $sql_count"); + $row = $res->fetchRow(DB_FETCHMODE_ASSOC); + #$res = mysql_query($sql_count, $db); + #$row = mysql_fetch_array($res, MYSQL_ASSOC); + if( $row['C'] == 0 ) + { + $leafvalue = 1; + #$sql_insert="REPLACE INTO ".$c->container_cfg_mysql_table." (`server`, `group`, `subgroup`, `leaf`, `leafvalue`, `timestamp`) VALUES('$server', '$group', '$subgroup', '$leaf', '".$leafvalue."', '".$time."')"; + # GOLEMQUERY # 6 + $sql_insert="INSERT INTO ".$c->container_cfg_oracle_table." (server, groupg, subgroup, leaf, leafvalue, timestampg) VALUES('$server', '$group', '$subgroup', '$leaf','$leafvalue', trunc((to_number(cast((systimestamp AT TIME ZONE 'GMT') as date)-cast(TO_TIMESTAMP_TZ ('01-01-1970 00:00:00 GMT', 'DD-MM-YYYY HH24:MI:SS TZR') as date))*86400)))"; + + #mysql_query($sql_insert, $db); + $res=$this->_db->query($sql_insert); + if (DB::isError($res)){ error_log("sql insert error $sql_insert " . $res->getMessage()); } + if (DEBUGSQL) error_log("sql_insert $sql_insert"); + } + else + { + # mysql was: + #$sql_update="UPDATE ".$c->container_cfg_mysql_table." SET leafvalue= LAST_INSERT_ID( leafvalue + 1 ), `timestamp`='".$time."' WHERE server='$server' AND groupg='$group' AND subgroup='$subgroup' AND leaf='$leaf'"; + # GOLEMQUERY #7 + # test using sequence nextval + $sql_update="UPDATE ".$c->container_cfg_oracle_table." SET leafvalue= phpfreechat_leafvalue_seq.NEXTVAL, timestampg=trunc((to_number(cast((systimestamp AT TIME ZONE 'GMT') as date)-cast(TO_TIMESTAMP_TZ ('01-01-1970 00:00:00 GMT', 'DD-MM-YYYY HH24:MI:SS TZR') as date))*86400)) WHERE server='$server' AND groupg='$group' AND subgroup='$subgroup' AND leaf='$leaf'"; + + $res=$this->_db->query($sql_update); + if (DB::isError($res)){ error_log("problema update: $sql_update " . $res->getMessage()); } + if (DEBUGSQL) error_log("sql_update $sql_update"); + # + # GOLEMQUERY #8 + # test using sequence currval + $sql_last="SELECT phpfreechat_leafvalue_seq.currVAL as lastleaf FROM dual"; + $res = $this->_db->query($sql_last); + if (DB::isError($res)) { error_log("error in SELECT lastleaf $sql_last" . $res->getMessage()); } + if (DEBUGSQL) error_log("select: SELECT phpfreechat_leafvalue_seq.currVAL as lastleaf FROM dual"); + #$row = mysql_fetch_array($res, MYSQL_ASSOC); + $row = $res->fetchRow(DB_FETCHMODE_ASSOC); + $leafvalue = $row['LASTLEAF']; + } + + $ret["value"][] = $leafvalue; + $ret["timestamp"][] = $time; + + return $ret; + } + + + function rmMeta($group, $subgroup = null, $leaf = null) + { + $c =& pfcGlobalConfig::Instance(); + + $server = $c->serverid; + $db = $this->_connect(); + # GOLEMQUERY #9 + $sql_delete = "DELETE FROM ".$c->container_cfg_oracle_table." WHERE server='$server'"; + + if($group != NULL) + $sql_delete .= " AND groupg='$group'"; + + if($subgroup != NULL) + $sql_delete .= " AND subgroup='$subgroup'"; + + if ($leaf != NULL) + $sql_delete .= " AND leaf='$leaf'"; + + #mysql_query($sql_delete, $db); + $res=$this->_db->query($sql_delete); + if (DB::isError($res)) + { error_log('sql_delete $sql_delete ' . $res->getMessage()); } + + if (DEBUGSQL) error_log("sql_delete $sql_delete"); + + return true; + } + + function encode($str) + { + return $str; + //return addslashes(urlencode($str)); + } + + function decode($str) + { + return $str; + //return urldecode(stripslashes($str)); + } + + + +} + +?> diff --git a/instmess/src/pfccommand.class.php b/instmess/src/pfccommand.class.php new file mode 100644 index 0000000..44d24e6 --- /dev/null +++ b/instmess/src/pfccommand.class.php @@ -0,0 +1,401 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +require_once dirname(__FILE__)."/pfci18n.class.php"; +require_once dirname(__FILE__)."/pfcuserconfig.class.php"; + +/** + * pfcCommand is an abstract class (interface) which must be inherited by each concrete commands + * Commands examples : /nick /me /update ... + * + * @example ../demo/demo27_customized_command.php + * @author Stephane Gully + */ +class pfcCommand +{ + /** + * Command name (lowercase) + */ + var $name = ''; + + /** + * Contains the command syntaxe (how to use the command) + */ + var $usage = ''; + + /** + * Not used for now + */ + var $desc = ''; + var $help = ''; + + /** + * Used to instanciate a command + * $tag is the command name : "nick", "me", "update" ... + */ + function &Factory($name) + { + $c =& pfcGlobalConfig::Instance(); + + // instanciate the real command + $cmd = NULL; + $cmd_name = $name; + $cmd_classname = "pfcCommand_".$name; + + $cmd_filename = $c->cmd_path_default.'/'.$cmd_name.'.class.php'; + if (file_exists($cmd_filename)) require_once($cmd_filename); + $cmd_filename = $c->cmd_path.'/'.$cmd_name.'.class.php'; + if (file_exists($cmd_filename)) require_once($cmd_filename); + + if (!class_exists($cmd_classname)) { $tmp = NULL; return $tmp; } + + $firstproxy = new $cmd_classname; + $firstproxy->name = $cmd_name; + + // instanciate the proxies chaine + $proxies = array(); + for($i = count($c->proxies)-1; $i >= 0; $i--) + { + $proxy_name = $c->proxies[$i]; + $proxy_classname = "pfcProxyCommand_" . $proxy_name; + + // try to include the proxy class file from the default path or from the customized path + $proxy_filename = $c->proxies_path_default.'/'.$proxy_name.".class.php"; + if (file_exists($proxy_filename)) require_once($proxy_filename); + $proxy_filename = $c->proxies_path.'/'.$proxy_name.".class.php"; + if (file_exists($proxy_filename)) require_once($proxy_filename); + + if (!class_exists($proxy_classname)) + return $firstproxy; + + // instanciate the proxy + $proxies[$i] = new $proxy_classname; + $proxies[$i]->name = $cmd_name; + $proxies[$i]->proxyname = $proxy_name; + $proxies[$i]->linkTo($firstproxy); + $firstproxy =& $proxies[$i]; + } + + /* + $tmp = ''; + $cur = $firstproxy; + while($cur) + { + $tmp .= (isset($cur->proxyname)?$cur->proxyname:$cur->name).'|'; + $cur = $cur->next; + } + $tmp .= var_export($firstproxy,true); + file_put_contents('/tmp/debug1',$tmp); +*/ + + // return the proxy, not the command (the proxy will forward the request to the real command) + return $firstproxy; + } + + /** + * Constructor + * @private + */ + function pfcCommand() + { + } + + /** + * Virtual methode which must be implemented by concrete commands + * It is called by the phpFreeChat::HandleRequest function to execute the wanted command + */ + function run(&$xml_reponse, $p) + { + die(_pfc("%s must be implemented", get_class($this)."::".__FUNCTION__)); + } + + /** + * Force whois reloading + */ + function forceWhoisReload($nickid) + { + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + $ct =& pfcContainer::Instance(); + + // list the users in the same channel as $nickid + $channels = $ct->getMeta("nickid-to-channelid", $nickid); + $channels = $channels['value']; + $channels = array_diff($channels, array('SERVER')); + $otherids = array(); + foreach($channels as $chan) + { + $ret = $ct->getOnlineNick($ct->decode($chan)); + $otherids = array_merge($otherids, $ret['nickid']); + } + + // alert them that $nickid user info just changed + foreach($otherids as $otherid) + { + $cmdstr = 'whois2'; + $cmdp = array(); + $cmdp['params'] = array($nickid); + pfcCommand::AppendCmdToPlay($otherid, $cmdstr, $cmdp); + + /* + $cmdtoplay = $ct->getUserMeta($otherid, 'cmdtoplay'); + $cmdtoplay = ($cmdtoplay == NULL) ? array() : unserialize($cmdtoplay); + $cmdtmp = array("whois2", // cmdname + $nicktorewhois, // param + NULL, // sender + NULL, // recipient + NULL, // recipientid + ); + if (!in_array($cmdtmp, $cmdtoplay)) + { + $cmdtoplay[] = $cmdtmp; + $ct->setUserMeta($otherid, 'cmdtoplay', serialize($cmdtoplay)); + } + */ + } + } + + /** + * Add command to be played onto command stack + * @param $nickid is the user that entered the command + * @param $cmdstr is the command + * @param $cmdp is the command's parameters + * @return false if $nickid is blank, true for all other values of $nickid + */ + function AppendCmdToPlay($nickid, $cmdstr, $cmdp) + { + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + + $ct =& pfcContainer::Instance(); + + // check for empty nickid + if ($nickid == "") return false; + + // get new command id + $cmdtoplay_id = $ct->incMeta("nickid-to-cmdtoplayid", $nickid, 'cmdtoplayid'); + if (count($cmdtoplay_id["value"]) == 0) + $cmdtoplay_id = 0; + else + $cmdtoplay_id = $cmdtoplay_id["value"][0]; + + // create command array + $cmdtoplay = array(); + $cmdtoplay['cmdstr'] = $cmdstr; + $cmdtoplay['params'] = $cmdp; + + // store command to play + $ct->setCmdMeta($nickid, $cmdtoplay_id, serialize($cmdtoplay)); + + return true; + } + + + /** + * Run all commands to be played for a user + * @param $nickid is the user that entered the command + * @param $context + * @param $xml_reponse + */ + function RunPendingCmdToPlay($nickid, $context, &$xml_reponse) + { + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + $ct =& pfcContainer::Instance(); + + // Get all queued commands to be played + $cmdtoplay_ids = $ct->getCmdMeta($nickid); + // process each command and parse content + foreach ( $cmdtoplay_ids as $cid ) + { + // take a command from the list + $cmdtoplay = $ct->getCmdMeta($nickid, $cid); + $cmdtoplay = ($cmdtoplay == NULL || count($cmdtoplay) == 0) ? array() : unserialize($cmdtoplay[0]); + + // play the command + $cmd =& pfcCommand::Factory($cmdtoplay['cmdstr']); + $cmdp = $cmdtoplay['params']; + if (!isset($cmdp['param'])) $cmdp['param'] = ''; + if (!isset($cmdp['sender'])) $cmdp['sender'] = $context['sender']; + if (!isset($cmdp['recipient'])) $cmdp['recipient'] = $context['recipient']; + if (!isset($cmdp['recipientid'])) $cmdp['recipientid'] = $context['recipientid']; + $cmdp['clientid'] = $context['clientid']; // the clientid must be the current user one + $cmdp['cmdtoplay'] = true; // used to run some specials actions in the command (ex: if the cmdtoplay is a 'leave' command, then show an alert to the kicked or banished user) + if ($c->debug) + $cmd->run($xml_reponse, $cmdp); + else + @$cmd->run($xml_reponse, $cmdp); + + // delete command when complete + $ct->rmMeta("nickid-to-cmdtoplay", $nickid, $cid); + } + } + + + function trace(&$xml_reponse, $msg, $data = NULL) + { + if ($data != NULL) + { + require_once dirname(__FILE__).'/pfcjson.class.php'; + $json = new pfcJSON(); + $js = $json->encode($data); + $xml_reponse->script("trace('".$msg." -> ".$js."');"); + } + else + $xml_reponse->script("trace('".$msg."');"); + + } + + function ParseCommand($cmd_str, $one_parameter = false) + { + $pattern_quote = '/([^\\\]|^)"([^"]+[^\\\])"/'; + $pattern_quote = '/"([^"]+)"/'; + $pattern_noquote = '/([^"\s]+)/'; + $pattern_command = '/^\/([a-z0-9]+)\s*([a-z0-9]+)\s*([a-z0-9]+)\s*(.*)/'; + $result = array(); + + // parse the command name (ex: '/invite') + if (preg_match($pattern_command, $cmd_str, $res)) + { + $cmd = $res[1]; + $clientid = $res[2]; + $recipientid = $res[3]; + $params_str = $res[4]; + + // don't parse multiple parameters for special commands with only one parameter + // this make possible to send double quotes (") in these commands + if ($one_parameter || $cmd == 'send' || $cmd == 'notice' || $cmd == 'me') + { + $result['cmdstr'] = $cmd_str; + $result['cmdname'] = $cmd; + $result['params'] = array($clientid, $recipientid, $params_str); + return $result; + } + + + // parse the quotted parameters (ex: '/invite "nickname with spaces"') + preg_match_all($pattern_quote,$params_str,$res1,PREG_OFFSET_CAPTURE); + $params_res = $res1[1]; + // split the parameters string + $nospaces = preg_split($pattern_quote,$params_str,-1,PREG_SPLIT_OFFSET_CAPTURE|PREG_SPLIT_NO_EMPTY); + foreach($nospaces as $p) + { + // parse the splited blocks with unquotted parameter pattern (ex: '/invite nicknamewithoutspace') + preg_match_all($pattern_noquote,$p[0],$res2,PREG_OFFSET_CAPTURE); + foreach( $res2[1] as $p2 ) + { + $p2[1] += $p[1]; + $params_res[] = $p2; + } + } + + // order the array by offset + $params = array(); + foreach($params_res as $p) $params[$p[1]] = $p[0]; + ksort($params); + $params = array_values($params); + $params = array_map("trim",$params); + $params = array_merge(array($clientid,$recipientid), $params); + + $result['cmdstr'] = $cmd_str; + $result['cmdname'] = $cmd; + $result['params'] = $params; + } + return $result; + } + + /* + // THIS IS ANOTHER WAY TO PARSE THE PARAMETERS + // IT'S NOT SIMPLIER BUT MAYBE FASTER + // @todo : take the faster methode + function ParseCommand($cmd_str, $one_parameter = false) + { + $pattern_command = '/^\/([a-z0-9]+)\s*([a-z0-9]+)\s*([a-z0-9]+)\s*(.*)/'; + $result = array(); + + // parse the command name (ex: '/invite') + if (preg_match($pattern_command, $cmd_str, $res)) + { + $cmd = $res[1]; + $clientid = $res[2]; + $recipientid = $res[3]; + $params_str = $res[4]; + + // don't parse multiple parameters for special commands with only one parameter + // this make possible to send double quotes (") in these commands + if ($one_parameter || $cmd == 'send' || $cmd == 'notice' || $cmd == 'me') + { + $result['cmdstr'] = $cmd_str; + $result['cmdname'] = $cmd; + $result['params'] = array($clientid, $recipientid, $params_str); + return $result; + } + + $params = array($clientid, $recipientid); + $sep = preg_match('/[^\\\\]"/',$params_str) ? '"' : ' '; + if ($sep == ' ') $params_str = ' ' . $params_str; + $offset = 0; + while (1) + { + $i1 = strpos($params_str,$sep,$offset); + // capture the parameter value + if ($i1 !== FALSE) + { + // remove multi-separators + while (1) + { + if (strpos($params_str,$sep,$i1+1) - $i1 == 1) + $i1++; + else + break; + } + // search the parameter terminason + $offset = $i1+1; + $i2 = strpos($params_str,$sep,$offset); + if ($i2 !== FALSE) + { + $offset = $i2 + ($sep == '"' ? 1 : 0); + $p = substr($params_str, $i1+1, $i2-$i1-1); + if (!preg_match('/^\s*$/',$p)) + $params[] = $p; + } + else + break; + } + else + break; + } + // append the tail + if ($offset < strlen($params_str)) + $params[] = substr($params_str,$offset); + + $result['cmdstr'] = $cmd_str; + $result['cmdname'] = $cmd; + $result['params'] = $params; + } + return $result; + } +*/ + + +} + +?> \ No newline at end of file diff --git a/instmess/src/pfccontainer.class.php b/instmess/src/pfccontainer.class.php new file mode 100644 index 0000000..30b2246 --- /dev/null +++ b/instmess/src/pfccontainer.class.php @@ -0,0 +1,772 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + + require_once dirname(__FILE__)."/pfccontainerinterface.class.php"; + require_once dirname(__FILE__)."/pfcurlprocessing.php"; + +/** + * pfcContainer is an abstract class which define interface + * to be implemented by concrete container (example: File) + * + * @author Stephane Gully + * @abstract + */ +class pfcContainer extends pfcContainerInterface +{ + var $_container = null; // contains the concrete container instance + var $_usememorycache = true; + + + function &Instance($type = 'File', $usememorycache = true) + { + static $i; + if (!isset($i)) + $i = new pfcContainer($type, $usememorycache); + return $i; + } + + function pfcContainer($type = 'File', $usememorycache = true) + { + pfcContainerInterface::pfcContainerInterface(); + + $this->_usememorycache = $usememorycache; + $type = strtolower($type); + + // create the concrete container instance + require_once dirname(__FILE__)."/containers/".$type.".class.php"; + $container_classname = "pfcContainer_".$type; + $this->_container = new $container_classname(); + } + function getDefaultConfig() + { + if ($this->_container) + return $this->_container->getDefaultConfig(); + else + return array(); + } + function init(&$c) + { + if ($this->_container) + return $this->_container->init($c); + } + + /** + * Create (connect/join) the nickname into the server or the channel locations + * Notice: the caller must take care to update all channels the users joined (use stored channel list into metadata) + * @param $chan if NULL then create the user on the server (connect), otherwise create the user on the given channel (join) + * @param $nick the nickname to create + * @param $nickid is the corresponding nickname id (taken from session) + */ + /* + function createNick($chan, $nick, $nickid) + { + $c =& pfcGlobalConfig::Instance(); + + if ($nick == '') + user_error('pfcContainer::createNick nick is empty', E_USER_ERROR); + if ($nickid == '') + user_error('pfcContainer::createNick nickid is empty', E_USER_ERROR); + + if ($chan == NULL) $chan = 'SERVER'; + + $this->setMeta("nickid-to-metadata", $nickid, 'nick', $nick); + $this->setMeta("metadata-to-nickid", 'nick', $this->encode($nick), $nickid); + + $this->setMeta("nickid-to-channelid", $nickid, $this->encode($chan)); + $this->setMeta("channelid-to-nickid", $this->encode($chan), $nickid); + + // update the SERVER channel + if ($chan != 'SERVER') $this->updateNick($nickid); + + return true; + } + */ + + function createNick($nickid, $nick) + { + $c =& pfcGlobalConfig::Instance(); + + if ($nick == '') + user_error('pfcContainer::createNick nick is empty', E_USER_ERROR); + if ($nickid == '') + user_error('pfcContainer::createNick nickid is empty', E_USER_ERROR); + + $this->setMeta("nickid-to-metadata", $nickid, 'nick', $nick); + $this->setMeta("metadata-to-nickid", 'nick', $this->encode($nick), $nickid); + + return true; + } + + + function joinChan($nickid, $chan) + { + $c =& pfcGlobalConfig::Instance(); + + if ($nickid == '') + user_error('pfcContainer::joinChan nickid is empty', E_USER_ERROR); + + if ($chan == NULL) $chan = 'SERVER'; + + $this->setMeta("nickid-to-channelid", $nickid, $this->encode($chan)); + $this->setMeta("channelid-to-nickid", $this->encode($chan), $nickid); + + // update the SERVER channel + if ($chan == 'SERVER') $this->updateNick($nickid); + + return true; + } + + + + /** + * Remove (disconnect/quit) the nickname from the server or from a channel + * Notice: when a user quit, the caller must take care removeNick from each channels ('SERVER' included) + * This function takes care to remove all users metadata when he his disconnected from all channels + * @param $chan if NULL then remove the user from the 'SERVER' channel, otherwise just remove the user from the given channel (quit) + * @param $nickid the nickname id to remove + * @return array which contains removed user infos ('nickid', 'nick', 'timestamp') + */ + function removeNick($chan, $nickid) + { + $c =& pfcGlobalConfig::Instance(); + + if ($chan == NULL) $chan = 'SERVER'; + + $deleted_user = array(); + $deleted_user["nick"] = array(); + $deleted_user["nickid"] = array(); + $deleted_user["timestamp"] = array(); + + if (!$nickid) return $deleted_user; + + $timestamp = $this->getMeta("channelid-to-nickid", $this->encode('SERVER'), $nickid); + if (count($timestamp["timestamp"]) == 0) return $deleted_user; + $timestamp = $timestamp["timestamp"][0]; + + $deleted_user["nick"][] = $this->getNickname($nickid); + $deleted_user["nickid"][] = $nickid; + $deleted_user["timestamp"][] = $timestamp; + + // remove the nickid from the channel list + $this->rmMeta('channelid-to-nickid', $this->encode($chan), $nickid); + $this->rmMeta('nickid-to-channelid', $nickid, $this->encode($chan)); + + // if the user is the last one to quit this room, + // and this room is not a default room, + // then clean the room history + $channels = array(); + foreach($c->channels as $cc) + $channels[] = 'ch_'.$cc; // @todo clean this piece of code when the chan and chanid will be refactored + if (!in_array($chan, $channels)) + { + $ret = $this->getOnlineNick($chan); + if (count($ret['nickid']) == 0) + { + $this->rmMeta('channelid-to-msg', $this->encode($chan)); + $this->rmMeta('channelid-to-msgid', $this->encode($chan)); + } + } + + // get the current user's channels list + $channels = $this->getMeta("nickid-to-channelid",$nickid); + $channels = $channels["value"]; + // no more joined channel, just remove the user's metadata + if (count($channels) == 0) + { + // remove the nickname to nickid correspondance + $this->rmMeta('metadata-to-nickid', 'nick', $this->encode($this->getNickname($nickid))); + // remove disconnected nickname metadata + $this->rmMeta('nickid-to-metadata', $nickid); + // remove users commands in queue + $this->rmMeta("nickid-to-cmdtoplay", $nickid); + $this->rmMeta("nickid-to-cmdtoplayid", $nickid); + } + + return $deleted_user; + } + + /** + * Store/update the alive user status on the 'SERVER' channel + * The default File container will just touch (update the date) of the nickname file in the 'SERVER' channel. + * @param $nickid the nickname id to keep alive + */ + function updateNick($nickid) + { + $c =& pfcGlobalConfig::Instance(); + + $chan = 'SERVER'; + + $this->setMeta("nickid-to-channelid", $nickid, $this->encode($chan)); + $this->setMeta("channelid-to-nickid", $this->encode($chan), $nickid); + return true; + } + + /** + * Change the user's nickname + * As nickname value are stored in user's metadata, this function just update the 'nick' metadata + * @param $newnick + * @param $oldnick + * @return true on success, false on failure (if the oldnick doesn't exists) + */ + function changeNick($newnick, $oldnick) + { + $c =& pfcGlobalConfig::Instance(); + + $oldnickid = $this->getNickId($oldnick); + $newnickid = $this->getNickId($newnick); + if ($oldnickid == "") return false; // the oldnick must be connected + if ($newnickid != "") return false; // the newnick must not be inuse + + // remove the oldnick to oldnickid correspondance + $this->rmMeta("metadata-to-nickid", 'nick', $this->encode($oldnick)); + + // update the nickname + $this->setMeta("nickid-to-metadata", $oldnickid, 'nick', $newnick); + $this->setMeta("metadata-to-nickid", 'nick', $this->encode($newnick), $oldnickid); + return true; + } + + /** + * Returns the nickid corresponding to the given nickname + * The nickid is a unique id used to identify a user (generated from the browser sessionid) + * The nickid is stored in the container when createNick is called. + * @param $nick + * @return string the nick id + */ + function getNickId($nick) + { + $nickid = $this->getMeta("metadata-to-nickid", 'nick', $this->encode($nick), true); + $nickid = isset($nickid["value"][0]) ? $nickid["value"][0] : ""; + return $nickid; + } + + /** + * Returns the nickname corresponding the the given nickid + * @param $nickid + * @return string the corresponding nickname + */ + function getNickname($nickid) + { + $nick = $this->getMeta("nickid-to-metadata", $nickid, 'nick', true); + $nick = isset($nick["value"][0]) ? $this->decode($nick["value"][0]) : ""; + return $nick; + } + + /** + * Remove (disconnect/quit) the timeouted nicknames + * Notice: this function will remove all nicknames which are not uptodate from all his joined channels + * @param $timeout + * @return array("nickid"=>array("nickid1", ...),"timestamp"=>array(timestamp1, ...)) contains all disconnected nickids and there timestamp + */ + function removeObsoleteNick($timeout) + { + $c =& pfcGlobalConfig::Instance(); + + $deleted_user = array('nick'=>array(), + 'nickid'=>array(), + 'timestamp'=>array(), + 'channels'=>array()); + $ret = $this->getMeta("channelid-to-nickid", $this->encode('SERVER')); + for($i = 0; $i ($timestamp+$timeout/1000) && $nickid) // user will be disconnected after 'timeout' secondes of inactivity + { + // get the current user's channels list + $channels = array(); + $ret2 = $this->getMeta("nickid-to-channelid",$nickid); + foreach($ret2["value"] as $userchan) + { + $userchan = $this->decode($userchan); + if ($userchan != 'SERVER') + { + // disconnect the user from each joined channels + $this->removeNick($userchan, $nickid); + $channels[] = $userchan; + } + } + // now disconnect the user from the server + // (order is important because the SERVER channel has timestamp informations) + $du = $this->removeNick('SERVER', $nickid); + $channels[] = 'SERVER'; + + $deleted_user["nick"] = array_merge($deleted_user["nick"], $du["nick"]); + $deleted_user["nickid"] = array_merge($deleted_user["nickid"], $du["nickid"]); + $deleted_user["timestamp"] = array_merge($deleted_user["timestamp"], $du["timestamp"]); + $deleted_user["channels"] = array_merge($deleted_user["channels"], array($channels)); + } + } + + return $deleted_user; + } + + /** + * Returns the nickname list on the given channel or on the whole server + * @param $chan if NULL then returns all connected user, otherwise just returns the channel nicknames + * @return array("nickid"=>array("nickid1", ...),"timestamp"=>array(timestamp1, ...)) contains the nickid list with the associated timestamp (laste update time) + */ + function getOnlineNick($chan) + { + $c =& pfcGlobalConfig::Instance(); + + if ($chan == NULL) $chan = 'SERVER'; + + $online_user = array('nick'=>array(),'nickid'=>array(),'timestamp'=>array()); + $ret = $this->getMeta("channelid-to-nickid", $this->encode($chan)); + for($i = 0; $igetMeta("channelid-to-nickid", $this->encode('SERVER'), $nickid); + if (count($timestamp['timestamp']) == 0) continue; + $timestamp = $timestamp['timestamp'][0]; + + $online_user["nick"][] = $this->getNickname($nickid); + $online_user["nickid"][] = $nickid; + $online_user["timestamp"][] = $timestamp; + } + return $online_user; + } + + /** + * Returns returns a positive number if the nick is online in the given channel + * @param $chan if NULL then check if the user is online on the server, otherwise check if the user has joined the channel + * @return false if the user is off line, true if the user is online + */ + function isNickOnline($chan, $nickid) + { + if (!$nickid) return false; + if ($chan == NULL) $chan = 'SERVER'; + + $ret = $this->getMeta("channelid-to-nickid", + $this->encode($chan), + $nickid); + + return (count($ret['timestamp']) > 0); + } + + /** + * Write a command to the given channel or to the server + * Notice: a message is very generic, it can be a misc command (notice, me, ...) + * @param $chan if NULL then write the message on the server, otherwise just write the message on the channel message pool + * @param $nick is the sender nickname + * @param $cmd is the command name (ex: "send", "nick", "kick" ...) + * @param $param is the command' parameters (ex: param of the "send" command is the message) + * @return $msg_id the created message identifier + */ + function write($chan, $nick, $cmd, $param) + { + $c =& pfcGlobalConfig::Instance(); + if ($chan == NULL) $chan = 'SERVER'; + + $msgid = $this->_requestMsgId($chan); + + // format message + $data = "\n"; + $data .= $msgid."\t"; + $data .= time()."\t"; + $data .= $nick."\t"; + $data .= $cmd."\t"; + $data .= $param; + + // write message + $this->setMeta("channelid-to-msg", $this->encode($chan), $msgid, $data); + + // delete the obsolete message + $old_msgid = $msgid - $c->max_msg - 20; + if ($old_msgid > 0) + $this->rmMeta("channelid-to-msg", $this->encode($chan), $old_msgid); + + return $msgid; + } + + /** + * Read the last posted commands from a channel or from the server + * Notice: the returned array is ordered by id + * @param $chan if NULL then read from the server, otherwise read from the given channel + * @param $from_id read all message with a greater id + * @return array() contains the formated command list + */ + function read($chan, $from_id) + { + $c =& pfcGlobalConfig::Instance(); + if ($chan == NULL) $chan = 'SERVER'; + + // read new messages content + parse content + $new_from_id = $this->getLastId($chan); + $datalist = array(); + for ( $mid = $from_id; $mid <= $new_from_id; $mid++ ) + { + $line = $this->getMeta("channelid-to-msg", $this->encode($chan), $mid, true); + $line = $line["value"][0]; + if ($line != "" && $line != "\n") + { + $formated_line = explode( "\t", $line ); + $data = array(); + $data["id"] = trim($formated_line[0]); + $data["timestamp"] = $formated_line[1]; + $data["sender"] = $formated_line[2]; + $data["cmd"] = $formated_line[3]; + // convert URLs to html + $data["param"] = $formated_line[4]; + $datalist[$data["id"]] = $data; + } + } + return array("data" => $datalist, + "new_from_id" => $new_from_id+1 ); + } + + /** + * Returns the last message id + * Notice: the default file container just returns the messages.index file content + * @param $chan if NULL then read if from the server, otherwise read if from the given channel + * @return int is the last posted message id + */ + function getLastId($chan) + { + if ($chan == NULL) $chan = 'SERVER'; + + $lastmsgid = $this->getMeta("channelid-to-msgid", $this->encode($chan), 'lastmsgid', true); + if (count($lastmsgid["value"]) == 0) + $lastmsgid = 0; + else + $lastmsgid = $lastmsgid["value"][0]; + return $lastmsgid; + } + + + /** + * Return a unique id. Each time this function is called, the last id is incremented. + * used internaly + * @private + */ + function _requestMsgId($chan) + { + if ($chan == NULL) $chan = 'SERVER'; + + $lastmsgid = $this->incMeta("channelid-to-msgid", $this->encode($chan), 'lastmsgid'); + + if (count($lastmsgid["value"]) == 0) + $lastmsgid = 0; + else + $lastmsgid = $lastmsgid["value"][0]; + return $lastmsgid; + } + + /** + * Remove all created data for this server (identified by serverid) + * Notice: for the default File container, it's just a recursive directory remove + */ + function clear() + { + $this->rmMeta(NULL); + } + + function getAllUserMeta($nickid) + { + $result = array(); + $ret = $this->getMeta("nickid-to-metadata", $nickid); + foreach($ret["value"] as $k) + $result[$k] = $this->getUserMeta($nickid, $k); + // $result['chanid'] = $this->getMeta("nickid-to-channelid", $nickid); + // $result['chanid'] = $result['chanid']['value']; + return $result; + } + + function getUserMeta($nickid, $key = NULL) + { + $ret = $this->getMeta("nickid-to-metadata", $nickid, $key, true); + return isset($ret['value'][0]) ? $ret['value'][0] : NULL; + } + + function setUserMeta($nickid, $key, $value) + { + $ret = $this->setMeta("nickid-to-metadata", $nickid, $key, $value); + return $ret; + } + + function getCmdMeta($nickid, $key = NULL) + { + $ret = $this->getMeta("nickid-to-cmdtoplay", $nickid, $key, true); + return $ret['value']; + } + + function setCmdMeta($nickid, $key, $value) + { + $ret = $this->setMeta("nickid-to-cmdtoplay", $nickid, $key, $value); + return $ret; + } + + function getAllChanMeta($chan) + { + $result = array(); + $ret = $this->getMeta("channelid-to-metadata", $this->encode($chan)); + foreach($ret["value"] as $k) + $result[$k] = $this->getChanMeta($chan, $k); + return $result; + } + + function getChanMeta($chan, $key = NULL) + { + $ret = $this->getMeta("channelid-to-metadata", $this->encode($chan), $key, true); + return isset($ret['value'][0]) ? $ret['value'][0] : NULL; + } + + function setChanMeta($chan, $key, $value) + { + $ret = $this->setMeta("channelid-to-metadata", $this->encode($chan), $key, $value); + return $ret; + } + + var $_cache = array(); + + /** + * Write a meta data value identified by a group / subgroup / leaf [with a value] + * As an example in the default file container this arborescent structure is modelised by simple directories + * group1/subgroup1/leaf1 + * /leaf2 + * /subgroup2/... + * Each leaf can contain or not a value. + * However each leaf and each group/subgroup must store the lastmodified time (timestamp). + * @param $group root arborescent element + * @param $subgroup is the root first child which contains leafs + * @param $leaf is the only element which can contain values + * @param $leafvalue NULL means the leaf will not contain any values + * @return 1 if the old leaf has been overwritten, 0 if a new leaf has been created + */ + function setMeta($group, $subgroup, $leaf, $leafvalue = NULL) + { + $ret = $this->_container->setMeta($group, $subgroup, $leaf, $leafvalue); + + if ($this->_usememorycache) + { + // store the modifications in the cache + if (isset($this->_cache[$group]['value']) && + !in_array($subgroup, $this->_cache[$group]['value'])) + { + $this->_cache[$group]['value'][] = $subgroup; + $this->_cache[$group]['timestamp'][] = time(); + } + if (isset($this->_cache[$group]['childs'][$subgroup]['value']) && + !in_array($leaf, $this->_cache[$group]['childs'][$subgroup]['value'])) + { + $this->_cache[$group]['childs'][$subgroup]['value'][] = $leaf; + $this->_cache[$group]['childs'][$subgroup]['timestamp'][] = time(); + } + $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['value'] = array($leafvalue); + $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['timestamp'] = array(time()); + } + + return $ret; + } + + + /** + * Read meta data identified by a group [/ subgroup [/ leaf]] + * @param $group is mandatory, it's the arborescence's root + * @param $subgroup if null then the subgroup list names are returned + * @param $leaf if null then the leaf names are returned + * @param $withleafvalue if set to true the leaf value will be returned + * @return array which contains two subarray 'timestamp' and 'value' + */ + function getMeta($group, $subgroup = null, $leaf = null, $withleafvalue = false) + { + $ret = array('timestamp' => array(), + 'value' => array()); + + if ($this->_usememorycache) + { + // check if the data exists in the cache + $incache = false; + if ($subgroup == null && + isset($this->_cache[$group]['value'])) + { + $incache = true; + $ret = $this->_cache[$group]; + } + else if ($leaf == null && + isset($this->_cache[$group]['childs'][$subgroup]['value'])) + { + $incache = true; + $ret = $this->_cache[$group]['childs'][$subgroup]; + } + else + { + if ($withleafvalue) + { + if (isset($this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['value'])) + { + $incache = true; + $ret = $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]; + } + } + else + { + if (isset($this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['timestamp'])) + { + $incache = true; + $ret = $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]; + } + } + } + + if ($incache) + { + $ret2 = array(); + if (isset($ret['timestamp'])) $ret2['timestamp'] = $ret['timestamp']; + if (isset($ret['value'])) $ret2['value'] = $ret['value']; + return $ret2; + } + } + + // get the fresh data + $ret = $this->_container->getMeta($group, $subgroup, $leaf, $withleafvalue); + + if ($this->_usememorycache) + { + // store in the cache + if ($subgroup == null) + { + $this->_cache[$group]['value'] = $ret['value']; + $this->_cache[$group]['timestamp'] = $ret['timestamp']; + } + else if ($leaf == null) + { + $this->_cache[$group]['childs'][$subgroup]['value'] = $ret['value']; + $this->_cache[$group]['childs'][$subgroup]['timestamp'] = $ret['timestamp']; + } + else + { + if ($withleafvalue) + $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['value'] = $ret['value']; + else + unset($this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['value']); + $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['timestamp'] = $ret['timestamp']; + } + } + + return $ret; + } + + /** + * Increment a counter identified by the following path : group / subgroup / leaf + * Notice: this step must be atomic in order to avoid multithread problem (don't forget to use locking features) + * @param $group is mandatory + * @param $subgroup is mandatory + * @param $leaf is mandatory, it's the counter name + * @return array which contains two subarray 'timestamp' and 'value' (value contains the incremented numeric value) + */ + function incMeta($group, $subgroup, $leaf) + { + $ret = $this->_container->incMeta($group, $subgroup, $leaf); + + if ($this->_usememorycache) + { + // store the modifications in the cache + if (isset($this->_cache[$group]['value']) && + !in_array($subgroup, $this->_cache[$group]['value'])) + { + $this->_cache[$group]['value'][] = $subgroup; + $this->_cache[$group]['timestamp'][] = time(); + } + if (isset($this->_cache[$group]['childs'][$subgroup]['value']) && + !in_array($leaf, $this->_cache[$group]['childs'][$subgroup]['value'])) + { + $this->_cache[$group]['childs'][$subgroup]['value'][] = $leaf; + $this->_cache[$group]['childs'][$subgroup]['timestamp'][] = time(); + } + $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['value'] = $ret['value']; + $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['timestamp'] = array(time()); + } + + return $ret; + } + + /** + * Remove a meta data or a group of metadata + * @param $group if null then it will remove all the possible groups (all the created metadata) + * @param $subgroup if null then it will remove the $group's childs (all the subgroup contained by $group) + * @param $leaf if null then it will remove all the $subgroup's childs (all the leafs contained by $subgroup) + * @return true on success, false on error + */ + function rmMeta($group, $subgroup = null, $leaf = null) + { + if ($this->_usememorycache) + { + // remove from the cache + if ($group == null) + $this->_cache = array(); + else if ($subgroup == null) + unset($this->_cache[$group]); + else if ($leaf == null) + { + if (isset($this->_cache[$group]['value'])) + { + $i = array_search($subgroup,$this->_cache[$group]['value']); + if ($i !== FALSE) + { + unset($this->_cache[$group]['value'][$i]); + unset($this->_cache[$group]['timestamp'][$i]); + } + } + unset($this->_cache[$group]['childs'][$subgroup]); + } + else + { + if (isset($this->_cache[$group]['childs'][$subgroup]['value'])) + { + $i = array_search($leaf,$this->_cache[$group]['childs'][$subgroup]['value']); + if ($i !== FALSE) + { + unset($this->_cache[$group]['childs'][$subgroup]['value'][$i]); + unset($this->_cache[$group]['childs'][$subgroup]['timestamp'][$i]); + } + } + unset($this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]); + } + } + + return $this->_container->rmMeta($group, $subgroup, $leaf); + } + + /** + * In the default File container: used to encode UTF8 strings to ASCII filenames + * This method can be overridden by the concrete container + */ + function encode($str) + { + return $this->_container->encode($str); + } + + /** + * In the default File container: used to decode ASCII filenames to UTF8 strings + * This method can be overridden by the concrete container + */ + function decode($str) + { + return $this->_container->decode($str); + } +} + +?> diff --git a/instmess/src/pfccontainerinterface.class.php b/instmess/src/pfccontainerinterface.class.php new file mode 100644 index 0000000..40d9b57 --- /dev/null +++ b/instmess/src/pfccontainerinterface.class.php @@ -0,0 +1,95 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/** + * pfcContainerInterface is an interface implemented by pfcContainer and each pfcContainer concrete isntances (File,Mysql...) + * + * @author Stephane Gully + * @abstract + */ +class pfcContainerInterface +{ + function pfcContainerInterface() { } + function getDefaultConfig() { return array(); } + function init(&$c) { return array(); } + + /** + * Write a meta data value identified by a group / subgroup / leaf [with a value] + * As an example in the default file container this arborescent structure is modelised by simple directories + * group1/subgroup1/leaf1 + * /leaf2 + * /subgroup2/... + * Each leaf can contain or not a value. + * However each leaf and each group/subgroup must store the lastmodified time (timestamp). + * @param $group root arborescent element + * @param $subgroup is the root first child which contains leafs + * @param $leaf is the only element which can contain values + * @param $leafvalue NULL means the leaf will not contain any values + * @return 1 if the old leaf has been overwritten, 0 if a new leaf has been created + */ + function setMeta($group, $subgroup, $leaf, $leafvalue = NULL) + { die(_pfc("%s must be implemented", get_class($this)."::".__FUNCTION__)); } + + + /** + * Read meta data identified by a group [/ subgroup [/ leaf]] + * @param $group is mandatory, it's the arborescence's root + * @param $subgroup if null then the subgroup list names are returned + * @param $leaf if null then the leaf names are returned + * @param $withleafvalue if set to true the leaf value will be returned + * @return array which contains two subarray 'timestamp' and 'value' + */ + function getMeta($group, $subgroup = null, $leaf = null, $withleafvalue = false) + { die(_pfc("%s must be implemented", get_class($this)."::".__FUNCTION__)); } + + + /** + * Remove a meta data or a group of metadata + * @param $group if null then it will remove all the possible groups (all the created metadata) + * @param $subgroup if null then it will remove the $group's childs (all the subgroup contained by $group) + * @param $leaf if null then it will remove all the $subgroup's childs (all the leafs contained by $subgroup) + * @return true on success, false on error + */ + function rmMeta($group, $subgroup = null, $leaf = null) + { die(_pfc("%s must be implemented", get_class($this)."::".__FUNCTION__)); } + + + /** + * In the default File container: used to encode UTF8 strings to ASCII filenames + * This method can be overridden by the concrete container + */ + function encode($str) + { + return $str; + } + + /** + * In the default File container: used to decode ASCII filenames to UTF8 strings + * This method can be overridden by the concrete container + */ + function decode($str) + { + return $str; + } +} + +?> \ No newline at end of file diff --git a/instmess/src/pfcglobalconfig.class.php b/instmess/src/pfcglobalconfig.class.php new file mode 100644 index 0000000..1a620ed --- /dev/null +++ b/instmess/src/pfcglobalconfig.class.php @@ -0,0 +1,1167 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once dirname(__FILE__)."/pfctools.php"; +require_once dirname(__FILE__)."/pfci18n.class.php"; +require_once dirname(__FILE__).'/pfccontainer.class.php'; + +/** + * pfcGlobalConfig stock configuration data into sessions and initialize some stuff + * + * @author Stephane Gully + */ +class pfcGlobalConfig +{ + // ------------------ + // public parameters + // ------------------ + + /** + *

This is the only mandatory parameter used to identify the chat server. + * You can compare it to the server ip/host like on an IRC server. + * If you don't know what to write, just try : $params["serverid"] = md5(__FILE__);

+ */ + var $serverid = ''; + + /** + *

Used to translate the chat text and messages. Accepted values are the i18n/ sub directories names. + * (By default, this is the local server language.)

+ */ + var $language = ''; + + /** + *

Set a sepcific encoding for chat labels. + * This is really useful when the Web page embedding the chat is not UTF-8 encoded. + * This parameter should be the same as the chat web page. + * Could be ISO-8859-1 or anything else but it must be supported by iconv php module. + * (Default value: UTF-8)

+ */ + var $output_encoding = 'UTF-8'; + + /** + *

If you have already identified the user (forum, portal...) you can force the user's nickname with this parameter. + * Defining a nick will skip the "Please enter your nickname" popup.

+ *

Warning : Nicknames must be encoded in UTF-8. + * For example, if you get nicks from a databases where they are ISO-8859-1 encoded, + * you must convert it: $params["nick"] = iconv("ISO-8859-1", "UTF-8", $bdd_nickname); + * (Of course, change the $bdd_nickname parameter for your needs.)

+ *

(Default value: "" - means users must choose a nickname when s/he connects.)

+ */ + var $nick = ""; + + /** + *

This is the maximum nickname length, a longer nickname is forbidden. + * (Default value: 15)

+ */ + var $max_nick_len = 15; + + /** + *

Setting this to true will forbid the user to change his/her nickname later. + * (Default value: false)

+ */ + var $frozen_nick = false; + + /** + *

Contains some extra data (metadata) about the user that can be used to customize the display. + * For example: the user's gender, age, real name, etc. can be setup in order to display it in the user's info box. + * A example for gender is : $params["nickmeta"] = array('gender'=>'f'); + * (Default value: empty array)

+ */ + var $nickmeta = array(); + + /** + *

Can be used to set user metadata that is only visible to admins. + * (Default value: array('ip') - means that the user's IP address is shown to admins only)

+ */ + var $nickmeta_private = array('ip'); + + /** + *

Can be used to hide keys in the final displayed whoisbox. + * (Default value: array() - means that nothing is hidden)

+ */ + var $nickmeta_key_to_hide = array(); + + /** + *

Set this parameter to true if you want to give admin rights to the connected user. + * Attention : if you don't use any external registration system, all your users will be admins. + * You have to test current user rights before setting this parameter to true. + * (Default value: false)

+ */ + var $isadmin = false; + + /** + *

This parameter contains a list of key/value that identify admin access. + * The keys are the nicknames and the values are the corresponding passwords. + * Note: The "isadmin" parameter does not depend on this variable. + * (Default value: nick 'admin' with no password is available. Don't forget to change it.)

+ */ + var $admins = array("admin" => ""); + + /** + *

When this parameter is true, it gives admin rights to the first connected user on the server. + * (Default value: false)

+ */ + var $firstisadmin = false; + + /** + *

Used to change the chat title that is visible just above the messages list. + * (Default value: "My Chat")

+ */ + var $title = ''; + + /** + *

Used to create default rooms (auto-joined at startup). It contains an array of rooms names. + * (Default value: one room is created named "My room")

+ */ + var $channels = array(); + + /** + *

This parameter can be used to restrict channels to users. + * If the array is empty, it allows users to create their own channels. + * (Default value: empty array)

+ */ + var $frozen_channels = array(); + + /** + *

The maximum number of allowed channels for each user. + * (Default value: 10)

+ */ + var $max_channels = 10; + + /** + *

This array contains the nicknames list you want to initiate a private message at chat loading. + * Of course, the listed nicknames should be online or it will just be ignored. + * (Default value: empty array)

+ */ + var $privmsg = array(); + + /** + *

This is the maximum number of private message allowed at the same time for one user. + * (Default value: 5)

+ */ + var $max_privmsg = 5; + + /** + *

This is the time to wait between two refreshes. + * A refresh is an HTTP request which asks the server if there are new messages to display. + * If there are no new messages, then an empty HTTP response is returned. + * This parameter will be dynamically changed depending on the chat activity, see refresh_delay_steps + * parameter for more information. + * (Default value: 2000 it means 2000 ms or 2 seconds)

+ */ + var $refresh_delay = 2000; + + /** + *

This parameter is used to control the refresh_delay value dynamically. + * More the chat is active, more the refresh_delay is low, that means more the chat is responsive. + * The first parameter is a refresh delay value, the second is a time inactivity boundary etc ... + * (Default value: array(2000,20000,3000,60000 ... that means: start with 2s delay after 20s of inactivity, + * 3s delay after 60s of inactivity ...)

+ */ + var $refresh_delay_steps = array(2000,20000,3000,30000,5000,60000,8000,300000,15000,600000,30000); + + /** + *

This is the time of inactivity to wait before considering a user is disconnected (in milliseconds). + * A user is inactive only if s/he closed his/her chat window. A user with an open chat window + * is not inactive because s/he sends each refresh_delay an HTTP request. + * (Default value: 35000 it means 35000 ms or 35 seconds)

+ */ + var $timeout = 35000; + + /** + * When this parameter is true, all the chatters will be redirected + * to the url indicated by the lockurl parameter. + * (Default value: false)

+ */ + var $islocked = false; + + /** + * This url is used when islocked parameter is true. + * The users will be redirected (http redirect) to this url. + * (Default value: http://www.phpfreechat.net) + */ + var $lockurl = 'http://www.phpfreechat.net'; + + /** + *

Contains the list of proxies to ingore. + * For example: append 'censor' to the list to disable words censoring. + * The list of system proxies can be found in src/proxies/. + * Attention: 'checktimeout' and 'checknickchange' proxies should not be disabled or the chat will not work anymore. + * (Default value: empty array - no proxies will be skipped)

+ */ + var $skip_proxies = array(); + + /** + *

This array contains the proxies that will be handled just before to process a command + * and just after the system proxies. + * You can use this array to execute your own proxy. + * (Default value: empty array)

+ */ + var $post_proxies = array(); + + /** + *

This array ocntains the proxies that will be handled just before system proxies. + * You can use this array to execute your own proxy. + * (Default value: empty array)

+ */ + var $pre_proxies = array(); + + /** + *

Contains the proxies configuration. + * TODO: explain the possible values for each proxies.

+ */ + var $proxies_cfg = array("auth" => array(), + "noflood" => array("charlimit" => 450, + "msglimit" => 10, + "delay" => 5), + "censor" => array("words" => array("fuck","sex","bitch"), + "replaceby" => "*", + "regex" => false), + "log" => array("path" => "")); + + /** + *

A custom proxies path. Used to easily plugin your own proxy to the chat without modifying the code. + * (Default value: empty path)

+ */ + var $proxies_path = ''; + + /** + *

Contains the default proxies location. + * Do not change this parameter if you don't know what you are doing. + * If you try to add your own proxy, check the proxies_path parameter. + * (Default value: dirname(__FILE__).'/proxies')

+ */ + var $proxies_path_default = ''; + + /** + *

This parameter indicates your own commands directory location. + * The chat uses commands to communicate between client and server. + * As an example, when a message is sent, the /send your message command is used, + * when a nickname is changed, the /nick newnickname command is used. + * To create a new command, you have to write it and indicate in this parameter where it is located. + * (Default value: empty string - means no custom command path is used)

+ */ + var $cmd_path = ''; + + /** + *

Contains the default command path used by the system. + * Do not change this parameter if you don't know what you are doing. + * If you try to add your own command, check the cmd_path parameter. + * (Default value: dirname(__FILE__).'/commands')

+ */ + var $cmd_path_default = ''; + + /** + *

This is the maximum message length in characters. A longer message is forbidden. + * (Default value: 400)

+ */ + var $max_text_len = 400; + + /** + *

This is the number of messages kept in the history. + * This is what you see when you reload the chat. + * The number of messages s/he can see is defined by this parameter. + * (Default value: 20

+ */ + var $max_msg = 20; + + /** + *

The maximum number of lines displayed in the window. + * Old lines will be deleted to save browser's memory on clients. + * Default value: 150)

+ */ + var $max_displayed_lines = 150; + + /** + *

Setting this to true will send a /quit command when the user closes his/her window. + * (NOTE: Doesn't work on Firefox). + * This parameter isn't true by default because on IE and Konqueror/Safari, + * reloading the window (F5) will generate the same event as closing the window which can be annoying. + * (Default value: false)

+ */ + var $quit_on_closedwindow = true; + + /** + *

Setting this to true will give the focus to the input text box when connecting to the chat. + * It can be useful not to touch the focus when integrating the chat into an existing website + * because when the focus is changed, the viewport follows the focus location. + * (Default value: true)

+ */ + var $focus_on_connect = true; + + /** + *

Setting this to false will oblige user to click on the connect button if s/he wants to chat. + * (Default value: true - a connection to the chat is automaticaly performed)

+ */ + var $connect_at_startup = true; + + /** + *

Setting it to true will start the chat minimized. + * (Default value: false)

+ */ + var $start_minimized = false; + + /** + *

Height of the chat area. + * (Default value: "440px")

+ */ + var $height = "440px"; + + /** + *

  • Setting this to 0 will show nothing.
  • + *
  • Setting it to 1 will show nicknames changes.
  • + *
  • Setting it to 2 will show connect/disconnect notifications.
  • + *
  • Setting it to 4 will show kick/ban notifications.
  • + *
  • Setting it to 7 (1+2+4) will show all the notifications.
+ * (Default value: 7)

+ */ + var $shownotice = 7; + + /** + *

Setting it to false will disable nickname colorization. + * (Default value: true)

+ **/ + var $nickmarker = true; + + /** + *

Setting it to false will hide the date/hour column. + * (Default value: true)

+ */ + var $clock = true; + + /** + *

Setting it to false will start the chat without sound notifications. + * (Default value: true)

+ */ + var $startwithsound = true; + + /** + *

Setting it to true will open all links in a new window. + * (Default value: true)

+ */ + var $openlinknewwindow = true; + + /** + *

Setting it to false will disable the window title notification. + * When a message is received and this parameter is true, the window title is modified with [n] + * (n is the number of new posted messages). + * (Default value: true)

+ */ + var $notify_window = true; + + /** + *

Setting it to true will shorten long URLs entered by users in the chat area. + * (Default value: true)

+ */ + var $short_url = true; + + /** + *

Final width of the shortened URL in characters. (This includes the elipsis on shortened URLs.) + * This parameter is taken into account only when short_url is true. + * (Default value: 40)

+ */ + var $short_url_width = 40; + + /** + *

Used to show/hide the ping information near the phpfreechat linkback logo. + * The ping is the time between a client request and a server response. + * More the ping is low, faster the chat is responding. + * (Default value: true)

+ */ + var $display_ping = true; + + /** + *

Used to hide the phpfreechat linkback logo. + * Be sure that you are conform to the license page + * before setting this to false! + * (Default value: true)

+ */ + var $display_pfc_logo = true; + + /** + *

Used to show/hide the images in the channels and pv tabs. + * (Default value: true)

+ */ + var $displaytabimage = true; + + /** + *

Used to show/hide the close button in the channels tabs. + * (Default value: true)

+ */ + var $displaytabclosebutton = true; + + /** + *

Used to show/hide online users list at startup. + * (Default value: true)

+ */ + var $showwhosonline = true; + + /** + *

Used to show/hide the smiley selector at startup. + * (Default value: true)

+ */ + var $showsmileys = true; + + /** + *

Used to show/hide the showwhosonline button. + * (Default value: true)

+ */ + var $btn_sh_whosonline = true; + + /** + *

Used to show/hide the showsmileys button. + * (Default value: true)

+ */ + var $btn_sh_smileys = true; + + /** + *

This is the list of colors that will appears into the bbcode palette. + * (Default value: contains an array of basic colors: '#FFFFFF', '#000000', ...)

+ */ + var $bbcode_colorlist = array('#FFFFFF', + '#000000', + '#000055', + '#008000', + '#FF0000', + '#800000', + '#800080', + '#FF5500', + '#FFFF00', + '#00FF00', + '#008080', + '#00FFFF', + '#0000FF', + '#FF00FF', + '#7F7F7F', + '#D2D2D2'); + + /** + *

This is the list of colors that will be used to automaticaly and randomly colorize the nicknames in the chat. + * (Default value: contains an array of basic colors: '#CCCCCC','#000000')

+ */ + var $nickname_colorlist = array('#CCCCCC', + '#000000', + '#3636B2', + '#2A8C2A', + '#C33B3B', + '#C73232', + '#80267F', + '#66361F', + '#D9A641', + '#3DCC3D', + '#1A5555', + '#2F8C74', + '#4545E6', + '#B037B0', + '#4C4C4C', + '#959595'); + + /** + *

This parameter specifies which theme the chat will use. + * A theme is a package that makes it possible to completly change the chat appearance (CSS) and the chat dynamics (JS) + * You can find official themes in the themes/ directory on your local phpfreechat distribution. + * (Default value: 'default')

+ */ + var $theme = 'default'; + + /** + *

Indicates where the themes are located. + * Use this parameter if you want to store your own theme in a special location. + * (by default the same as theme_default_path)

+ */ + var $theme_path = ''; + + /** + *

This url indicates the theme_path location. + * It will be used by the browser to load theme resources : images, css, js. + * If this parameter is not indicated, the themes will be copied to data_public_path/themes + * and this parameter value will be set to data_public_url/theme. + * (Default value: '')

+ */ + var $theme_url = ''; + + /** + *

Indicate where the official pfc default theme is located. + * Do not change this parameter if you don't know what you are doing. + * If you try to add your own theme, check the theme_path parameter. + * (Default value: '' - empty string means dirname(__FILE__).'/../themes' is used automatically)

+ */ + var $theme_default_path = ''; + + /** + *

This url indicates the theme_default_path location. + * Do not change this parameter if you don't know what you are doing. + * If you try to add your own theme, check the theme_path parameter. + * (Default value: the theme is copied into data_public_path/themes + * and this parameter will be set to data_public_url/theme)

+ */ + var $theme_default_url = ''; + + /** + *

Used to specify the chat container (chat database). + * Accepted containers are : File and Mysql (maybe others in the future). + * (Default value: 'File')

+ */ + var $container_type = 'File'; + + /** + *

Used to specify the script that will handle asynchronous requests. + * Very useful when the chat (client) script is resource consuming (ex: forum or portal chat integration). + * (Default value: '' - means this parameter is automatically calculated)

+ */ + var $server_script_path = ''; + + /** + *

This url indicates the server_script_path. + * It will be used to do AJAX requests from the browser. Therefore, this URL should be a browsable public url. + * This parameter is useful when using URL rewriting because basic auto-calculation will fail. + * (Default value: '' - means this parameter is automatically calculated)

+ */ + var $server_script_url = ''; + + /** + *

Used to specify the script path which first displays the chat. + * This path will be used to calculate relatives paths for resources: javascript lib and images. + * Useful when the php configuration is uncommon. This option can be used to force the automatic detection process. + * (Default value: '' - means this parameter is automatically calculated)

+ */ + var $client_script_path = ''; + + /** + *

Used to store private data like cache, logs and chat history. + * Tip: you can optimize your chat performances, + * see this FAQ entry. + * (Default value: '' - means dirname(__FILE__)."/../data/private" is used automatically)

+ */ + var $data_private_path = ''; + + /** + * This path must be reachable by your web server. + * Javascript and every resources (theme) files will be stored here. + * (Default value: '' - means dirname(__FILE__)."/../data/public" is used automatically) + */ + var $data_public_path = ''; + + /** + * This URL should link to the data_private_path directory so that + * the clients' browsers will be able to load needed javascript files and theme resources. + * It can be useful when url rewriting is done on the server. + * (Default value: '' - means this parameter is automatically calculated from data_private_path) + */ + var $data_public_url = ''; + + /** + *

This is the prototype javascript library URL. + * Use this parameter to use your external library. + * (Default value: '' - means data/js/prototype.js is used automatically)

+ */ + var $prototypejs_url = ''; + + /** + *

When debug is true, some traces will be shown on the chat clients + * (Default value: false)

+ */ + var $debug = false; + + /** + *

Can be used to setup the chat time zone. + * It is the difference in seconds between chat clock and server clock. + * (Default value: 0)

+ */ + var $time_offset = 0; + + /** + *

How to display the dates in the chat. + * (Default value: 'd/m/Y')

+ */ + var $date_format = 'd/m/Y'; + + /** + *

How to display the time in the chat + * (Default value: 'H:i:s')

+ */ + var $time_format = 'H:i:s'; + + /** + *

This parameter is useful when your chat server is behind a reverse proxy that + * forwards client ip address in HTTP_X_FORWARDED_FOR http header. + * Some discutions about this parameter are available + * on the forum. + * (Default value: false)

+ */ + var $get_ip_from_xforwardedfor = false; + + /** + *

Most of the chat parameters are stored in a internal cache for performances issues. + * It means that for all the clients the chat will have the same parameters. However sometime you need + * to customize some parameters for each clients. + * For example: the 'language' parameter could depends on the chatter profil so it could interesting to + * ignore the cache for this parameter. + * The 'dyn_params' contains the parameters that need to be dynamic (not stored in the cache). + * (Default value: array())

+ */ + var $dyn_params = array(); + + // ------------------ + // private parameters + // ------------------ + /** + * Contains proxies to execute on each commands. + * Filled in the init step, this parameter cannot be overridden. + */ + var $proxies = array(); + + var $smileys = array(); + var $errors = array(); + var $is_init = false; // used internaly to know if the chat config is initialized + var $version = ''; // the phpfreechat version: taken from the 'version' file content + + var $_sys_proxies = array("lock", "checktimeout", "checknickchange", "auth", "noflood", "censor", "log"); + var $_dyn_params = array("nick","isadmin","islocked","admins","frozen_channels", "channels", "privmsg", "nickmeta","time_offset","date_format","time_format"); + var $_params_type = array(); + var $_query_string = ''; + + function pfcGlobalConfig( $params = array() ) + { + // @todo find a cleaner way to forward serverid to i18n functions + $GLOBALS['serverid'] = isset($params['serverid']) ? $params['serverid'] : '_serverid_'; + // setup the locales for the translated messages + pfcI18N::Init(isset($params['language']) ? $params['language'] : ''); + + // check the serverid is really defined + if (!isset($params["serverid"])) + $this->errors[] = _pfc("'%s' parameter is mandatory by default use '%s' value", "serverid", "md5(__FILE__)"); + $this->serverid = $params["serverid"]; + + // setup data_private_path because _GetCacheFile needs it + if (!isset($params["data_private_path"])) + $this->data_private_path = dirname(__FILE__)."/../data/private"; + else + $this->data_private_path = $params["data_private_path"]; + + // check if a cached configuration already exists + // don't load parameters if the cache exists + $cachefile = $this->_GetCacheFile(); + if (!file_exists($cachefile)) + { + // first of all, save our current state in order to be able to check for variable types later + $this->_saveParamsTypes(); + + if (!isset($params["data_public_path"])) + $this->data_public_path = dirname(__FILE__)."/../data/public"; + else + $this->data_public_path = $params["data_public_path"]; + + // if the user didn't specify the server_script_url, then remember it and + // append QUERY_STRING to it + if (!isset($params['server_script_url'])) + $this->_query_string = isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] != '' ? + '?'.$_SERVER['QUERY_STRING'] : + ''; + + // load users container or keep default one + if (isset($params["container_type"])) + $this->container_type = $params["container_type"]; + + // load default container's config + $ct =& pfcContainer::Instance($this->container_type, true); + $ct_cfg = $ct->getDefaultConfig(); + foreach( $ct_cfg as $k => $v ) + { + $attr = "container_cfg_".$k; + if (!isset($this->$attr)) + $this->$attr = $v; + } + + // load all user's parameters which will override default ones + foreach ( $params as $k => $v ) + { + if (!isset($this->$k)) + $this->errors[] = _pfc("Error: undefined or obsolete parameter '%s', please correct or remove this parameter", $k); + if (preg_match('/^_/',$k)) + $this->errors[] = _pfc("Error: '%s' is a private parameter, you are not allowed to change it", $k); + + if ($k == "proxies_cfg") + { + // don't replace all the proxy_cfg parameters, just replace the specified ones + foreach ( $params["proxies_cfg"] as $k2 => $v2 ) + { + if (is_array($v2)) + foreach( $v2 as $k3 => $v3) + $this->proxies_cfg[$k2][$k3] = $v3; + else + $this->proxies_cfg[$k2] = $v2; + } + } + else + $this->$k = $v; + } + } + + // load dynamic parameter even if the config exists in the cache + if (isset($params['dyn_params']) && is_array($params['dyn_params'])) + $this->_dyn_params = array_merge($this->_dyn_params,$params['dyn_params']); + foreach ( $this->_dyn_params as $dp ) + if (isset($params[$dp])) + $this->$dp = $params[$dp]; + + // 'channels' is now a dynamic parameter, just check if I need to initialize it or not + if (is_array($this->channels) && + count($this->channels) == 0 && + !isset($params['channels'])) + $this->channels = array(_pfc("My room")); + + // now load or save the configuration in the cache + $this->synchronizeWithCache(); + + // to be sure the container instance is initialized + $ct =& pfcContainer::Instance($this->container_type, true); + + // This is a dirty workaround which fix a infinite loop when: + // 'frozen_nick' is true + // 'nick' length is > 'max_nick_len' + $this->nick = $this->filterNickname($this->nick); + } + + function &Instance( $params = array(), $destroy_instance = false ) + { + static $i; + if ($destroy_instance) + $i = NULL; + else + if (!isset($i)) + $i = new pfcGlobalConfig( $params ); + return $i; + } + + /** + * This function saves all the parameters types in order to check later if the types are ok + */ + function _saveParamsTypes() + { + $vars = get_object_vars($this); + foreach($vars as $k => $v) + { + if (is_string($v)) $this->_params_type["string"][] = $k; + else if (is_bool($v)) $this->_params_type["bool"][] = $k; + else if (is_array($v)) $this->_params_type["array"][] = $k; + else if (is_int($v) && $v>0) $this->_params_type["positivenumeric"][] = $k; + else $this->_params_type["misc"][] = $k; + } + } + + /** + * Initialize the phpfreechat configuration + * this initialisation is done once at startup then it is stored into a session cache + */ + function init() + { + $ok = true; + + // check the parameters types + $array_params = $this->_params_type["array"]; + foreach( $array_params as $ap ) + { + if (!is_array($this->$ap)) + $this->errors[] = _pfc("'%s' parameter must be an array", $ap); + } + $numerical_positive_params = $this->_params_type["positivenumeric"]; + foreach( $numerical_positive_params as $npp ) + { + if (!is_int($this->$npp) || $this->$npp < 0) + $this->errors[] = _pfc("'%s' parameter must be a positive number", $npp); + } + $boolean_params = $this->_params_type["bool"]; + foreach( $boolean_params as $bp ) + { + if (!is_bool($this->$bp)) + $this->errors[] = _pfc("'%s' parameter must be a boolean", $bp); + } + $string_params = $this->_params_type["string"]; + foreach( $string_params as $sp ) + { + if (!is_string($this->$sp)) + $this->errors[] = _pfc("'%s' parameter must be a charatere string", $sp); + } + + if ($this->title == "") $this->title = _pfc("My Chat"); + + // first of all, check the used functions + $f_list["file_get_contents"] = _pfc("You need %s", "PHP 4 >= 4.3.0 or PHP 5"); + $err_session_x = "You need PHP 4 or PHP 5"; + $f_list["session_start"] = $err_session_x; + $f_list["session_destroy"] = $err_session_x; + $f_list["session_id"] = $err_session_x; + $f_list["session_name"] = $err_session_x; + $err_preg_x = _pfc("You need %s", "PHP 3 >= 3.0.9 or PHP 4 or PHP 5"); + $f_list["preg_match"] = $err_preg_x; + $f_list["preg_replace"] = $err_preg_x; + $f_list["preg_split"] = $err_preg_x; + $err_ob_x = _pfc("You need %s", "PHP 4 or PHP 5"); + $f_list["ob_start"] = $err_ob_x; + $f_list["ob_get_contents"] = $err_ob_x; + $f_list["ob_end_clean"] = $err_ob_x; + $f_list["get_object_vars"] = _pfc("You need %s", "PHP 4 or PHP 5"); + $this->errors = array_merge($this->errors, check_functions_exist($f_list)); + + // $this->errors = array_merge($this->errors, @test_writable_dir($this->data_public_path, "data_public_path")); + $this->errors = array_merge($this->errors, @test_writable_dir($this->data_private_path, "data_private_path")); + $this->errors = array_merge($this->errors, @test_writable_dir($this->data_private_path."/cache", "data_private_path/cache")); + + + // install the public directory content + $dir = dirname(__FILE__)."/../data/public/js"; + $dh = opendir($dir); + while (false !== ($file = readdir($dh))) + { + $f_src = $dir.'/'.$file; + $f_dst = $this->data_public_path.'/js/'.$file; + if ($file == "." || $file == ".." || !is_file($f_src)) continue; // skip . and .. generic files + // install js files only if the destination doesn't exists or if the destination timestamp is older than the source timestamp + if (!file_exists($f_dst) || filemtime($f_dst) < filemtime($f_src) ) + { + mkdir_r($this->data_public_path.'/js/'); + copy( $f_src, $f_dst ); + } + if (!file_exists($f_dst)) $this->errors[] = _pfc("%s doesn't exist, data_public_path cannot be installed", $f_dst); + } + closedir($dh); + + + // --- + // test client script + // try to find the path into server configuration + if ($this->client_script_path == '') + $this->client_script_path = pfc_GetScriptFilename(); + + if ($this->server_script_url == '' && $this->server_script_path == '') + { + $filetotest = $this->client_script_path; + // do not take into account the url parameters + if (preg_match("/(.*)\?(.*)/", $filetotest, $res)) + $filetotest = $res[1]; + if ( !file_exists($filetotest) ) + $this->errors[] = _pfc("%s doesn't exist", $filetotest); + $this->server_script_url = './'.basename($filetotest).$this->_query_string; + } + + // calculate datapublic url + if ($this->data_public_url == "") + $this->data_public_url = pfc_RelativePath($this->client_script_path, $this->data_public_path); + + if ($this->server_script_path == '') + $this->server_script_path = $this->client_script_path; + + // --- + // test server script + if ($this->server_script_url == '') + { + $filetotest = $this->server_script_path; + // do not take into account the url parameters + if (preg_match("/(.*)\?(.*)/",$this->server_script_path, $res)) + $filetotest = $res[1]; + if ( !file_exists($filetotest) ) + $this->errors[] = _pfc("%s doesn't exist", $filetotest); + $this->server_script_url = pfc_RelativePath($this->client_script_path, $this->server_script_path).'/'.basename($filetotest).$this->_query_string; + } + + // check if the theme_path parameter are correctly setup + if ($this->theme_default_path == '' || !is_dir($this->theme_default_path)) + $this->theme_default_path = dirname(__FILE__).'/../themes'; + if ($this->theme_path == '' || !is_dir($this->theme_path)) + $this->theme_path = $this->theme_default_path; + + // If the user didn't give any theme_default_url value, + // copy the default theme resources in a public directory + if ($this->theme_default_url == '') + { + mkdir_r($this->data_public_path.'/themes/default'); + if (!is_dir($this->data_public_path.'/themes/default')) + $this->errors[] = _pfc("cannot create %s", $this->data_public_path.'/themes/default'); + else + { + $ret = copy_r( dirname(__FILE__).'/../themes/default', + $this->data_public_path.'/themes/default' ); + if (!$ret) + $this->errors[] = _pfc("cannot copy %s in %s", + dirname(__FILE__).'/../themes/default', + $this->data_public_path.'/themes/default'); + } + $this->theme_default_url = $this->data_public_url.'/themes'; + } + if ($this->theme_url == '') + { + mkdir_r($this->data_public_path.'/themes/'.$this->theme); + if (!is_dir($this->data_public_path.'/themes/'.$this->theme)) + $this->errors[] = _pfc("cannot create %s", $this->data_public_path.'/themes/'.$this->theme); + else + { + $ret = copy_r( $this->theme_path.'/'.$this->theme, + $this->data_public_path.'/themes/'.$this->theme ); + if (!$ret) + $this->errors[] = _pfc("cannot copy %s in %s", + $this->theme_path.'/'.$this->theme, + $this->data_public_path.'/themes/'.$this->theme); + } + $this->theme_url = $this->data_public_url.'/themes'; + } + + // if the user do not have an existing prototype.js library, we use the embeded one + if ($this->prototypejs_url == '') $this->prototypejs_url = $this->data_public_url.'/js/prototype.js'; + + // --- + // run specific container initialisation + $ct =& pfcContainer::Instance(); + $ct_errors = $ct->init($this); + $this->errors = array_merge($this->errors, $ct_errors); + + // check if the wanted language is known + $lg_list = pfcI18N::GetAcceptedLanguage(); + if ( $this->language != "" && !in_array($this->language, $lg_list) ) + $this->errors[] = _pfc("'%s' parameter is not valid. Available values are : '%s'", "language", implode(", ", $lg_list)); + + // calculate the proxies chaine + $this->proxies = array(); + foreach($this->pre_proxies as $px) + { + if (!in_array($px,$this->skip_proxies) && !in_array($px,$this->proxies)) + $this->proxies[] = $px; + + } + foreach($this->_sys_proxies as $px) + { + if (!in_array($px,$this->skip_proxies) && !in_array($px,$this->proxies)) + $this->proxies[] = $px; + + } + foreach($this->post_proxies as $px) + { + if (!in_array($px,$this->skip_proxies) && !in_array($px,$this->proxies)) + $this->proxies[] = $px; + + } + + if (in_array('log',$this->proxies)) { + // test the LOCK_EX feature because the log proxy needs to write in a file + $filename = $this->data_private_path.'/filemtime2.test'; + if (is_writable(dirname($filename))) + { + $data1 = time(); + file_put_contents($filename, $data1, LOCK_EX); + $data2 = file_get_contents($filename); + if ($data1 != $data2) { + unset($this->proxies[array_search('log',$this->proxies)]); + } + } + } + + // save the proxies path + $this->proxies_path_default = dirname(__FILE__).'/proxies'; + // check the customized proxies path + if ($this->proxies_path != '' && !is_dir($this->proxies_path)) + $this->errors[] = _pfc("'%s' directory doesn't exist", $this->proxies_path); + if ($this->proxies_path == '') $this->proxies_path = $this->proxies_path_default; + + // save the commands path + $this->cmd_path_default = dirname(__FILE__).'/commands'; + if ($this->cmd_path == '') $this->cmd_path = $this->cmd_path_default; + + // load smileys from file + $this->loadSmileyTheme(); + + // load version number from file + $this->version = trim(file_get_contents(dirname(__FILE__)."/../version.txt")); + + $this->is_init = (count($this->errors) == 0); + } + + function isInit() + { + return $this->is_init; + } + + function &getErrors() + { + return $this->errors; + } + + function loadSmileyTheme() + { + $theme = file($this->getFilePathFromTheme("smileys/theme.txt")); + $result = array(); + foreach($theme as $line) + { + $line = trim($line); + if (preg_match("/^#.*/",$line)) + continue; + else if (preg_match("/([a-z_\-0-9\.]+)(.*)$/i",$line,$res)) + { + $smiley_file = 'smileys/'.$res[1]; + $smiley_str = trim($res[2])."\n"; + $smiley_str = str_replace("\n", "", $smiley_str); + $smiley_str = str_replace("\t", " ", $smiley_str); + $smiley_str_tab = explode(" ", $smiley_str); + foreach($smiley_str_tab as $str) + $result[$smiley_file][] = htmlspecialchars(addslashes($str)); + } + } + $this->smileys =& $result; + } + + function getId() + { + return $this->serverid; + } + + function _GetCacheFile($serverid = "", $data_private_path = "") + { + if ($serverid == '') $serverid = $this->getId(); + if ($data_private_path == '') $data_private_path = $this->data_private_path; + return $data_private_path.'/cache/'.$serverid.'.php'; + } + + function destroyCache() + { + $cachefile = $this->_GetCacheFile(); + if (!file_exists($cachefile)) + return false; + $this->is_init = false; + // destroy the cache lock file + $cachefile_lock = $cachefile."_lock"; + if (file_exists($cachefile_lock)) @unlink($cachefile_lock); + // destroy the cache file + return @unlink($cachefile); + } + + /** + * Save the pfcConfig object into cache if it doesn't exists yet + * else restore the old pfcConfig object + */ + function synchronizeWithCache() + { + $cachefile = $this->_GetCacheFile(); + $cachefile_lock = $cachefile."_lock"; + + if (file_exists($cachefile)) + { + // if a cache file exists, remove the lock file because config has been succesfully stored + if (file_exists($cachefile_lock)) @unlink($cachefile_lock); + + include $cachefile; + foreach($pfc_conf as $key => $val) + // the dynamics parameters must not be cached + if (!in_array($key,$this->_dyn_params)) + $this->$key = $val; + + return true; // synchronized + } + else + { + if (file_exists($cachefile_lock)) + { + // delete too old lockfiles (more than 15 seconds) + $locktime = filemtime($cachefile_lock); + if ($locktime+15 < time()) + unlink($cachefile_lock); + else + return false; // do nothing if the lock file exists + } + else + @touch($cachefile_lock); // create the lockfile + + if (!$this->isInit()) + $this->init(); + $errors =& $this->getErrors(); + if (count($errors) == 0) + { + // save the validated config in cache + $this->saveInCache(); + } + else + @unlink($cachefile_lock); // destroy the lock file for the next attempt + return false; // new cache created + } + } + function saveInCache() + { + $cachefile = $this->_GetCacheFile(); + $data = '_dyn_params as $k) + unset($conf[$k]); + + $data .= '$pfc_conf = '.var_export($conf,true).";\n"; + $data .= '?>'; + + file_put_contents($cachefile, $data/*serialize(get_object_vars($this))*/); + } + + function isDefaultFile($file) + { + $fexists1 = file_exists($this->theme_path."/default/".$file); + $fexists2 = file_exists($this->theme_path."/".$this->theme."/".$file); + return ($this->theme == "default" ? $fexists1 : !$fexists2); + } + + function getFilePathFromTheme($file) + { + if (file_exists($this->theme_path."/".$this->theme."/".$file)) + return $this->theme_path."/".$this->theme."/".$file; + else + if (file_exists($this->theme_default_path."/default/".$file)) + return $this->theme_default_path."/default/".$file; + else + { + $this->destroyCache(); + die(_pfc("Error: '%s' could not be found, please check your themepath '%s' and your theme '%s' are correct", $file, $this->theme_path, $this->theme)); + } + } + + function getFileUrlFromTheme($file) + { + if (file_exists($this->theme_path.'/'.$this->theme.'/'.$file)) + return $this->theme_url.'/'.$this->theme.'/'.$file; + else + if (file_exists($this->theme_default_path.'/default/'.$file)) + return $this->theme_default_url.'/default/'.$file; + else + return 'notfound'; + } + + + function filterNickname($nickname) + { + $nickname = trim($nickname); + require_once dirname(__FILE__)."/../lib/utf8/utf8_substr.php"; + $nickname = (string)utf8_substr($nickname, 0, $this->max_nick_len); + return $nickname; + } +} + +?> diff --git a/instmess/src/pfci18n.class.php b/instmess/src/pfci18n.class.php new file mode 100644 index 0000000..cd0a45b --- /dev/null +++ b/instmess/src/pfci18n.class.php @@ -0,0 +1,195 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once(dirname(__FILE__)."/pfctools.php"); + +function _pfc() +{ + $args = func_get_args(); + $serverid = isset($GLOBALS['serverid']) ? $GLOBALS['serverid'] : 0; // serverid is used to avoid conflicts with external code using same 'i18n' key + $args[0] = isset($GLOBALS[$serverid]["i18n"][$args[0]]) && $GLOBALS[$serverid]["i18n"][$args[0]] != "" ? + ($GLOBALS["output_encoding"] == "UTF-8" ? + $GLOBALS[$serverid]["i18n"][$args[0]] : + iconv("UTF-8", $GLOBALS["output_encoding"], $GLOBALS[$serverid]["i18n"][$args[0]])) : + "_".$args[0]."_"; + return call_user_func_array('sprintf', $args); +} +/** + * Just like _pfc but just return the raw translated string, keeping the %s into it + * (used by the javascript resources (i18n) class) + */ +function _pfc2() +{ + $args = func_get_args(); + $serverid = isset($GLOBALS['serverid']) ? $GLOBALS['serverid'] : 0; // serverid is used to avoid conflicts with external code using same 'i18n' key + $args[0] = isset($GLOBALS[$serverid]["i18n"][$args[0]]) && $GLOBALS[$serverid]["i18n"][$args[0]] != "" ? + ($GLOBALS["output_encoding"] == "UTF-8" ? + $GLOBALS[$serverid]["i18n"][$args[0]] : + iconv("UTF-8", $GLOBALS["output_encoding"], $GLOBALS[$serverid]["i18n"][$args[0]])) : + "_".$args[0]."_"; + return $args[0]; +} + +class pfcI18N +{ + function Init($language,$type="main") + { + if ($type=="admin") + if (!in_array($language, pfcI18N::GetAcceptedLanguage("admin"))) + $language = pfcI18N::GetDefaultLanguage(); + if (!in_array($language, pfcI18N::GetAcceptedLanguage())) + $language = pfcI18N::GetDefaultLanguage(); + + if ($type=="admin") + require_once(dirname(__FILE__)."/../i18n/".$language."/admin.php"); + else + require_once(dirname(__FILE__)."/../i18n/".$language."/main.php"); + + $serverid = isset($GLOBALS['serverid']) ? $GLOBALS['serverid'] : 0; // serverid is used to avoid conflicts with external code using same 'i18n' key + $GLOBALS[$serverid]['i18n'] = $GLOBALS['i18n']; // do not pass by reference because $GLOBALS['i18n'] is maybe used by unknown external code + + $GLOBALS["output_encoding"] = "UTF-8"; // by default client/server communication is utf8 encoded + } + + /** + * Switch output encoding in order to write the right characteres in the web page + */ + function SwitchOutputEncoding($oe = "") + { + if ($oe == "") + { + $GLOBALS["output_encoding"] = $GLOBALS["old_output_encoding"]; + unset($GLOBALS["old_output_encoding"]); + } + else + { + if (isset($GLOBALS["old_output_encoding"])) + die("old_output_encoding must be empty (".$GLOBALS["old_output_encoding"].")"); + $GLOBALS["old_output_encoding"] = $GLOBALS["output_encoding"]; + $GLOBALS["output_encoding"] = $oe; + } + } + + /** + * Return the default language : "en" + */ + function GetDefaultLanguage() + { + return "en_US"; + } + + /** + * Return the language list supported bye i18n system + * (content of the i18n directory) + */ + function GetAcceptedLanguage($type="main") + { + return /**/array('nl_NL','ko_KR','nl_BE','tr_TR','pt_PT','en_US','eo','hr_HR','vi_VN','es_ES','zh_TW','nn_NO','ru_RU','id_ID','hu_HU','th_TH','hy_AM','oc_FR','da_DK','de_DE-formal','uk_RO','nb_NO','fr_FR','it_IT','sv_SE','uk_UA','sr_CS','ar_LB','bg_BG','pt_BR','ba_BA','bn_BD','el_GR','zh_CN','gl_ES','pl_PL','de_DE-informal','ja_JP');/**/ + } + + /** + * Parse the source-code and update the i18n ressources files + */ + function UpdateMessageRessources() + { + // first of all, update the GetAcceptedLanguage list + $i18n_basepath = dirname(__FILE__).'/../i18n'; + $i18n_accepted_lang = array(); + $dh = opendir($i18n_basepath); + while (false !== ($file = readdir($dh))) + { + // skip . and .. generic files, skip also .svn directory + if ($file == "." || $file == ".." || strpos($file,".")===0) continue; + if (file_exists($i18n_basepath.'/'.$file.'/main.php')) $i18n_accepted_lang[] = $file; + } + closedir($dh); + $i18n_accepted_lang_str = "array('" . implode("','", $i18n_accepted_lang) . "');"; + $data = file_get_contents_flock(__FILE__); + $data = preg_replace("/(\/\*\*\/)(.*)(\/\*<\/GetAcceptedLanguage>\*\/)/", + "$1".$i18n_accepted_lang_str."$3", + $data); + file_put_contents(__FILE__, $data, LOCK_EX); + + // Now scan the source code in order to find "_pfc" patterns + $files = array(); + $files = array_merge($files, glob(dirname(__FILE__)."/*.php")); + $files = array_merge($files, glob(dirname(__FILE__)."/commands/*.php")); + $files = array_merge($files, glob(dirname(__FILE__)."/containers/*.php")); + $files = array_merge($files, glob(dirname(__FILE__)."/proxies/*.php")); + $files = array_merge($files, glob(dirname(__FILE__)."/client/*.php")); + $files = array_merge($files, glob(dirname(__FILE__)."/../themes/default/*.php")); + $res = array(); + foreach ( $files as $src_filename ) + { + $lines = file($src_filename); + $line_nb = 1; + foreach( $lines as $l) + { + // the labels server side + if( preg_match_all('/_pfc\("([^\"]+)"/', $l, $matches) ) + { + foreach($matches[1] as $label) + { + echo "line: ".$line_nb."\t- ".$label."\n"; + $res[$label] = "// line ".$line_nb." in ".basename($src_filename); + } + } + // the labels client side (JS) + if( preg_match_all('/"([^"]*)",\s\/\/\s_pfc/', $l, $matches) ) + { + echo "line: ".$line_nb."\t- ".$matches[1][0]."\n"; + $res[$matches[1][0]] = "// line ".$line_nb." in ".basename($src_filename); + } + $line_nb++; + } + } + + $dst_filenames = array(); + foreach($i18n_accepted_lang as $lg) + $dst_filenames[] = dirname(__FILE__)."/../i18n/".$lg."/main.php"; + + foreach( $dst_filenames as $dst_filename ) + { + // filter lines to keep, line to add + $old_content = file_get_contents_flock($dst_filename); + // remove php tags to keep only real content + $old_content = preg_replace("/^\<\?php/", "", $old_content); + $old_content = preg_replace("/\?\>$/", "", $old_content); + + // save into the file + $new_content = ""; + foreach($res as $str => $com) + { + //echo "com=".$com."\n"; + //echo "str=".$str."\n"; + if (preg_match("/".preg_quote($str,'/')."/", $old_content) == 0) + $new_content .= $com."\n\$GLOBALS[\"i18n\"][\"".$str."\"] = \"\";\n\n"; + } + $content = ""; + //echo $content; + + file_put_contents($dst_filename, $content, LOCK_EX); + } + } +} + +?> diff --git a/instmess/src/pfcinfo.class.php b/instmess/src/pfcinfo.class.php new file mode 100644 index 0000000..76bc5d0 --- /dev/null +++ b/instmess/src/pfcinfo.class.php @@ -0,0 +1,112 @@ +errors[] = _pfc("Error: the cached config file doesn't exists"); + return; + } + // then intitialize the pfcglobalconfig + $params = array(); + $params["serverid"] = $serverid; + $params["data_private_path"] = $data_private_path; + $this->c =& pfcGlobalConfig::Instance($params); + } + + function free() + { + // free the pfcglobalconfig instance + pfcGlobalConfig::Instance(array(), true); + } + + /** + * @return array(string) a list of errors + */ + function getErrors() + { + if ($this->c != null) + return array_merge($this->errors, $this->c->getErrors()); + else + return $this->errors; + } + function hasErrors() + { + return count($this->getErrors()) > 0; + } + + /** + * @param $channel the returned list is the list of nicknames present on the given channel (NULL for the whole server) + * @param $timeout is the time to wait before a nickname is considered offline + * @return array(string) a list of online nicknames + */ + function getOnlineNick($channel = NULL, $timeout = 20) + { + if ($this->hasErrors()) return array(); + + $ct =& pfcContainer::Instance(); + + if ($channel != NULL) $channel = pfcCommand_join::GetRecipient($channel); + + $res = $ct->getOnlineNick($channel); + $users = array(); + if (isset($res["nickid"])) + { + for($i = 0; $i < count($res["nickid"]); $i++) + { + if (time()-$timeout < $res["timestamp"][$i]) + $users[] = $ct->getNickname($res["nickid"][$i]); + } + } + return $users; + } + + /** + * Return the last $nb message from the $channel room. + * The messages format is very basic, it's raw data (it will certainly change in future) + */ + function getLastMsg($channel, $nb) + { + if ($this->hasErrors()) return array(); + + // to be sure the $nb params is a positive number + if ( !( $nb >= 0 ) ) $nb = 10; + + // to get the channel recipient name + // @todo must use another function to get a private message last messages + $channel = pfcCommand_join::GetRecipient($channel); + + $ct =& pfcContainer::Instance(); + $lastmsg_id = $ct->getLastId($channel); + $lastmsg_raw = $ct->read($channel, $lastmsg_id-$nb); + return $lastmsg_raw; + } + + /** + * Rehash the server config (same as /rehash command) + * Usefull to take into account new server's parameters + */ + function rehash() + { + if ($this->hasErrors()) return false; + + $destroyed = $this->c->destroyCache(); + return $destroyed; + } +} + +?> \ No newline at end of file diff --git a/instmess/src/pfcjson.class.php b/instmess/src/pfcjson.class.php new file mode 100644 index 0000000..14c1055 --- /dev/null +++ b/instmess/src/pfcjson.class.php @@ -0,0 +1,54 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +class pfcJSON +{ + var $json = null; + + function pfcJSON() + { + // if the php5-json module is not available, use a software json implementation + if (!function_exists('json_encode')) { + if (!class_exists('Services_JSON')) + require_once(dirname(__FILE__)."/../lib/json/JSON.php"); + $this->json = new Services_JSON(); + } + } + + function encode($v) + { + if ($this->json) + return $this->json->encode($v); + else + return json_encode($v); + } + + function decode($v) + { + if ($this->json) + return $this->json->decode($v); + else + return json_decode($v); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/pfcproxycommand.class.php b/instmess/src/pfcproxycommand.class.php new file mode 100644 index 0000000..59075e8 --- /dev/null +++ b/instmess/src/pfcproxycommand.class.php @@ -0,0 +1,57 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +require_once dirname(__FILE__)."/pfci18n.class.php"; +require_once dirname(__FILE__)."/pfcuserconfig.class.php"; +require_once dirname(__FILE__)."/pfccommand.class.php"; + +/** + * pfcProxyCommand is an abstract class (interface) which must be inherited by each concrete proxy commands + * + * @author Stephane Gully + */ +class pfcProxyCommand extends pfcCommand +{ + /** + * Next proxy command + */ + var $next = null; + + /** + * The proxy name (similare to the command name) + */ + var $proxyname = ''; + + /** + * Constructor + */ + function pfcProxyCommand() + { + pfcCommand::pfcCommand(); + } + + function linkTo(&$cmd) + { + $this->next = $cmd; + } +} + +?> \ No newline at end of file diff --git a/instmess/src/pfcresponse.class.php b/instmess/src/pfcresponse.class.php new file mode 100644 index 0000000..7a46ca4 --- /dev/null +++ b/instmess/src/pfcresponse.class.php @@ -0,0 +1,69 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/** + * pfcResponse is used to build each ajax response + * Commands stack contains characteres strings with javascript instructions. + */ +class pfcResponse +{ + var $_commands = array(); + + function pfcResponse() + { + } + + function remove($id) + { + $this->_commands[] = '$(\''.$id.'\').remove();'; + } + + function update($id,$data) + { + $data = preg_replace("/'/","\'",$data); + $data = preg_replace("/[\n\r]/","",$data); + $data = preg_replace("/\s*\s*/","> ",$data); + $this->_commands[] = '$(\''.$id.'\').update(\''.$data.'\');'; + } + + function script($js) + { + $this->_commands[] = $js; + } + + function redirect($url) + { + $this->script('window.location = "'.$url.'";'); + } + + function getCommandCount() + { + return count($this->_commands); + } + + function getOutput() + { + return implode("\n",$this->_commands); + } +} +?> \ No newline at end of file diff --git a/instmess/src/pfctemplate.class.php b/instmess/src/pfctemplate.class.php new file mode 100644 index 0000000..e20d86f --- /dev/null +++ b/instmess/src/pfctemplate.class.php @@ -0,0 +1,68 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once dirname(__FILE__)."/pfci18n.class.php"; + +/** + * pfcTemplate is used to display chat templates (html and javascript) + * @author Stephane Gully + */ +class pfcTemplate +{ + var $tpl_filename; + var $vars; + + function pfcTemplate($tpl_filename = "") + { + $this->tpl_filename = $tpl_filename; + } + + function setTemplate($tpl_filename) + { + $this->tpl_filename = $tpl_filename; + } + + function getOutput() + { + ob_start(); + if (!file_exists($this->tpl_filename)) + die(_pfc("%s template could not be found", $this->tpl_filename)); + // assign defined vars to this template + foreach( $this->vars as $v_name => $v_val ) + $$v_name = $v_val; + // execute the template + include($this->tpl_filename); + $result = ob_get_contents(); + ob_end_clean(); + return $result; + } + + function assignObject(&$obj, $name = "c") + { + $vars = get_object_vars($obj); + foreach( $vars as $v_name => $v_val ) + $this->vars[$v_name] = $v_val; + $this->vars[$name] =& $obj; // assigne also the whole object + } +} + +?> \ No newline at end of file diff --git a/instmess/src/pfctools.php b/instmess/src/pfctools.php new file mode 100644 index 0000000..c42d63f --- /dev/null +++ b/instmess/src/pfctools.php @@ -0,0 +1,515 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +/** + * this file contains a toolbox with misc. usefull functions + * + * @author Stephane Gully + */ + +// To be sure the directory separator is defined +// I don't know if this constant can be undefined or not so maybe this code is not necessary +if (!defined("DIRECTORY_SEPARATOR")) + define("DIRECTORY_SEPARATOR", "/"); + + +/** + * Returns the absolute script filename + * takes care of php cgi configuration which do not support SCRIPT_FILENAME variable. + */ +function pfc_GetScriptFilename() +{ + $sf = ''; + if(function_exists('debug_backtrace')) + { + // browse the backtrace history and take the first unknown filename as the client script + foreach(debug_backtrace() as $db) + { + $f = $db['file']; + if (!preg_match('/phpfreechat.class.php/',$f) && + !preg_match('/pfcglobalconfig.class.php/',$f) && + !preg_match('/pfctools.class.php/',$f) && + !preg_match('/pfcinfo.class.php/',$f) + ) + { + $sf = $f; + break; + } + } + } + else if (isset($_SERVER['PATH_TRANSLATED']) && + file_exists($_SERVER['SCRIPT_FILENAME'])) // check for a cgi configurations + { + $sf = $_SERVER['PATH_TRANSLATED']; + } + else if (isset($_SERVER['SCRIPT_FILENAME'])&& + file_exists($_SERVER['SCRIPT_FILENAME'])) // for non-cgi configurations + { + $sf = $_SERVER['SCRIPT_FILENAME']; + } + else + { + echo "
";
+    echo "Error: pfc_GetScriptFilename function returns a wrong path. Please contact the pfc team (contact@phpfreechat.net) and copy/paste these data to help debugging:\n";
+    print_r($_SERVER);
+    print_r(debug_backtrace());
+    echo "
"; + exit; + } + return $sf; +} + +function pfc_RelativePath($p1, $p2) +{ + if (is_file($p1)) $p1 = dirname($p1); + if (is_file($p2)) $p2 = dirname($p2); + // using realpath function is necessary to resolve symbolic links + $p1 = realpath(cleanPath($p1)); + $p2 = realpath(cleanPath($p2)); + $res = ""; + // echo $p1."
"; + // echo $p2."
"; + while( $p1 != "" && + $p1 != "/" && // for unix root dir + !preg_match("/^[a-z]\:\\\$/i",$p1) && // for windows rootdir + strpos($p2, $p1) !== 0) + { + $res .= "../"; + $p1 = dirname($p1); + } + if (isset($_SERVER["WINDIR"]) || isset($_SERVER["windir"])) { + $p2 = str_replace("\\","/",substr($p2, strlen($p1)+1, strlen($p2)-strlen($p1))); + } else { + if ($p1 === "/" || $p1 === "") { + $p2 = substr($p2, strlen($p1)); + } else { + $p2 = substr($p2, strlen($p1)+1); + } + } + $res .= $p2; + // remove the last "/" + if (preg_match("/.*\/$/", $res)) $res = preg_replace("/(.*)\//","$1",$res); + // if rootpath is empty replace it by "." to avoide url starting with "/" + if ($res == "") $res = "."; + // echo $res."
"; + return $res; +} + +function cleanPath($path) +{ + $result = array(); + $pathA = explode(DIRECTORY_SEPARATOR, $path); + if (!$pathA[0]) + $result[] = ''; + foreach ($pathA AS $key => $dir) { + if ($dir == '..') { + if (end($result) == '..') { + $result[] = '..'; + } elseif (!array_pop($result)) { + $result[] = '..'; + } + } elseif ($dir && $dir != '.') { + $result[] = $dir; + } + } + if (!end($pathA)) + $result[] = ''; + return implode('/', $result); +} + + +function mkdir_r($path, $modedir = 0755) +{ + // This function creates the specified directory using mkdir(). Note + // that the recursive feature on mkdir() is broken with PHP 5.0.4 for + // Windows, so I have to do the recursion myself. + if (!file_exists($path)) + { + // The directory doesn't exist. Recurse, passing in the parent + // directory so that it gets created. + mkdir_r(dirname($path), $modedir); + mkdir($path, $modedir); + } +} + +function rm_r($dir) +{ + if(!$dh = @opendir($dir)) return; + while (($obj = readdir($dh))) + { + if($obj=='.' || $obj=='..') continue; + if (!@unlink($dir.'/'.$obj)) rm_r($dir.'/'.$obj); + } + closedir($dh); + @rmdir($dir); +} + +/** + * Copy a file, or recursively copy a folder and its contents + * + * @author Aidan Lister + * @link http://aidanlister.com/repos/v/function.copyr.php + * @param string $source Source path + * @param string $dest Destination path + * @return bool Returns TRUE on success, FALSE on failure + */ +function copy_r($source, $dest, $modedir = 0755, $modefile = 0664) +{ + // Simple copy for a file + if (is_file($source)) { + $ret = copy($source, $dest); + chmod($dest, $modefile); + return $ret; + } + + // Make destination directory + if (!is_dir($dest)) { + mkdir($dest, $modedir); + } + + // Take the directories entries + $dir = dir($source); + $entries = array(); + while (false !== $entry = $dir->read()) + { + $entries[] = $entry; + } + + // Loop through the folder + foreach ($entries as $e) + { + // Skip pointers and subversion directories + if ($e == '.' || $e == '..' || $e == '.svn') continue; + // Deep copy directories + if ($dest !== $source . DIRECTORY_SEPARATOR . $e) + copy_r($source . DIRECTORY_SEPARATOR . $e, $dest . DIRECTORY_SEPARATOR . $e, $modedir, $modefile); + } + + // Clean up + $dir->close(); + return true; +} + +/** + * Check the functions really exists on this server + */ +function check_functions_exist( $f_list ) +{ + $errors = array(); + foreach( $f_list as $func => $err ) + { + if (!function_exists( $func )) + $errors[] = _pfc("%s doesn't exist: %s", $func, $err); + } + return $errors; +} + + +function test_writable_dir($dir, $name = "") +{ + $errors = array(); + if ($dir == "") + $errors[] = _pfc("%s directory must be specified", ($name!="" ? $name : $dir)); + + if (is_file($dir)) + $this->errors[] = _pfc("%s must be a directory",$dir); + if (!is_dir($dir)) + mkdir_r($dir); + if (!is_dir($dir)) + $errors[] = _pfc("%s can't be created",$dir); + if (!is_writeable($dir)) + $errors[] = _pfc("%s is not writeable",$dir); + if (!is_readable($dir)) + $errors[] = _pfc("%s is not readable",$dir); + + return $errors; +} + +function install_file($src_file, $dst_file) +{ + $errors = array(); + + $src_dir = dirname($src_file); + $dst_dir = dirname($dst_file); + + if (!is_file($src_file)) + $errors[] = _pfc("%s is not a file", $src_file); + if (!is_readable($src_file)) + $errors[] = _pfc("%s is not readable", $src_file); + if (!is_dir($src_dir)) + $errors[] = _pfc("%s is not a directory", $src_dir); + if (!is_dir($dst_dir)) + mkdir_r($dst_dir); + + copy( $src_file, $dst_file ); + + return $errors; +} + +function install_dir($src_dir, $dst_dir) +{ + $errors = array(); + + if (!is_dir($src_dir)) + $errors[] = _pfc("%s is not a directory", $src_dir); + if (!is_readable($src_dir)) + $errors[] = _pfc("%s is not readable", $src_dir); + + copy_r( $src_dir, $dst_dir ); + + return $errors; +} + +/** + * file_get_contents_flock + * define an alternative file_get_contents when this function doesn't exists on the used php version (<4.3.0) + */ + +if (!defined('LOCK_SH')) { + define('LOCK_SH', 1); +} + +function file_get_contents_flock($filename, $incpath = false, $resource_context = null) +{ + if (false === $fh = fopen($filename, 'rb', $incpath)) { + user_error('file_get_contents() failed to open stream: No such file or directory ['.$filename.']', + E_USER_WARNING); + return false; + } + + // Attempt to get a shared (read) lock + if (!flock($fh, LOCK_SH)) { + return false; + } + + clearstatcache(); + if ($fsize = @filesize($filename)) { + $data = fread($fh, $fsize); + } else { + $data = ''; + while (!feof($fh)) { + $data .= fread($fh, 8192); + } + } + + fclose($fh); + return $data; +} + +/** + * file_get_contents + * define an alternative file_get_contents when this function doesn't exists on the used php version (<4.3.0) + */ +if (!function_exists('file_get_contents')) +{ + function file_get_contents($filename, $incpath = false, $resource_context = null) + { + if (false === $fh = fopen($filename, 'rb', $incpath)) + { + trigger_error('file_get_contents() failed to open stream: No such file or directory ['.$filename.']', E_USER_WARNING); + return false; + } + clearstatcache(); + if ($fsize = filesize($filename)) + { + $data = fread($fh, $fsize); + } + else + { + while (!feof($fh)) { + $data .= fread($fh, 8192); + } + } + fclose($fh); + return $data; + } +} + +/** + * Replace file_put_contents() + * + * @category PHP + * @package PHP_Compat + * @link http://php.net/function.file_put_contents + * @author Aidan Lister + * @internal resource_context is not supported + * @since PHP 5 + * @require PHP 4.0.0 (user_error) + */ +if (!defined('FILE_USE_INCLUDE_PATH')) { + define('FILE_USE_INCLUDE_PATH', 1); +} + +if (!defined('LOCK_EX')) { + define('LOCK_EX', 2); +} + +if (!defined('FILE_APPEND')) { + define('FILE_APPEND', 8); +} +if (!function_exists('file_put_contents')) { + function file_put_contents($filename, $content, $flags = null, $resource_context = null) + { + // If $content is an array, convert it to a string + if (is_array($content)) { + $content = implode('', $content); + } + + // If we don't have a string, throw an error + if (!is_scalar($content)) { + user_error('file_put_contents() The 2nd parameter should be either a string or an array ['.$filename.']', + E_USER_WARNING); + return false; + } + + // Get the length of data to write + $length = strlen($content); + + // Check what mode we are using + $mode = ($flags & FILE_APPEND) ? + 'a' : + 'wb'; + + // Check if we're using the include path + $use_inc_path = ($flags & FILE_USE_INCLUDE_PATH) ? + true : + false; + + // Open the file for writing + if (($fh = @fopen($filename, $mode, $use_inc_path)) === false) { + user_error('file_put_contents() failed to open stream: Permission denied ['.$filename.']', + E_USER_WARNING); + return false; + } + + // Attempt to get an exclusive lock + $use_lock = ($flags & LOCK_EX) ? true : false ; + if ($use_lock === true) { + if (!flock($fh, LOCK_EX)) { + return false; + } + } + + // Write to the file + $bytes = 0; + if (($bytes = @fwrite($fh, $content)) === false) { + $errormsg = sprintf('file_put_contents() Failed to write %d bytes to %s ['.$filename.']', + $length, + $filename); + user_error($errormsg, E_USER_WARNING); + return false; + } + + // Close the handle + @fclose($fh); + + // Check all the data was written + if ($bytes != $length) { + $errormsg = sprintf('file_put_contents() Only %d of %d bytes written, possibly out of free disk space. ['.$filename.']', + $bytes, + $length); + user_error($errormsg, E_USER_WARNING); + return false; + } + + // Return length + return $bytes; + } +} + + +/** + * iconv + * define an alternative iconv when this function doesn't exists on the php modules + */ +if (!function_exists('iconv')) +{ + if(function_exists('libiconv')) + { + // use libiconv if it exists + function iconv($input_encoding, $output_encoding, $string) + { + return libiconv($input_encoding, $output_encoding, $string); + } + } + else + { + // fallback if nothing has been found + function iconv($input_encoding, $output_encoding, $string) + { + return $string; + } + } +} + +/** + * Replace html_entity_decode() + * + * @category PHP + * @package PHP_Compat + * @link http://php.net/function.html_entity_decode + * @author David Irvine + * @author Aidan Lister + * @version $Revision: 1.8 $ + * @since PHP 4.3.0 + * @internal Setting the charset will not do anything + * @require PHP 4.0.0 (user_error) + */ + +if (!defined('ENT_NOQUOTES')) { + define('ENT_NOQUOTES', 0); +} + +if (!defined('ENT_COMPAT')) { + define('ENT_COMPAT', 2); +} + +if (!defined('ENT_QUOTES')) { + define('ENT_QUOTES', 3); +} + +if (!function_exists('html_entity_decode')) { + function html_entity_decode($string, $quote_style = ENT_COMPAT, $charset = null) + { + if (!is_int($quote_style)) { + user_error('html_entity_decode() expects parameter 2 to be long, ' . + gettype($quote_style) . ' given', E_USER_WARNING); + return; + } + + $trans_tbl = get_html_translation_table(HTML_ENTITIES); + $trans_tbl = array_flip($trans_tbl); + + // Add single quote to translation table; + $trans_tbl['''] = '\''; + + // Not translating double quotes + if ($quote_style & ENT_NOQUOTES) { + // Remove double quote from translation table + unset($trans_tbl['"']); + } + + return strtr($string, $trans_tbl); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/pfcurlprocessing.php b/instmess/src/pfcurlprocessing.php new file mode 100644 index 0000000..0366617 --- /dev/null +++ b/instmess/src/pfcurlprocessing.php @@ -0,0 +1,95 @@ + tag linking + * to that URL + * - Goes through the given string, and replaces www.xxxx.yyyy[zzzz] with an HTML tag linking + * to http://www.xxxx.yyyy[/zzzz] + * - Goes through the given string, and replaces xxxx@yyyy with an HTML mailto: tag linking + * to that email address + * - Only matches these 2 patterns either after a space, or at the beginning of a line + * + * Notes: the email one might get annoying - it's easy to make it more restrictive, though.. maybe + * have it require something like xxxx@yyyy.zzzz or such. We'll see. + */ +function pfc_make_hyperlink($text) +{ + $c =& pfcGlobalConfig::Instance(); + $openlinknewwindow = $c->openlinknewwindow; + + if ($openlinknewwindow) + $target = " onclick=\"window.open(this.href,\\'_blank\\');return false;\""; + else + $target = ""; + + $text = preg_replace('#(script|about|applet|activex|chrome):#is', "\\1:", $text); + + // pad it with a space so we can match things at the start of the 1st line. + $ret = ' ' . $text; + + // matches an "xxxx://yyyy" URL at the start of a line, or after a space. + // xxxx can only be alpha characters. + // yyyy is anything up to the first space, newline, comma, double quote or < + //$ret = preg_replace("#(^|[\n ])([\w]+?://[\w\#$%&~/.\-;:=,?@\[\]+]*)#is", "\\1\\2", $ret); + $ret = preg_replace("#(^|[\n \]])([\w]+?://[\w\#$%&~/.\-;:=,?@+]*)#ise", "'\\1' . pfc_shorten_url('\\2') . ''", $ret); + + // matches a "www|ftp.xxxx.yyyy[/zzzz]" kinda lazy URL thing + // Must contain at least 2 dots. xxxx contains either alphanum, or "-" + // zzzz is optional.. will contain everything up to the first space, newline, + // comma, double quote or <. + //$ret = preg_replace("#(^|[\n ])((www|ftp)\.[\w\#$%&~/.\-;:=,?@\[\]+]*)#is", "\\1\\2", $ret); + $ret = preg_replace("#(^|[\n \]])((www|ftp)\.[\w\#$%&~/.\-;:=,?@+]*)#ise", "'\\1' . pfc_shorten_url('\\2') . ''", $ret); + + // matches an email@domain type address at the start of a line, or after a space. + // Note: Only the followed chars are valid; alphanums, "-", "_" and or ".". + //$ret = preg_replace("#(^|[\n ])([a-z0-9&\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i", "\\1\\2@\\3", $ret); + $ret = preg_replace("#(^|[\n \]])([a-z0-9&\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#ie", "'\\1' . pfc_shorten_url('\\2@\\3') . ''", $ret); + + // Remove our padding.. + $ret = substr($ret, 1); + + return($ret); +} + +/** + * Nathan Codding - Feb 6, 2001 + * Reverses the effects of make_clickable(), for use in editpost. + * - Does not distinguish between "www.xxxx.yyyy" and "http://aaaa.bbbb" type URLs. + * + */ +function pfc_undo_make_hyperlink($text) +{ + $text = preg_replace("#.*?#i", "\\1", $text); + $text = preg_replace("#.*?#i", "\\1", $text); + + return $text; + +} + +function pfc_shorten_url($url) +{ + $c =& pfcGlobalConfig::Instance(); + + if (! $c->short_url) + return $url; + + // Short URL Width + $shurl_w = $c->short_url_width; + + $shurl_end_w = floor($shurl_w * .25) - 3; + if ($shurl_end_w < 3) $shurl_end_w = 3; + $shurl_begin_w = $shurl_w - $shurl_end_w - 3; + if ($shurl_begin_w < 3) $shurl_begin_w = 3; + + $decodedurl = html_entity_decode($url, ENT_QUOTES); + + $len = strlen($decodedurl); + $short_url = ($len > $shurl_w) ? substr($decodedurl, 0, $shurl_begin_w) . "..." . substr($decodedurl, -$shurl_end_w) : $decodedurl; + + return htmlentities($short_url, ENT_QUOTES); +} + +?> diff --git a/instmess/src/pfcuserconfig.class.php b/instmess/src/pfcuserconfig.class.php new file mode 100644 index 0000000..7b2b23e --- /dev/null +++ b/instmess/src/pfcuserconfig.class.php @@ -0,0 +1,140 @@ +nickid = sha1(session_id()); + + // user parameters are cached in sessions + $this->_getParam("nick"); + if (!isset($this->nick)) $this->_setParam("nick",""); // setup a blank nick if it is not yet in session + $this->_getParam("active"); + if (!isset($this->active)) $this->_setParam("active",false); + $this->_getParam("channels"); + if (!isset($this->channels)) $this->_setParam("channels",array()); + $this->_getParam("privmsg"); + if (!isset($this->privmsg)) $this->_setParam("privmsg",array()); + $this->_getParam("serverid"); + if (!isset($this->serverid)) $this->_setParam("serverid",$c->serverid); + } + + function &Instance() + { + static $i; + + if (!isset($i)) + { + $i = new pfcUserConfig(); + } + return $i; + } + + function &_getParam($p) + { + if (!isset($this->$p)) + { + $c =& pfcGlobalConfig::Instance(); + $nickid = 'pfcuserconfig_'.$c->getId().'_'.$this->nickid; + $nickid_param = $nickid."_".$p; + if (isset($_SESSION[$nickid_param])) + $this->$p = $_SESSION[$nickid_param]; + } + return $this->$p; + } + + function _setParam($p, $v) + { + $c =& pfcGlobalConfig::Instance(); + $nickid_param = 'pfcuserconfig_'.$c->getId().'_'.$this->nickid.'_'.$p; + $_SESSION[$nickid_param] = $v; + $this->$p = $v; + } + + function _rmParam($p) + { + $c =& pfcGlobalConfig::Instance(); + $nickid_param = 'pfcuserconfig_'.$c->getId().'_'.$this->nickid.'_'.$p; + unset($_SESSION[$nickid_param]); + unset($this->$p); + if ($p == 'active') $this->active = false; + } + + + function destroy() + { + $this->_rmParam("nick"); + $this->_rmParam("active"); + $this->_rmParam("channels"); + $this->_rmParam("privmsg"); + $this->_rmParam("serverid"); + } + + function saveInCache() + { + // echo "saveInCache()
"; + $c =& pfcGlobalConfig::Instance(); + + // do not save anything as long as nickname is not assigned + //if ($this->active && $this->nick != "") + { + $this->_setParam("nick", $this->nick); + $this->_setParam("active", $this->active); + $this->_setParam("channels", $this->channels); + $this->_setParam("privmsg", $this->privmsg); + $this->_setParam("serverid", $this->serverid); + } + } + + function isOnline() + { + $ct =& pfcContainer::Instance(); + $online = $ct->isNickOnline(NULL, $this->nickid); + return $online; + } + + function getNickname() + { + if ($this->nick != '') return $this->nick; + $ct =& pfcContainer::Instance(); + return $ct->getNickname($this->nickid); + } + + function getChannelNames() + { + $list = array(); + foreach( $this->channels as $v ) + $list[] = $v["name"]; + return $list; + } + function getPrivMsgNames() + { + $list = array(); + foreach( $this->privmsg as $v ) + $list[] = $v["name"]; + return $list; + } +} + +?> \ No newline at end of file diff --git a/instmess/src/phpfreechat.class.php b/instmess/src/phpfreechat.class.php new file mode 100644 index 0000000..2fd9eaf --- /dev/null +++ b/instmess/src/phpfreechat.class.php @@ -0,0 +1,573 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +require_once dirname(__FILE__).'/pfccommand.class.php'; +require_once dirname(__FILE__).'/pfcglobalconfig.class.php'; +require_once dirname(__FILE__).'/pfcuserconfig.class.php'; +require_once dirname(__FILE__).'/pfctemplate.class.php'; +require_once dirname(__FILE__).'/../lib/utf8/utf8_substr.php'; +require_once dirname(__FILE__).'/pfcresponse.class.php'; + +/** + * phpFreeChat is the entry point for developpers + * + * @example ../demo/demo1_simple.php + * @author Stephane Gully + */ +class phpFreeChat +{ + function phpFreeChat( &$params ) + { + if (!is_array($params)) + die('phpFreeChat parameters must be an array'); + + // initialize the global config object + $c =& pfcGlobalConfig::Instance( $params ); + + // need to initiate the user config object here because it uses sessions + $u =& pfcUserConfig::Instance(); + + // this code is used to handle the AJAX call and build the response + if (isset($_REQUEST['pfc_ajax'])) + { + $function = isset($_REQUEST['f']) ? $_REQUEST['f'] : ''; + $cmd = isset($_REQUEST['cmd']) ? stripslashes($_REQUEST['cmd']) : ''; + $r = null; + if ($function && method_exists($this,$function)) + { + require_once dirname(__FILE__).'/pfcresponse.class.php'; + $r =& $this->$function($cmd); + } + echo $r->getOutput(); + die(); + } + } + + /** + * depreciated + */ + function printJavaScript( $return = false ) + { + $output = ''; + if ($return) + return $output; + else + echo $output; + } + + /** + * depreciated + */ + function printStyle( $return = false ) + { + $output = ''; + if($return) + return $output; + else + echo $output; + } + + /** + * printChat must be called somewhere in the page + * it inserts necessary html which will receive chat's data + * usage: + * + * printChat(); ?> + * + */ + function printChat( $return = false ) + { + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + + $output = ''; + + if (count($c->getErrors()) > 0) + { + $output .= "

phpFreeChat cannot be initialized, please correct these errors:

    "; + foreach( $c->getErrors() as $e ) $output .= "
  • ".$e."
  • "; $output .= "
"; + } + else + { + pfcI18N::SwitchOutputEncoding($c->output_encoding); + + $path = $c->getFilePathFromTheme('chat.js.tpl.php'); + $t = new pfcTemplate($path); + $t->assignObject($u,"u"); + $t->assignObject($c,"c"); + $output .= $t->getOutput(); + + pfcI18N::SwitchOutputEncoding(); + } + + if($return) + return $output; + else + echo $output; + } + + /** + * Encode special caracteres and remove extra slashes + */ + function FilterSpecialChar($str) + { + return htmlspecialchars($str, ENT_NOQUOTES); + } + + /** + * Just check the nicknames doesn't start with spaces and is not longer than the max_nick_len + */ + function FilterNickname($nickname) + { + $c =& pfcGlobalConfig::Instance(); + //$nickname = str_replace("\\", "", $nickname); // '\' is a forbidden charactere for nicknames + $nickname = trim($nickname); + $nickname = utf8_substr($nickname, 0, $c->max_nick_len); + return $nickname; + } + + /** + * search/replace smileys + */ + function FilterSmiley($msg) + { + $c =& pfcGlobalConfig::Instance(); + // build a preg_replace array + $search = array(); + $replace = array(); + $query = "/("; + foreach($c->smileys as $s_file => $s_strs) + { + foreach ($s_strs as $s_str) + { + $s_str = stripslashes($s_str); /* the :'( smileys needs this filter */ + $query .= preg_quote($s_str,'/')."|"; + $search[] = "/".preg_quote($s_str,'/')."/"; + $replace[] = ''.$s_str.''; + } + } + $query = substr($query, 0, strlen($query)-1); + $query .= ")/i"; + + $split_words = preg_split($query, $msg, -1, PREG_SPLIT_DELIM_CAPTURE); + $msg = ""; + foreach($split_words as $word) + $msg .= preg_replace($search, $replace, $word); + return $msg; + } + + + /** + * Filter messages before they are sent to container + */ + function PreFilterMsg($msg) + { + $c =& pfcGlobalConfig::Instance(); + if (preg_match("/^\[/i",$msg)) + // add 25 characteres if the message starts with [ : means there is a bbcode + $msg = utf8_substr($msg, 0, $c->max_text_len+25); + else + $msg = utf8_substr($msg, 0, $c->max_text_len); + $msg = phpFreeChat::FilterSpecialChar($msg); + + // $msg = phpFreeChat::FilterSmiley($msg); + + /* if ($msg[0] == "\n") $msg = substr($msg, 1); */ // delete the first \n generated by FF + /* if (strpos($msg,"\n") > 0) $msg = "
".$msg; + $msg = str_replace("\r\n", "
", $msg); + $msg = str_replace("\n", "
", $msg); + $msg = str_replace("\t", " ", $msg);*/ + //$msg = str_replace(" ", "  ", $msg); + // $msg = preg_replace('/(http\:\/\/[^\s]*)/i', "$1", $msg ); + return $msg; + } + + /** + * Filter messages when they are recived from container + */ + function PostFilterMsg($msg) + { + //$c =& pfcGlobalConfig::Instance(); + // $msg = preg_replace('/('.preg_quote($c->nick,'/').')/i', "$1", $msg ); + $msg = preg_replace('/\n/i', "", $msg ); + return $msg; + } + + function &handleRequest($request) + { + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + + if ($c->debug) ob_start(); // capture output + + $xml_reponse = new pfcResponse(); + + // check the command + $cmdstr = ""; + $cmdname = ""; + $clientid = ""; + $recipient = ""; + $recipientid = ""; + $param = ""; + $sender = ""; + + $res = pfcCommand::ParseCommand($request); + + $cmdstr = isset($res['cmdstr']) ? $res['cmdstr'] : $request; + $cmdname = strtolower(isset($res['cmdname']) ? $res['cmdname'] : ''); + $clientid = isset($res['params'][0]) ? $res['params'][0] : ''; + $recipientid = isset($res['params'][1]) ? $res['params'][1] : ""; + $params = array_slice(is_array($res['params']) ? $res['params'] : array() ,2); + $param = implode(" ",$params); // to keep compatibility (will be removed) + $sender = $u->getNickname(); + + // translate the recipientid to the channel name + if (isset($u->channels[$recipientid])) + { + $recipient = $u->channels[$recipientid]["recipient"]; + } + if (isset($u->privmsg[$recipientid])) + { + $recipient = $u->privmsg[$recipientid]["recipient"]; + + + // @todo: move this code in a proxy + if ($cmdname != "update" && + $cmdname != "leave" && // do not open the pv tab when other user close the tab + $cmdname != "quit" && + $cmdname != "privmsg2") + { + // alert the other from the new pv + // (warn other user that someone talk to him) + + $ct =& pfcContainer::Instance(); + $nickidtopv = $u->privmsg[$recipientid]["pvnickid"]; + $cmdstr = 'privmsg2'; + $cmdp = array(); + $cmdp['param'] = $u->nickid;//$sender; + $cmdp['params'][] = $u->nickid;//$sender; + pfcCommand::AppendCmdToPlay($nickidtopv, $cmdstr, $cmdp); + } + + } + + + $cmdp = array(); + $cmdp["clientid"] = $clientid; + $cmdp["sender"] = $sender; + $cmdp["recipient"] = $recipient; + $cmdp["recipientid"] = $recipientid; + // before playing the wanted command + // play the found commands into the meta 'cmdtoplay' + pfcCommand::RunPendingCmdToPlay($u->nickid, $cmdp, $xml_reponse); + // play the wanted command + $cmd =& pfcCommand::Factory($cmdname); + $cmdp["param"] = $param; + $cmdp["params"] = $params; + if ($cmd != NULL) + { + // call the command + if ($c->debug) + $cmd->run($xml_reponse, $cmdp); + else + @$cmd->run($xml_reponse, $cmdp); + } + else + { + $cmd =& pfcCommand::Factory("error"); + $cmdp = array(); + $cmdp["clientid"] = $clientid; + $cmdp["param"] = _pfc("Unknown command [%s]",stripslashes("/".$cmdname." ".$param)); + $cmdp["sender"] = $sender; + $cmdp["recipient"] = $recipient; + $cmdp["recipientid"] = $recipientid; + if ($c->debug) + $cmd->run($xml_reponse, $cmdp); + else + @$cmd->run($xml_reponse, $cmdp); + } + + // do not update twice + // do not update when the user just quit + if ($cmdname != "update" && + $cmdname != "quit" && + $u->nickid != '') + { + // force an update just after a command is sent + // thus the message user just poster is really fastly displayed + $cmd =& pfcCommand::Factory("update"); + $cmdp = array(); + $cmdp["clientid"] = $clientid; + $cmdp["param"] = $param; + $cmdp["sender"] = $sender; + $cmdp["recipient"] = $recipient; + $cmdp["recipientid"] = $recipientid; + if ($c->debug) + $cmd->run($xml_reponse, $cmdp); + else + @$cmd->run($xml_reponse, $cmdp); + } + + if ($c->debug) + { + // capture echoed content + // if a content not empty is captured it is a php error in the code + $data = ob_get_contents(); + if ($data != "") + { + // todo : display the $data somewhere to warn the user + } + ob_end_clean(); + } + + // do nothing else if the xml response is empty in order to save bandwidth + if ($xml_reponse->getCommandCount() == 0) die(); + + return $xml_reponse; + } + + + function &loadStyles($theme = 'default', &$xml_reponse) + { + if ($xml_reponse == null) $xml_reponse = new pfcResponse(); + + $c =& pfcGlobalConfig::Instance(); + + // do not overload the theme parameter as long as + // the ajax request do not give the correct one + // $c->theme = $theme; + + $u =& pfcUserConfig::Instance(); + + $js = '';//file_get_contents(dirname(__FILE__).'/client/createstylerule.js'); + $js .= 'var c = $H();'; + $path = $c->getFilePathFromTheme('style.css.php'); + require_once dirname(__FILE__).'/../lib/ctype/ctype.php'; // to keep compatibility for php without ctype support + require_once dirname(__FILE__).'/../lib/csstidy-1.2/class.csstidy.php'; + $css = new csstidy(); + $css->set_cfg('preserve_css',false); + + + $css_code = ''; + $t = new pfcTemplate(); + $t->assignObject($u,"u"); + $t->assignObject($c,"c"); + if (!$c->isDefaultFile('style.css.php')) + { + $t->setTemplate($c->theme_default_path.'/default/style.css.php'); + $css_code .= $t->getOutput(); + } + $t->setTemplate($c->getFilePathFromTheme('style.css.php')); + $css_code .= $t->getOutput(); + + $css->parse($css_code); + foreach($css->css as $k => $v) + { + foreach($v as $k2 => $v2) + { + $rules = ''; + foreach($v2 as $k3 => $v3) + $rules .= $k3.':'.$v3.';'; + $js .= "c.set('".$k2."', '".str_replace("\n", "", $rules)."');\n"; + } + } + $js .= "var pfccss = new pfcCSS(); var k = c.keys(); c.each(function (a) { pfccss.applyRule(a[0],a[1]); });"; + $xml_reponse->script($js); + + return $xml_reponse; + } + + function &loadScripts($theme, &$xml_reponse) + { + if ($xml_reponse == null) $xml_reponse = new pfcResponse(); + + $c =& pfcGlobalConfig::Instance(); + + $js = ''; + + // load customize.js.php + $path = $c->getFilePathFromTheme('customize.js.php'); + $t = new pfcTemplate($path); + $t->assignObject($c,"c"); + $js .= $t->getOutput(); + + // load translations + require_once dirname(__FILE__).'/pfcjson.class.php'; + $json = new pfcJSON(); + + $labels_to_load = + array( "Do you really want to leave this room ?", // _pfc + "Are you sure you want to close this tab ?", // _pfc + "Hide nickname marker", // _pfc + "Show nickname marker", // _pfc + "Hide dates and hours", // _pfc + "Show dates and hours", // _pfc + "Disconnect", // _pfc + "Connect", // _pfc + "Magnify", // _pfc + "Cut down", // _pfc + "Hide smiley box", // _pfc + "Show smiley box", // _pfc + "Hide online users box", // _pfc + "Show online users box", // _pfc + "Please enter your nickname", // _pfc + "Private message", // _pfc + "Close this tab", // _pfc + "Enter your message here", // _pfc + "Enter your nickname here", // _pfc + "Bold", // _pfc + "Italics", // _pfc + "Underline", // _pfc + "Delete", // _pfc + "Mail", // _pfc + "Color", // _pfc + "PHP FREE CHAT [powered by phpFreeChat-%s]", // _pfc + "Enter the text to format", // _pfc + "Configuration has been rehashed", // _pfc + "A problem occurs during rehash", // _pfc + "Chosen nickname is already used", // _pfc + "phpfreechat current version is %s", // _pfc + "Maximum number of joined channels has been reached", // _pfc + "Maximum number of private chat has been reached", // _pfc + "Click here to send your message", // _pfc + "Send", // _pfc + "You are not allowed to speak to yourself", // _pfc + "Close", // _pfc + "Chosen nickname is not allowed", // _pfc + "Enable sound notifications", // _pfc + "Disable sound notifications", // _pfc + "Input Required", // _pfc + "OK", // _pfc + "Cancel", // _pfc + "You are trying to speak to a unknown (or not connected) user", // _pfc + ); + foreach($labels_to_load as $l) + { + $js .= "pfc.res.setLabel(".$json->encode($l).",".$json->encode(_pfc2($l)).");\n"; + } + + // load ressources + $fileurl_to_load = + array( 'images/ch.gif', + 'images/pv.gif', + 'images/tab_remove.gif', + 'images/ch-active.gif', + 'images/pv-active.gif', + 'images/user.gif', + 'images/user-me.gif', + 'images/user_female.gif', + 'images/user_female-me.gif', + 'images/color-on.gif', + 'images/color-off.gif', + 'images/clock-on.gif', + 'images/clock-off.gif', + 'images/logout.gif', + 'images/login.gif', + 'images/maximize.gif', + 'images/minimize.gif', + 'images/smiley-on.gif', + 'images/smiley-off.gif', + 'images/online-on.gif', + 'images/online-off.gif', + 'images/bt_strong.gif', + 'images/bt_em.gif', + 'images/bt_ins.gif', + 'images/bt_del.gif', + 'images/bt_mail.gif', + 'images/bt_color.gif', + 'images/color_transparent.gif', + 'images/close-whoisbox.gif', + 'images/openpv.gif', + 'images/user-admin.gif', + 'images/sound-on.gif', + 'images/sound-off.gif', + 'sound.swf', + ); + + foreach($fileurl_to_load as $f) + { + $js .= "pfc.res.setFileUrl(".$json->encode($f).",\"".$c->getFileUrlFromTheme($f)."\");\n"; + } + + foreach($c->smileys as $s_file => $s_str) { + for($j = 0; $jencode($s_str[$j]).",\"".$c->getFileUrlFromTheme($s_file)."\");\n"; + } + } + + $js .= ' +pfc.gui.loadSmileyBox(); +pfc.gui.loadBBCodeColorList(); +pfc.connectListener(); +pfc.refreshGUI(); +if (pfc_connect_at_startup) pfc.connect_disconnect(); +'; + + $xml_reponse->script($js); + return $xml_reponse; + } + + + function loadInterface($theme = 'default', &$xml_reponse) + { + if ($xml_reponse == null) $xml_reponse = new pfcResponse(); + + $c =& pfcGlobalConfig::Instance(); + + // do not overload the theme parameter as long as + // the ajax request do not give the correct one + // $c->theme = $theme; + + $u =& pfcUserConfig::Instance(); + + $html = ''; + + // pfcI18N::SwitchOutputEncoding($c->output_encoding); + + $path = $c->getFilePathFromTheme('chat.html.tpl.php'); + $t = new pfcTemplate($path); + $t->assignObject($u,"u"); + $t->assignObject($c,"c"); + $html .= $t->getOutput(); + + // pfcI18N::SwitchOutputEncoding(); + + $xml_reponse->remove('pfc_loader'); // to hide the loading box + $xml_reponse->update('pfc_container', $html); + + return $xml_reponse; + } + + function &loadChat($theme = 'default') + { + $xml_reponse = new pfcResponse(); + + $this->loadInterface($theme,$xml_reponse); + $this->loadStyles($theme,$xml_reponse); + $this->loadScripts($theme,$xml_reponse); + + return $xml_reponse; + } + +} + +?> diff --git a/instmess/src/proxies/auth.class.php b/instmess/src/proxies/auth.class.php new file mode 100644 index 0000000..c807bfc --- /dev/null +++ b/instmess/src/proxies/auth.class.php @@ -0,0 +1,116 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +require_once dirname(__FILE__)."/../pfci18n.class.php"; +require_once dirname(__FILE__)."/../pfcuserconfig.class.php"; +require_once dirname(__FILE__)."/../pfcproxycommand.class.php"; + +/** + * pfcProxyCommand_auth + * + * @author Stephane Gully + */ +class pfcProxyCommand_auth extends pfcProxyCommand +{ + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + + + // do not allow someone to run a command if he is not online + if ( !$u->isOnline() && + $this->name != 'error' && + $this->name != 'connect' && + $this->name != 'update' ) + { + $cmdp = $p; + $cmdp["param"] = _pfc("Your must be connected to send a message"); + $cmd =& pfcCommand::Factory("error"); + $cmd->run($xml_reponse, $cmdp); + return false; + } + + + // protect admin commands + $admincmd = array("kick", "ban", "unban", "op", "deop", "debug", "rehash"); + if ( in_array($this->name, $admincmd) ) + { + $container =& pfcContainer::Instance(); + $nickid = $u->nickid; + $isadmin = $container->getUserMeta($nickid, 'isadmin'); + if (!$isadmin) + { + $xml_reponse->script("alert('".addslashes(_pfc("You are not allowed to run '%s' command", $this->name))."');"); + return false; + } + } + + // channels protection + if ($this->name == "join" || + $this->name == "join2") + { + $container =& pfcContainer::Instance(); + $channame = $param; + + // check the user is not listed in the banished channel list + $chan = pfcCommand_join::GetRecipient($channame); + $chanid = pfcCommand_join::GetRecipientId($channame); + $banlist = $container->getChanMeta($chan, 'banlist_nickid'); + if ($banlist == NULL) $banlist = array(); else $banlist = unserialize($banlist); + $nickid = $u->nickid; + if (in_array($nickid,$banlist)) + { + // the user is banished, show a message and don't forward the /join command + $msg = _pfc("Can't join %s because you are banished", $param); + $xml_reponse->script("pfc.handleResponse('".$this->proxyname."', 'ban', '".addslashes($msg)."');"); + return false; + } + + if (count($c->frozen_channels)>0) + { + if (!in_array($channame,$c->frozen_channels)) + { + // the user is banished, show a message and don't forward the /join command + $msg = _pfc("Can't join %s because the channels list is restricted", $param); + $xml_reponse->script("pfc.handleResponse('".$this->proxyname."', 'frozen', '".addslashes($msg)."');"); + return false; + } + } + } + + // forward the command to the next proxy or to the final command + $p["clientid"] = $clientid; + $p["param"] = $param; + $p["sender"] = $sender; + $p["recipient"] = $recipient; + $p["recipientid"] = $recipientid; + return $this->next->run($xml_reponse, $p); + } +} + +?> diff --git a/instmess/src/proxies/censor.class.php b/instmess/src/proxies/censor.class.php new file mode 100644 index 0000000..4b5f222 --- /dev/null +++ b/instmess/src/proxies/censor.class.php @@ -0,0 +1,81 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +require_once dirname(__FILE__)."/../pfci18n.class.php"; +require_once dirname(__FILE__)."/../pfcuserconfig.class.php"; +require_once dirname(__FILE__)."/../pfcproxycommand.class.php"; + +/** + * pfcProxyCommand_censor + * this proxy will filter bad words from messages + * @author Stephane Gully + */ +class pfcProxyCommand_censor extends pfcProxyCommand +{ + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + + $cmdtocheck = array("send", "nick", "me"); + if ( in_array($this->name, $cmdtocheck) ) + { + $words = $c->proxies_cfg[$this->proxyname]["words"]; + $replaceby = $c->proxies_cfg[$this->proxyname]["replaceby"]; + $regex = $c->proxies_cfg[$this->proxyname]["regex"]; + + $patterns = array(); + $replacements = array(); + foreach($words as $w) + { + if ($regex) + { + // the words are regular expressions + $patterns[] = "/".$w."/ie"; + $replacements[] = "'\\1'.str_repeat('$replaceby',strlen('\\2')).'\\3'"; + } + else + { + // the words are simple words + $patterns[] = "/".preg_quote($w)."/i"; + $replacements[] = str_repeat($replaceby,strlen($w)); + } + } + $param = preg_replace($patterns, $replacements, $param); + } + + // forward the command to the next proxy or to the final command + $p["clientid"] = $clientid; + $p["param"] = $param; + $p["sender"] = $sender; + $p["recipient"] = $recipient; + $p["recipientid"] = $recipientid; + return $this->next->run($xml_reponse, $p); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/proxies/checknickchange.class.php b/instmess/src/proxies/checknickchange.class.php new file mode 100644 index 0000000..57e75cc --- /dev/null +++ b/instmess/src/proxies/checknickchange.class.php @@ -0,0 +1,126 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +require_once dirname(__FILE__)."/../pfci18n.class.php"; +require_once dirname(__FILE__)."/../pfcuserconfig.class.php"; +require_once dirname(__FILE__)."/../pfcproxycommand.class.php"; + +/** + * pfcProxyCommand_checknickchange + * + * @author Stephane Gully + */ +class pfcProxyCommand_checknickchange extends pfcProxyCommand +{ + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + $owner = isset($p["owner"]) ? $p["owner"] : ''; + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + $ct =& pfcContainer::Instance(); + + $newnick = phpFreeChat::FilterNickname($param); + $oldnick = $ct->getNickname($u->nickid); + + if ( $this->name == 'nick' ) + { + + // if the user want to change his nickname but the frozen_nick is enable + // then send him a warning + if ( $this->name == 'nick' && + $oldnick != '' && + $newnick != $oldnick && + $c->frozen_nick == true && + $owner != $this->proxyname ) + { + $msg = _pfc("You are not allowed to change your nickname"); + $xml_reponse->script("pfc.handleResponse('".$this->proxyname."', 'nick', '".addslashes($msg)."');"); + return false; + } + + $newnickid = $ct->getNickId($newnick); + $oldnickid = $u->nickid; + + if ($newnick == $oldnick && + $newnickid == $oldnickid) + { + $xml_reponse->script("pfc.handleResponse('".$this->name."', 'notchanged', '".addslashes($newnick)."');"); + return true; + } + + // now check the nickname is not yet used (unsensitive case) + // 'BoB' and 'bob' must be considered same nicknames + $nick_in_use = $this->_checkNickIsUsed($newnick, $oldnickid); + if ($nick_in_use) + { + if ($c->frozen_nick) + $xml_reponse->script("pfc.handleResponse('nick', 'notallowed', '".addslashes($newnick)."');"); + else + $xml_reponse->script("pfc.handleResponse('nick', 'isused', '".addslashes($newnick)."');"); + return false; + } + } + + // allow nick changes only from the parameters array (server side) + if ($this->name != 'connect' && // don't check anything on the connect process or it could block the periodic refresh + $c->frozen_nick == true && + $oldnick != $c->nick && + $c->nick != '' && // don't change the nickname to empty or the asknick popup will loop indefinatly + $owner != $this->proxyname) + { + // change the user nickname + $cmdp = $p; + $cmdp["param"] = $c->nick; + $cmdp["owner"] = $this->proxyname; + $cmd =& pfcCommand::Factory("nick"); + return $cmd->run($xml_reponse, $cmdp); + } + + // forward the command to the next proxy or to the final command + return $this->next->run($xml_reponse, $p); + } + + function _checkNickIsUsed($newnick, $oldnickid) + { + $c =& pfcGlobalConfig::Instance(); + $ct =& pfcContainer::Instance(); + $nick_in_use = false; + $online_users = $ct->getOnlineNick(NULL); + if (isset($online_users["nickid"])) + foreach($online_users["nickid"] as $nid) + { + if (preg_match("/^".preg_quote($ct->getNickname($nid))."$/i",$newnick)) + { + // the nick match + // just allow the owner to change his capitalised letters + if ($nid != $oldnickid) + return true; + } + } + } +} + +?> diff --git a/instmess/src/proxies/checktimeout.class.php b/instmess/src/proxies/checktimeout.class.php new file mode 100644 index 0000000..456e334 --- /dev/null +++ b/instmess/src/proxies/checktimeout.class.php @@ -0,0 +1,79 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +require_once dirname(__FILE__)."/../pfci18n.class.php"; +require_once dirname(__FILE__)."/../pfcuserconfig.class.php"; +require_once dirname(__FILE__)."/../pfcproxycommand.class.php"; + +/** + * pfcProxyCommand_checktimeout + * this command disconnect obsolete users (timouted) + * an obsolete user is an user which didn't update his stats since more than 20 seconds (default timeout value) + * @author Stephane Gully + */ +class pfcProxyCommand_checktimeout extends pfcProxyCommand +{ + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + + if ($this->name == 'update' || $this->name == 'connect') + { + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + $ct =& pfcContainer::Instance(); + + // disconnect users from channels when they timeout + $disconnected_users = $ct->removeObsoleteNick($c->timeout); + for($i=0; $igetOnlineNick($chan); + if (count($online_users['nickid']) > 0) + { + $cmdp = $p; + $cmdp["param"] = _pfc("%s quit (timeout)", $nick); + $cmdp["flag"] = 2; + $cmdp["recipient"] = $chan; + $cmdp["recipientid"] = md5($chan); // @todo: clean the recipient/recipientid notion + $cmd =& pfcCommand::Factory("notice"); + $cmd->run($xml_reponse, $cmdp); + } + } + } + } + } + + // forward the command to the next proxy or to the final command + return $this->next->run($xml_reponse, $p); + } +} + +?> diff --git a/instmess/src/proxies/lock.class.php b/instmess/src/proxies/lock.class.php new file mode 100644 index 0000000..4a0d460 --- /dev/null +++ b/instmess/src/proxies/lock.class.php @@ -0,0 +1,63 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +require_once dirname(__FILE__)."/../pfci18n.class.php"; +require_once dirname(__FILE__)."/../pfcuserconfig.class.php"; +require_once dirname(__FILE__)."/../pfcproxycommand.class.php"; + +/** + * pfcProxyCommand_lock + * if the chat is locked, redirect users to a given url + * @author Stephane Gully + */ +class pfcProxyCommand_lock extends pfcProxyCommand +{ + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + + // check if the chat is locked + if ($c->islocked) + { + $xml_reponse->redirect($c->lockurl); + return false; + } + else + { + // forward the command to the next proxy or to the final command + $p["clientid"] = $clientid; + $p["param"] = $param; + $p["sender"] = $sender; + $p["recipient"] = $recipient; + $p["recipientid"] = $recipientid; + return $this->next->run($xml_reponse, $p); + } + } +} + +?> \ No newline at end of file diff --git a/instmess/src/proxies/log.class.php b/instmess/src/proxies/log.class.php new file mode 100644 index 0000000..eba5877 --- /dev/null +++ b/instmess/src/proxies/log.class.php @@ -0,0 +1,72 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +require_once dirname(__FILE__)."/../pfci18n.class.php"; +require_once dirname(__FILE__)."/../pfcuserconfig.class.php"; +require_once dirname(__FILE__)."/../pfcproxycommand.class.php"; + +/** + * pfcProxyCommand_log + * this proxy will log "everything" from the chat + * @author Stephane Gully + */ +class pfcProxyCommand_log extends pfcProxyCommand +{ + function run(&$xml_reponse, $p) + { + $cmdtocheck = array("send", "me", "notice"); + if ( in_array($this->name, $cmdtocheck) ) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + + $logpath = ($c->proxies_cfg[$this->proxyname]["path"] == "" ? $c->data_private_path."/logs" : + $c->proxies_cfg[$this->proxyname]["path"]); + $logpath .= "/".$c->getId(); + + if (!file_exists($logpath)) @mkdir_r($logpath); + if (file_exists($logpath) && is_writable($logpath)) + { + $logfile = $logpath."/chat.log"; + if (is_writable($logpath)) + { + // @todo write logs in a cleaner structured language (xml, html ... ?) + $log = $recipient."\t"; + $log .= date("d/m/Y")."\t"; + $log .= date("H:i:s")."\t"; + $log .= $sender."\t"; + $log .= $param."\n"; + file_put_contents($logfile, $log, FILE_APPEND | LOCK_EX); + } + } + } + + // forward the command to the next proxy or to the final command + return $this->next->run($xml_reponse, $p); + } +} + +?> \ No newline at end of file diff --git a/instmess/src/proxies/noflood.class.php b/instmess/src/proxies/noflood.class.php new file mode 100644 index 0000000..93d49fc --- /dev/null +++ b/instmess/src/proxies/noflood.class.php @@ -0,0 +1,105 @@ + + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +require_once dirname(__FILE__)."/../pfci18n.class.php"; +require_once dirname(__FILE__)."/../pfcuserconfig.class.php"; +require_once dirname(__FILE__)."/../pfcproxycommand.class.php"; +require_once dirname(__FILE__)."/../../lib/utf8/utf8_strlen.php"; + +/** + * pfcProxyCommand_noflood + * this proxy will protect the chat from flooders + * @author Stephane Gully + */ +class pfcProxyCommand_noflood extends pfcProxyCommand +{ + function run(&$xml_reponse, $p) + { + $clientid = $p["clientid"]; + $param = $p["param"]; + $sender = $p["sender"]; + $recipient = $p["recipient"]; + $recipientid = $p["recipientid"]; + + $c =& pfcGlobalConfig::Instance(); + $u =& pfcUserConfig::Instance(); + + $cmdtocheck = array("send", "nick", "me"); + if ( in_array($this->name, $cmdtocheck) ) + { + $container =& pfcContainer::Instance(); + $nickid = $u->nickid; + $isadmin = $container->getUserMeta($nickid, 'isadmin'); + $lastfloodtime = $container->getUserMeta($nickid, 'floodtime'); + $flood_nbmsg = $container->getUserMeta($nickid, 'flood_nbmsg'); + $flood_nbchar = $container->getUserMeta($nickid, 'flood_nbchar'); + $floodtime = time(); + + if ($floodtime - $lastfloodtime <= $c->proxies_cfg[$this->proxyname]["delay"]) + { + // update the number of posted message indicator + $flood_nbmsg++; + // update the number of posted characteres indicator + $flood_nbchar += utf8_strlen($param); + } + else + { + $flood_nbmsg = 0; + $flood_nbchar = 0; + } + + if (!$isadmin && + ($flood_nbmsg>$c->proxies_cfg[$this->proxyname]["msglimit"] || + $flood_nbchar>$c->proxies_cfg[$this->proxyname]["charlimit"]) + ) + { + // warn the flooder + $msg = _pfc("Please don't post so many message, flood is not tolerated"); + $xml_reponse->script("alert('".addslashes($msg)."');"); + + // kick the flooder + $cmdp = $p; + $cmdp["param"] = null; + $cmdp["params"][0] = "ch"; + $cmdp["params"][1] = $u->channels[$recipientid]["name"]; + $cmdp["params"][2] .=_pfc("kicked from %s by %s", $u->channels[$recipientid]["name"], "noflood"); + $cmd =& pfcCommand::Factory("leave"); + $cmd->run($xml_reponse, $cmdp); + return false; + } + + if ($flood_nbmsg == 0) + $container->setUserMeta($nickid, 'floodtime', $floodtime); + $container->setUserMeta($nickid, 'flood_nbmsg', $flood_nbmsg); + $container->setUserMeta($nickid, 'flood_nbchar', $flood_nbchar); + } + + // forward the command to the next proxy or to the final command + $p["clientid"] = $clientid; + $p["param"] = $param; + $p["sender"] = $sender; + $p["recipient"] = $recipient; + $p["recipientid"] = $recipientid; + return $this->next->run($xml_reponse, $p); + } +} + +?> diff --git a/instmess/testcase/container_file.php b/instmess/testcase/container_file.php new file mode 100644 index 0000000..68643e0 --- /dev/null +++ b/instmess/testcase/container_file.php @@ -0,0 +1,188 @@ +type = "File"; + $this->pfcContainerTestcase($name); + } + + // called before the test functions will be executed + // this function is defined in PHPUnit_TestCase and overwritten + // here + function setUp() + { + pfcContainerTestcase::setUp(); + } + + // called after the test functions are executed + // this function is defined in PHPUnit_TestCase and overwritten + // here + function tearDown() + { + pfcContainerTestcase::tearDown(); + } + + function test_setMeta_File_1() + { + $c =& $this->c; + $ct =& $this->ct; + + $prefix = __FUNCTION__; + $group = $prefix."_nickid-to-channelid"; + $subgroup = $prefix."_nickid1"; + $leaf = $prefix."_channelid1"; + $ret = $ct->setMeta($group, $subgroup, $leaf); + $this->assertEquals($ret, 0, "the leaf should be first time created"); + + $f = $c->container_cfg_server_dir.'/'.$group.'/'.$subgroup.'/'.$leaf; + $ret = file_exists($f); + $this->assertEquals($ret, true, "the leaf file should exists"); + + $ret = file_get_contents($f); + $this->assertEquals($ret, '', "the leaf file should contain nothing"); + } + + function test_setMeta_File_2() + { + $c =& $this->c; + $ct =& $this->ct; + + $prefix = __FUNCTION__; + $group = $prefix."_nickid-to-channelid"; + $subgroup = $prefix."_nickid1"; + $leaf = $prefix."_channelid1"; + $leafvalue = $prefix."_leafvalue1"; + $ret = $ct->setMeta($group, $subgroup, $leaf, $leafvalue); + $this->assertEquals($ret, 0, "the leaf should be first time created"); + + $f = $c->container_cfg_server_dir.'/'.$group.'/'.$subgroup.'/'.$leaf; + $ret = file_exists($f); + $this->assertEquals($ret, true, "the leaf file should exists"); + + $ret = file_get_contents($f); + $this->assertEquals($ret, $leafvalue, "the leaf file should contain the value"); + } + + function test_setMeta_File_3() + { + $c =& $this->c; + $ct =& $this->ct; + + $prefix = __FUNCTION__; + $group = $prefix."_nickid-to-channelid"; + $subgroup = $prefix."_nickid1"; + $leaf = $prefix."_channelid1"; + $leafvalue = $prefix."_leafvalue1"; + + $ret = $ct->setMeta($group, $subgroup, $leaf, $leafvalue); + $this->assertEquals($ret, 0, "the leaf should be first time created"); + + $leafvalue = null; + $ret = $ct->setMeta($group, $subgroup, $leaf, $leafvalue); + $this->assertEquals($ret, 1, "the leaf should be overwritten"); + + $f = $c->container_cfg_server_dir.'/'.$group.'/'.$subgroup.'/'.$leaf; + $ret = file_exists($f); + $this->assertEquals($ret, true, "the leaf file should exists"); + + $ret = file_get_contents($f); + $this->assertEquals($ret, '', "the leaf file should contain nothing"); + } + + + function test_rmMeta_File_1() + { + $c =& $this->c; + $ct =& $this->ct; + + $prefix = __FUNCTION__; + $group = $prefix."_nickid-to-channelid"; + $subgroup = $prefix."_nickid1"; + $leaf = $prefix."_channelid1"; + $ret = $ct->setMeta($group, $subgroup, $leaf); + + $ret = $ct->rmMeta($group, $subgroup, $leaf); + $this->assertEquals($ret, true, "the returned value should be true (rm success)"); + + $f = $c->container_cfg_server_dir.'/'.$group.'/'.$subgroup.'/'.$leaf; + $ret = file_exists($f); + $this->assertEquals($ret, false, "the leaf file should not exists anymore"); + } + + function test_rmMeta_File_2() + { + $c =& $this->c; + $ct =& $this->ct; + + $prefix = __FUNCTION__; + $group = $prefix."_nickid-to-channelid"; + $subgroup = $prefix."_nickid1"; + $leaf1 = $prefix."_channelid1"; + $leaf2 = $prefix."_channelid2"; + $ret = $ct->setMeta($group, $subgroup, $leaf1); + $ret = $ct->setMeta($group, $subgroup, $leaf2); + + $ret = $ct->rmMeta($group, $subgroup); + $this->assertEquals($ret, true, "the returned value should be true (rm success)"); + + $f = $c->container_cfg_server_dir.'/'.$group.'/'.$subgroup.'/'.$leaf1; + $ret = file_exists($f); + $this->assertEquals($ret, false, "the leaf file should not exists anymore"); + $f = $c->container_cfg_server_dir.'/'.$group.'/'.$subgroup.'/'.$leaf2; + $ret = file_exists($f); + $this->assertEquals($ret, false, "the leaf file should not exists anymore"); + + $d = $c->container_cfg_server_dir.'/'.$group.'/'.$subgroup; + $ret = file_exists($f); + $this->assertEquals($ret, false, "the subgroup directory should not exists anymore"); + } + + function test_rmMeta_File_3() + { + $c =& $this->c; + $ct =& $this->ct; + + $prefix = __FUNCTION__; + $group = $prefix."_nickid-to-channelid"; + $subgroup = $prefix."_nickid1"; + $leaf1 = $prefix."_channelid1"; + $leaf2 = $prefix."_channelid2"; + $ret = $ct->setMeta($group, $subgroup, $leaf1); + $ret = $ct->setMeta($group, $subgroup, $leaf2); + + $ret = $ct->rmMeta($group); + $this->assertEquals($ret, true, "the returned value should be true (rm success)"); + + $f = $c->container_cfg_server_dir.'/'.$group.'/'.$subgroup.'/'.$leaf1; + $ret = file_exists($f); + $this->assertEquals($ret, false, "the leaf file should not exists anymore"); + $f = $c->container_cfg_server_dir.'/'.$group.'/'.$subgroup.'/'.$leaf2; + $ret = file_exists($f); + $this->assertEquals($ret, false, "the leaf file should not exists anymore"); + + $d = $c->container_cfg_server_dir.'/'.$group.'/'.$subgroup; + $ret = file_exists($d); + $this->assertEquals($ret, false, "the subgroup directory should not exists anymore"); + + $d = $c->container_cfg_server_dir.'/'.$group; + $ret = file_exists($d); + $this->assertEquals($ret, false, "the group directory should not exists anymore"); + } +} + +// on desactive le timeout car se script peut mettre bcp de temps a s'executer +ini_set('max_execution_time', 0); + +$suite = new PHPUnit_TestSuite(); +$suite->addTestSuite("pfcContainerTestcase_File"); +$result =& PHPUnit::run($suite); +echo "
";
+print_r($result->toString());
+echo "
"; + +?> \ No newline at end of file diff --git a/instmess/testcase/container_generic.php b/instmess/testcase/container_generic.php new file mode 100644 index 0000000..f1a66e7 --- /dev/null +++ b/instmess/testcase/container_generic.php @@ -0,0 +1,395 @@ +PHPUnit_TestCase($name); + } + + // called before the test functions will be executed + // this function is defined in PHPUnit_TestCase and overwritten + // here + function setUp() + { + // echo "setUp
"; + require_once dirname(__FILE__)."/../src/pfcglobalconfig.class.php"; + $params = array(); + $params["title"] = "testcase -> pfccontainer_".$this->type; + $params["serverid"] = md5(__FILE__ . time()); + $params["container_type"] = $this->type; + $this->c = pfcGlobalConfig::Instance($params); + $this->ct = pfcContainer::Instance(); + } + + // called after the test functions are executed + // this function is defined in PHPUnit_TestCase and overwritten + // here + function tearDown() + { + // echo "tearDown
"; + $this->ct->clear(); + $this->c->destroyCache(); + } + + function test_createNick_Generic() + { + $c =& $this->c; + $ct =& $this->ct; + $prefix = __FUNCTION__; + $nick = $prefix . '_' . $this->nick; + $nickid = $prefix . '_' . $this->nickid; + $chan = $prefix . '_' . $this->chan; + + $this->ct->createNick($nickid, $nick); + $nick2 = $this->ct->getNickname($nickid); + $nickid2 = $this->ct->getNickId($nick); + $this->assertEquals($nick, $nick2, "nicknames should be the same"); + $this->assertEquals($nickid, $nickid2, "nickids should be the same"); + } + + function test_removeNick_Generic() + { + $c =& $this->c; + $ct =& $this->ct; + $prefix = __FUNCTION__; + $nick = $prefix . '_' . $this->nick; + $nickid = $prefix . '_' . $this->nickid; + $chan = $prefix . '_' . $this->chan; + + // on the channel + $this->ct->createNick($nickid, $nick); + $this->ct->joinChan($nickid, $chan); + $this->ct->joinChan($nickid, NULL); + + $this->assertTrue($this->ct->isNickOnline($chan,$nickid),"should be online"); + $this->assertTrue($this->ct->isNickOnline(NULL,$nickid),"should be online"); + + $this->ct->removeNick($chan, $nickid); + $this->assertFalse($this->ct->isNickOnline($chan,$nickid),"should not be online"); + $this->assertTrue($this->ct->isNickOnline(NULL,$nickid),"should be online"); + + $this->ct->removeNick(NULL, $nickid); + $this->assertFalse($this->ct->isNickOnline($chan,$nickid),"should not be online"); + $this->assertFalse($this->ct->isNickOnline(NULL,$nickid),"should not be online"); + } + + function test_getOnlineNick_Generic() + { + $c =& $this->c; + $ct =& $this->ct; + $prefix = __FUNCTION__; + $nick = $prefix . '_' . $this->nick; + $nickid = $prefix . '_' . $this->nickid; + $chan = $prefix . '_' . $this->chan; + + $this->ct->createNick($nickid, $nick); + $this->ct->joinChan($nickid, NULL); + $this->ct->joinChan($nickid, $chan); + + $time = time(); + $ret = $this->ct->getOnlineNick($chan); + $this->assertEquals(1, count($ret["nickid"]), "1 nickname should be online"); + $this->assertEquals(1, count($ret["nick"]), "1 nickname should be online"); + $this->assertEquals(1, count($ret["timestamp"]), "1 nickname should be online"); + + $this->assertEquals($time, $ret["timestamp"][0], "nickname timestamp is wrong"); + $this->assertEquals($nick, $ret["nick"][0], "nickname value is wrong"); + $this->assertEquals($nickid, $ret["nickid"][0], "nickname id is wrong"); + } + + + function test_removeObsoleteNick_Generic() + { + $c =& $this->c; + $ct =& $this->ct; + $prefix = __FUNCTION__; + $nick = $prefix . '_' . $this->nick; + $nickid = $prefix . '_' . $this->nickid; + $chan = $prefix . '_' . $this->chan; + + $this->ct->createNick($nickid, $nick); + $this->ct->joinChan($nickid, NULL); + $this->ct->joinChan($nickid, $chan); + + sleep(2); + + $ret = $this->ct->removeObsoleteNick(1000); + $this->assertEquals(1, count($ret["nickid"]), "1 nickname should be obsolete"); + $this->assertEquals(2, count($ret["channels"][0]), "nickname should be disconnected from two channels"); + $isonline = $this->ct->isNickOnline($chan, $nickid); + $this->assertFalse($isonline, "nickname shouldn't be online anymore"); + } + + function test_updateNick_Generic() + { + $c =& $this->c; + $ct =& $this->ct; + $prefix = __FUNCTION__; + $nick = $prefix . '_' . $this->nick; + $nickid = $prefix . '_' . $this->nickid; + $chan = $prefix . '_' . $this->chan; + + $this->ct->createNick($nickid, $nick); + $this->ct->joinChan($nickid, NULL); + $this->ct->joinChan($nickid, $chan); + + sleep(2); + + $ret = $this->ct->updateNick($nickid); + $this->assertTrue($ret, "nickname should be correctly updated"); + + $ret = $this->ct->removeObsoleteNick(1000); + $this->assertFalse(in_array($nick, $ret['nick']), "nickname shouldn't be removed because it has been updated"); + $isonline = $this->ct->isNickOnline($chan, $nickid); + $this->assertTrue($isonline, "nickname should be online"); + } + + + function test_changeNick_Generic() + { + $c =& $this->c; + $ct =& $this->ct; + $prefix = __FUNCTION__; + $nick1 = $prefix . '_' . $this->nick; + $nick2 = $prefix . '_' . $this->nick.'2'; + $nickid = $prefix . '_' . $this->nickid; + $chan = $prefix . '_' . $this->chan; + + // create a nick on a channel and change it + $this->ct->createNick($nickid, $nick1); + $this->ct->joinChan($nickid, NULL); + $this->ct->joinChan($nickid, $chan); + + $ret = $this->ct->changeNick($nick2, $nick1); + $this->assertTrue($ret, "nickname change function should returns true (success)"); + $isonline1 = $this->ct->isNickOnline($chan, $this->ct->getNickId($nick1)); + $isonline2 = $this->ct->isNickOnline($chan, $this->ct->getNickId($nick2)); + $this->assertFalse($isonline1, "nickname shouldn't be online"); + $this->assertTrue($isonline2, "nickname shouldn't be online"); + } + + function test_getLastId_Generic() + { + $c =& $this->c; + $ct =& $this->ct; + $prefix = __FUNCTION__; + $nick = $prefix . '_' . $this->nick; + $nickid = $prefix . '_' . $this->nickid; + $chan = $prefix . '_' . $this->chan; + $cmd = "send"; + $msg = "my test message"; + + // on the channel + $this->ct->createNick($nickid, $nick); + $this->ct->joinChan($nickid, NULL); + $this->ct->joinChan($nickid, $chan); + for($i = 0; $i < 10; $i++) + { + $msgid = $this->ct->write($chan, $nick, $cmd ,$msg . $i); + $this->assertEquals($msgid, $i+1,"generated msg_id is not correct"); + } + $msgid = $this->ct->getLastId($chan); + $this->assertEquals(10, $msgid, "last msgid is not correct"); + } + + function test_write_Generic() + { + $c =& $this->c; + $ct =& $this->ct; + $prefix = __FUNCTION__; + $nick = $prefix . '_' . $this->nick; + $nickid = $prefix . '_' . $this->nickid; + $chan = $prefix . '_' . $this->chan; + $cmd = "send"; + $msg = "my test message"; + + // create message on the channel + $this->ct->createNick($nickid, $nick); + $this->ct->joinChan($nickid, NULL); + $this->ct->joinChan($nickid, $chan); + $msgid = $this->ct->write($chan, $nick, $cmd, $msg); + $this->assertEquals(1, $msgid,"generated msg_id is not correct"); + $res = $this->ct->read($chan, 0); + $this->assertEquals(1, count($res["data"]), "1 messages should be read"); + $this->assertEquals($msg, $res["data"][1]["param"] ,"messages data is not the same as the sent one"); + $this->assertEquals(1, $res["new_from_id"],"new_from_id is not correct"); + } + + function test_read_Generic() + { + $c =& $this->c; + $ct =& $this->ct; + $prefix = __FUNCTION__; + $nick = $prefix . '_' . $this->nick; + $nickid = $prefix . '_' . $this->nickid; + $chan = $prefix . '_' . $this->chan; + $cmd = "send"; + $msg = "my test message"; + + // create on the channel + $this->ct->createNick($nickid, $nick); + $this->ct->joinChan($nickid, NULL); + $this->ct->joinChan($nickid, $chan); + for($i = 0; $i < 10; $i++) + { + $msgid = $this->ct->write($chan, $nick, $cmd ,$msg . $i); + $this->assertEquals($msgid, $i+1, "generated msg_id is not correct"); + } + + $res = $this->ct->read($chan, 0); + $this->assertEquals(10, count($res["data"]), "10 messages should be read"); + $this->assertEquals($msg."0", $res["data"][1]["param"] ,"messages data is not the same as the sent one"); + $this->assertEquals($msg."8", $res["data"][9]["param"] ,"messages data is not the same as the sent one"); + $this->assertEquals($res["new_from_id"], 10 ,"new_from_id is not correct"); + + $res = $this->ct->read($chan, 5); + $this->assertEquals(5, count($res["data"]), "5 messages should be read"); + $this->assertEquals($msg."5", $res["data"][6]["param"] ,"messages data is not the same as the sent one"); + $this->assertEquals($msg."9", $res["data"][10]["param"] ,"messages data is not the same as the sent one"); + $this->assertEquals($res["new_from_id"], 10 ,"new_from_id is not correct"); + } + + function test_encodedecode_Generic() + { + $c =& $this->c; + $ct =& $this->ct; + + $string = "il était une fois C;h:!?§+ toto=}at是"; + + $prefix = __FUNCTION__; + $group = $prefix."_nickid-to-channelid"; + $subgroup = $prefix."_nickid1"; + $leaf = $prefix."_".$ct->encode($string); + $leafvalue = $string; + $ct->setMeta($group, $subgroup, $leaf, $leafvalue); + + $ret = $ct->getMeta($group, $subgroup); + $this->assertEquals($ret['value'][0], $leaf, "the leaf name is wrong"); + $ret = $ct->getMeta($group, $subgroup, $leaf, true); + $this->assertEquals($ret['value'][0], $leafvalue, "the leaf value is wrong"); + } + + function test_getMeta_Generic_1() + { + $c =& $this->c; + $ct =& $this->ct; + + $prefix = __FUNCTION__; + $group = $prefix."_nickid-to-channelid"; + $subgroup = $prefix."_nickid1"; + $leaf = $prefix."_channelid1"; + $ct->setMeta($group, $subgroup, $leaf); + $time = time(); + + $ret = $ct->getMeta($group, $subgroup, $leaf); + $this->assertEquals(count($ret["timestamp"]), 1, "number of leaf is wrong"); + $this->assertEquals($ret["timestamp"][0], $time, "the leaf timestamp is wrong"); + $this->assertEquals($ret["value"][0], null, "the leaf value is wrong"); + + $ret = $ct->getMeta($group, $subgroup); + $this->assertEquals(count($ret["timestamp"]), 1, "number of leaf is wrong"); + $this->assertEquals($ret["timestamp"][0], $time, "the leaf timestamp is wrong"); + $this->assertEquals($ret["value"][0], $leaf, "the leaf name is wrong"); + + $leafvalue = $prefix."_leafvalue"; + $ct->setMeta($group, $subgroup, $leaf, $leafvalue); + $time = time(); + + $ret = $ct->getMeta($group, $subgroup, $leaf, true); + $this->assertEquals(count($ret["timestamp"]), 1, "number of leaf is wrong"); + $this->assertEquals($ret["timestamp"][0], $time, "the leaf timestamp is wrong"); + $this->assertEquals($ret["value"][0], $leafvalue, "the leaf value is wrong"); + } + + function test_getMeta_Generic_2() + { + $c =& $this->c; + $ct =& $this->ct; + + $prefix = __FUNCTION__; + $group = $prefix."_nickid-to-channelid"; + $subgroup = $prefix."_nickid1"; + $leaf1 = $prefix."_channelid1"; + $leaf2 = $prefix."_channelid2"; + $ct->setMeta($group, $subgroup, $leaf1); + $ct->setMeta($group, $subgroup, $leaf2); + $time = time(); + + $ret = $ct->getMeta($group, $subgroup); + sort($ret["value"]); + $this->assertEquals(count($ret["timestamp"]), 2, "number of leaf is wrong"); + $this->assertEquals($ret["timestamp"][0], $time, "the leaf timestamp is wrong"); + $this->assertEquals($ret["timestamp"][1], $time, "the leaf timestamp is wrong"); + $this->assertEquals($ret["value"][0], $leaf1, "the leaf name is wrong"); + $this->assertEquals($ret["value"][1], $leaf2, "the leaf name is wrong"); + } + + function test_getMeta_Generic_3() + { + $c =& $this->c; + $ct =& $this->ct; + + $prefix = __FUNCTION__; + $group = $prefix."_nickid-to-channelid"; + $subgroup1 = $prefix."_nickid1"; + $subgroup2 = $prefix."_nickid2"; + $leaf1 = $prefix."_channelid1"; + $leaf2 = $prefix."_channelid2"; + $ct->setMeta($group, $subgroup1, $leaf1); + $ct->setMeta($group, $subgroup1, $leaf2); + $ct->setMeta($group, $subgroup2, $leaf1); + $ct->setMeta($group, $subgroup2, $leaf2); + $time = time(); + + $ret = $ct->getMeta($group); + sort($ret["value"]); + $this->assertEquals(2, count($ret["timestamp"]), "number of subgroup is wrong"); + $this->assertEquals($time, $ret["timestamp"][0], "the subgroup timestamp is wrong"); + $this->assertEquals($time, $ret["timestamp"][1], "the subgroup timestamp is wrong"); + $this->assertEquals($subgroup1, $ret["value"][0], "the subgroup name is wrong"); + $this->assertEquals($subgroup2, $ret["value"][1], "the subgroup name is wrong"); + } + + function test_rmMeta_Generic() + { + $c =& $this->c; + $ct =& $this->ct; + + $prefix = __FUNCTION__; + $group = $prefix."_nickid-to-channelid"; + $subgroup1 = $prefix."_nickid1"; + $subgroup2 = $prefix."_nickid2"; + $leaf1 = $prefix."_channelid1"; + $leaf2 = $prefix."_channelid2"; + $ct->setMeta($group, $subgroup1, $leaf1); + $ct->setMeta($group, $subgroup1, $leaf2); + + $ret = $ct->getMeta($group,$subgroup1); + $ret = $ct->getMeta($group,$subgroup1,$leaf1); + + $ct->rmMeta($group, $subgroup1, $leaf1); + + $ret = $ct->getMeta($group,$subgroup1); + $this->assertEquals(1, count($ret["value"]), "number of leaf is wrong"); + $ret = $ct->getMeta($group,$subgroup1,$leaf1); + $this->assertEquals(0, count($ret["value"]), "leaf should not exists"); + } +} + +?> diff --git a/instmess/testcase/container_mysql.php b/instmess/testcase/container_mysql.php new file mode 100644 index 0000000..23241fb --- /dev/null +++ b/instmess/testcase/container_mysql.php @@ -0,0 +1,41 @@ +type = "Mysql"; + $this->pfcContainerTestcase($name); + } + + // called before the test functions will be executed + // this function is defined in PHPUnit_TestCase and overwritten + // here + function setUp() + { + pfcContainerTestcase::setUp(); + } + + // called after the test functions are executed + // this function is defined in PHPUnit_TestCase and overwritten + // here + function tearDown() + { + pfcContainerTestcase::tearDown(); + } +} + +// on desactive le timeout car se script peut mettre bcp de temps a s'executer +ini_set('max_execution_time', 0); + +$suite = new PHPUnit_TestSuite(); +$suite->addTestSuite("pfcContainerTestcase_Mysql"); +$result =& PHPUnit::run($suite); +echo "
";
+print_r($result->toString());
+echo "
"; + +?> \ No newline at end of file diff --git a/instmess/testcase/ctype.php b/instmess/testcase/ctype.php new file mode 100644 index 0000000..5e4fe76 --- /dev/null +++ b/instmess/testcase/ctype.php @@ -0,0 +1,47 @@ +"; +foreach ($test as $a) +{ + echo $a . " : " . ((my_ctype_space($a)) ? "true" : "false") ." : " . ((ctype_space($a)) ? "true" : "false") ."
"; +} + + +echo "ctype_xdigit()"."
"; +foreach ($test as $a) +{ + echo $a . " : " . ((my_ctype_xdigit($a)) ? "true" : "false"). " : " . ((ctype_xdigit($a)) ? "true" : "false") ."
"; +} + +echo "ctype_alpha()"."
"; +foreach ($test as $a) +{ + echo $a . " : " . ((my_ctype_alpha($a)) ? "true" : "false") ." : " . ((ctype_alpha($a)) ? "true" : "false") ."
"; +} + +?> \ No newline at end of file diff --git a/instmess/testcase/filemtime.php b/instmess/testcase/filemtime.php new file mode 100644 index 0000000..e5075f2 --- /dev/null +++ b/instmess/testcase/filemtime.php @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/instmess/testcase/parsecommand.php b/instmess/testcase/parsecommand.php new file mode 100644 index 0000000..fddc4d9 --- /dev/null +++ b/instmess/testcase/parsecommand.php @@ -0,0 +1,62 @@ + '/cmdname clientid recipientid', + 'cmdname' => 'cmdname', + 'params' => array('clientid', 'recipientid')); +$results[] = array('cmdstr' => '/cmdname clientid recipientid param1 param2', + 'cmdname' => 'cmdname', + 'params' => array('clientid', 'recipientid', 'param1','param2')); +$results[] = array('cmdstr' => '/cmdname clientid recipientid param1 param2 param3', + 'cmdname' => 'cmdname', + 'params' => array('clientid', 'recipientid', 'param1','param2','param3')); +$results[] = array('cmdstr' => '/cmdname clientid recipientid "param1" "param2"', + 'cmdname' => 'cmdname', + 'params' => array('clientid', 'recipientid', 'param1','param2')); +$results[] = array('cmdstr' => '/cmdname clientid recipientid "param1" "param2" "param3"', + 'cmdname' => 'cmdname', + 'params' => array('clientid', 'recipientid', 'param1','param2','param3')); +$results[] = array('cmdstr' => '/cmdname clientid recipientid "param1 with spaces" "param2 with spaces"', + 'cmdname' => 'cmdname', + 'params' => array('clientid', 'recipientid', 'param1 with spaces','param2 with spaces')); +$results[] = array('cmdstr' => '/cmdname000 clientid recipientid "param1" "param2"', + 'cmdname' => 'cmdname000', + 'params' => array('clientid', 'recipientid', 'param1','param2')); +$results[] = array('cmdstr' => '/cmdname clientid recipientid param1 param2', + 'cmdname' => 'cmdname', + 'params' => array('clientid', 'recipientid', 'param1','param2')); +$results[] = array('cmdstr' => '/cmdname clientid recipientid "param1 with spaces" param2 param3', + 'cmdname' => 'cmdname', + 'params' => array('clientid', 'recipientid', 'param1 with spaces','param2', 'param3')); +$results[] = array('cmdstr' => '/cmdname clientid recipientid "param1" param2 "param3 with spaces" param4', + 'cmdname' => 'cmdname', + 'params' => array('clientid', 'recipientid', 'param1', 'param2', 'param3 with spaces', 'param4')); +$results[] = array('cmdstr' => '/cmdname clientid recipientid "param1""param2"', + 'cmdname' => 'cmdname', + 'params' => array('clientid', 'recipientid', 'param1', 'param2')); +$results[] = array('cmdstr' => '/cmdname clientid recipientid "param1withoutspace"', + 'cmdname' => 'cmdname', + 'params' => array('clientid', 'recipientid', 'param1withoutspace')); +$results[] = array('cmdstr' => '/send clientid recipientid my sentance " with double " quotes', + 'cmdname' => 'send', + 'params' => array('clientid', 'recipientid', 'my sentance " with double " quotes')); + +echo '
';
+for($i = 0; $i $command\n";
+  else
+  {
+    print_r($result);
+    echo "KO => $command\n";
+  }
+}
+echo '
'; + +?> \ No newline at end of file diff --git a/instmess/themes/default/chat.html.tpl.php b/instmess/themes/default/chat.html.tpl.php new file mode 100644 index 0000000..627377b --- /dev/null +++ b/instmess/themes/default/chat.html.tpl.php @@ -0,0 +1,167 @@ + +

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

    + >nick); ?>

    +
    + " + maxlength=""/> + + " + title="" + onclick="pfc.doSendMessage()"/> +
    + +
    + + + + + + "> + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + + btn_sh_smileys) { ?> +
    + +
    + + + btn_sh_whosonline) { ?> +
    + +
    + + +
    + +
    +
    + <?php echo _pfc(" + title="" + class="pfc_bt_strong" + onclick="pfc.insert_text('[b]','[/b]',true)" /> +
    +
    + <?php echo _pfc(" + title="" + class="pfc_bt_italics" + onclick="pfc.insert_text('[i]','[/i]',true)" /> +
    +
    + <?php echo _pfc(" + title="" + class="pfc_bt_underline" + onclick="pfc.insert_text('[u]','[/u]',true)" /> +
    +
    + <?php echo _pfc(" + title="" + class="pfc_bt_delete" + onclick="pfc.insert_text('[s]','[/s]',true)" /> +
    + +
    + <?php echo _pfc(" + title="" + id="pfc_bt_color" + class="pfc_bt_color" + onclick="pfc.minimize_maximize('pfc_colorlist','inline')" /> +
    +
    +
    + +
    + +
    + +
    + +
    + +
    diff --git a/instmess/themes/default/chat.js.tpl.php b/instmess/themes/default/chat.js.tpl.php new file mode 100644 index 0000000..a74eea4 --- /dev/null +++ b/instmess/themes/default/chat.js.tpl.php @@ -0,0 +1,129 @@ +
    + +
    +

    +
    +

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

    +PHP FREE CHAT [powered by phpFreeChat-<?php echo $version ?>] +
    + +
    + +
    + + +
    + diff --git a/instmess/themes/default/customize.js.php b/instmess/themes/default/customize.js.php new file mode 100644 index 0000000..12e32ad --- /dev/null +++ b/instmess/themes/default/customize.js.php @@ -0,0 +1,6 @@ +/** + * Put here the pfcClient, pfcGui, pfcResources customizations + * ex: you can override the pfcClient::updateNickList methode + * in order to display links on the nicknames (see demo34 for a concrete example) + */ + diff --git a/instmess/themes/default/iepngfix.htc b/instmess/themes/default/iepngfix.htc new file mode 100644 index 0000000..791348f --- /dev/null +++ b/instmess/themes/default/iepngfix.htc @@ -0,0 +1,71 @@ + + + + + \ No newline at end of file diff --git a/instmess/themes/default/images/background.gif b/instmess/themes/default/images/background.gif new file mode 100644 index 0000000..aeeb2e3 Binary files /dev/null and b/instmess/themes/default/images/background.gif differ diff --git a/instmess/themes/default/images/blank.gif b/instmess/themes/default/images/blank.gif new file mode 100644 index 0000000..75b945d Binary files /dev/null and b/instmess/themes/default/images/blank.gif differ diff --git a/instmess/themes/default/images/bt_color.gif b/instmess/themes/default/images/bt_color.gif new file mode 100644 index 0000000..c6115c5 Binary files /dev/null and b/instmess/themes/default/images/bt_color.gif differ diff --git a/instmess/themes/default/images/bt_del.gif b/instmess/themes/default/images/bt_del.gif new file mode 100644 index 0000000..bd72c08 Binary files /dev/null and b/instmess/themes/default/images/bt_del.gif differ diff --git a/instmess/themes/default/images/bt_em.gif b/instmess/themes/default/images/bt_em.gif new file mode 100644 index 0000000..429801b Binary files /dev/null and b/instmess/themes/default/images/bt_em.gif differ diff --git a/instmess/themes/default/images/bt_ins.gif b/instmess/themes/default/images/bt_ins.gif new file mode 100644 index 0000000..bd8f91e Binary files /dev/null and b/instmess/themes/default/images/bt_ins.gif differ diff --git a/instmess/themes/default/images/bt_mail.gif b/instmess/themes/default/images/bt_mail.gif new file mode 100644 index 0000000..d0a437b Binary files /dev/null and b/instmess/themes/default/images/bt_mail.gif differ diff --git a/instmess/themes/default/images/bt_pre.gif b/instmess/themes/default/images/bt_pre.gif new file mode 100644 index 0000000..24e97d6 Binary files /dev/null and b/instmess/themes/default/images/bt_pre.gif differ diff --git a/instmess/themes/default/images/bt_strong.gif b/instmess/themes/default/images/bt_strong.gif new file mode 100644 index 0000000..403aba2 Binary files /dev/null and b/instmess/themes/default/images/bt_strong.gif differ diff --git a/instmess/themes/default/images/ch-active.gif b/instmess/themes/default/images/ch-active.gif new file mode 100644 index 0000000..df7158a Binary files /dev/null and b/instmess/themes/default/images/ch-active.gif differ diff --git a/instmess/themes/default/images/ch.gif b/instmess/themes/default/images/ch.gif new file mode 100644 index 0000000..897cf2e Binary files /dev/null and b/instmess/themes/default/images/ch.gif differ diff --git a/instmess/themes/default/images/clock-off.gif b/instmess/themes/default/images/clock-off.gif new file mode 100644 index 0000000..e9de050 Binary files /dev/null and b/instmess/themes/default/images/clock-off.gif differ diff --git a/instmess/themes/default/images/clock-on.gif b/instmess/themes/default/images/clock-on.gif new file mode 100644 index 0000000..6ac6ebc Binary files /dev/null and b/instmess/themes/default/images/clock-on.gif differ diff --git a/instmess/themes/default/images/close-whoisbox.gif b/instmess/themes/default/images/close-whoisbox.gif new file mode 100644 index 0000000..444d409 Binary files /dev/null and b/instmess/themes/default/images/close-whoisbox.gif differ diff --git a/instmess/themes/default/images/color-off.gif b/instmess/themes/default/images/color-off.gif new file mode 100644 index 0000000..b36c8a3 Binary files /dev/null and b/instmess/themes/default/images/color-off.gif differ diff --git a/instmess/themes/default/images/color-on.gif b/instmess/themes/default/images/color-on.gif new file mode 100644 index 0000000..bde079f Binary files /dev/null and b/instmess/themes/default/images/color-on.gif differ diff --git a/instmess/themes/default/images/color_transparent.gif b/instmess/themes/default/images/color_transparent.gif new file mode 100644 index 0000000..0fb0d1c Binary files /dev/null and b/instmess/themes/default/images/color_transparent.gif differ diff --git a/instmess/themes/default/images/login.gif b/instmess/themes/default/images/login.gif new file mode 100644 index 0000000..355c1a9 Binary files /dev/null and b/instmess/themes/default/images/login.gif differ diff --git a/instmess/themes/default/images/logout.gif b/instmess/themes/default/images/logout.gif new file mode 100644 index 0000000..e5ed08c Binary files /dev/null and b/instmess/themes/default/images/logout.gif differ diff --git a/instmess/themes/default/images/maximize.gif b/instmess/themes/default/images/maximize.gif new file mode 100644 index 0000000..de84c18 Binary files /dev/null and b/instmess/themes/default/images/maximize.gif differ diff --git a/instmess/themes/default/images/minimize.gif b/instmess/themes/default/images/minimize.gif new file mode 100644 index 0000000..ed4e499 Binary files /dev/null and b/instmess/themes/default/images/minimize.gif differ diff --git a/instmess/themes/default/images/newmsg.gif b/instmess/themes/default/images/newmsg.gif new file mode 100644 index 0000000..72d41f2 Binary files /dev/null and b/instmess/themes/default/images/newmsg.gif differ diff --git a/instmess/themes/default/images/oldmsg.gif b/instmess/themes/default/images/oldmsg.gif new file mode 100644 index 0000000..dc769f5 Binary files /dev/null and b/instmess/themes/default/images/oldmsg.gif differ diff --git a/instmess/themes/default/images/online-off.gif b/instmess/themes/default/images/online-off.gif new file mode 100644 index 0000000..185cebf Binary files /dev/null and b/instmess/themes/default/images/online-off.gif differ diff --git a/instmess/themes/default/images/online-on.gif b/instmess/themes/default/images/online-on.gif new file mode 100644 index 0000000..12cde0b Binary files /dev/null and b/instmess/themes/default/images/online-on.gif differ diff --git a/instmess/themes/default/images/online-separator.gif b/instmess/themes/default/images/online-separator.gif new file mode 100644 index 0000000..049482b Binary files /dev/null and b/instmess/themes/default/images/online-separator.gif differ diff --git a/instmess/themes/default/images/openpv.gif b/instmess/themes/default/images/openpv.gif new file mode 100644 index 0000000..aa7e9d3 Binary files /dev/null and b/instmess/themes/default/images/openpv.gif differ diff --git a/instmess/themes/default/images/pv-active.gif b/instmess/themes/default/images/pv-active.gif new file mode 100644 index 0000000..d95cd6e Binary files /dev/null and b/instmess/themes/default/images/pv-active.gif differ diff --git a/instmess/themes/default/images/pv.gif b/instmess/themes/default/images/pv.gif new file mode 100644 index 0000000..407cfe6 Binary files /dev/null and b/instmess/themes/default/images/pv.gif differ diff --git a/instmess/themes/default/images/smiley-off.gif b/instmess/themes/default/images/smiley-off.gif new file mode 100644 index 0000000..08fe602 Binary files /dev/null and b/instmess/themes/default/images/smiley-off.gif differ diff --git a/instmess/themes/default/images/smiley-on.gif b/instmess/themes/default/images/smiley-on.gif new file mode 100644 index 0000000..ad577bf Binary files /dev/null and b/instmess/themes/default/images/smiley-on.gif differ diff --git a/instmess/themes/default/images/sound-off.gif b/instmess/themes/default/images/sound-off.gif new file mode 100644 index 0000000..c4fe8b0 Binary files /dev/null and b/instmess/themes/default/images/sound-off.gif differ diff --git a/instmess/themes/default/images/sound-on.gif b/instmess/themes/default/images/sound-on.gif new file mode 100644 index 0000000..62494e4 Binary files /dev/null and b/instmess/themes/default/images/sound-on.gif differ diff --git a/instmess/themes/default/images/tab_remove.gif b/instmess/themes/default/images/tab_remove.gif new file mode 100644 index 0000000..8b1a97a Binary files /dev/null and b/instmess/themes/default/images/tab_remove.gif differ diff --git a/instmess/themes/default/images/user-admin.gif b/instmess/themes/default/images/user-admin.gif new file mode 100644 index 0000000..929284d Binary files /dev/null and b/instmess/themes/default/images/user-admin.gif differ diff --git a/instmess/themes/default/images/user-me.gif b/instmess/themes/default/images/user-me.gif new file mode 100644 index 0000000..22fbab4 Binary files /dev/null and b/instmess/themes/default/images/user-me.gif differ diff --git a/instmess/themes/default/images/user.gif b/instmess/themes/default/images/user.gif new file mode 100644 index 0000000..dcb5c2a Binary files /dev/null and b/instmess/themes/default/images/user.gif differ diff --git a/instmess/themes/default/images/user_female-me.gif b/instmess/themes/default/images/user_female-me.gif new file mode 100644 index 0000000..9928657 Binary files /dev/null and b/instmess/themes/default/images/user_female-me.gif differ diff --git a/instmess/themes/default/images/user_female.gif b/instmess/themes/default/images/user_female.gif new file mode 100644 index 0000000..ffa195e Binary files /dev/null and b/instmess/themes/default/images/user_female.gif differ diff --git a/instmess/themes/default/info.php b/instmess/themes/default/info.php new file mode 100644 index 0000000..6af57d0 --- /dev/null +++ b/instmess/themes/default/info.php @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/instmess/themes/default/smileys/arrow_left.png b/instmess/themes/default/smileys/arrow_left.png new file mode 100644 index 0000000..55adf70 Binary files /dev/null and b/instmess/themes/default/smileys/arrow_left.png differ diff --git a/instmess/themes/default/smileys/arrow_right.png b/instmess/themes/default/smileys/arrow_right.png new file mode 100644 index 0000000..dcc6db7 Binary files /dev/null and b/instmess/themes/default/smileys/arrow_right.png differ diff --git a/instmess/themes/default/smileys/emoticon_evilgrin.png b/instmess/themes/default/smileys/emoticon_evilgrin.png new file mode 100644 index 0000000..538b861 Binary files /dev/null and b/instmess/themes/default/smileys/emoticon_evilgrin.png differ diff --git a/instmess/themes/default/smileys/emoticon_grin.png b/instmess/themes/default/smileys/emoticon_grin.png new file mode 100644 index 0000000..a1c19d4 Binary files /dev/null and b/instmess/themes/default/smileys/emoticon_grin.png differ diff --git a/instmess/themes/default/smileys/emoticon_happy.png b/instmess/themes/default/smileys/emoticon_happy.png new file mode 100644 index 0000000..9830a74 Binary files /dev/null and b/instmess/themes/default/smileys/emoticon_happy.png differ diff --git a/instmess/themes/default/smileys/emoticon_smile.png b/instmess/themes/default/smileys/emoticon_smile.png new file mode 100644 index 0000000..224c2b9 Binary files /dev/null and b/instmess/themes/default/smileys/emoticon_smile.png differ diff --git a/instmess/themes/default/smileys/emoticon_surprised.png b/instmess/themes/default/smileys/emoticon_surprised.png new file mode 100644 index 0000000..d71899a Binary files /dev/null and b/instmess/themes/default/smileys/emoticon_surprised.png differ diff --git a/instmess/themes/default/smileys/emoticon_tongue.png b/instmess/themes/default/smileys/emoticon_tongue.png new file mode 100644 index 0000000..062b740 Binary files /dev/null and b/instmess/themes/default/smileys/emoticon_tongue.png differ diff --git a/instmess/themes/default/smileys/emoticon_unhappy.png b/instmess/themes/default/smileys/emoticon_unhappy.png new file mode 100644 index 0000000..6dea97a Binary files /dev/null and b/instmess/themes/default/smileys/emoticon_unhappy.png differ diff --git a/instmess/themes/default/smileys/emoticon_waii.png b/instmess/themes/default/smileys/emoticon_waii.png new file mode 100644 index 0000000..7c247a0 Binary files /dev/null and b/instmess/themes/default/smileys/emoticon_waii.png differ diff --git a/instmess/themes/default/smileys/emoticon_wink.png b/instmess/themes/default/smileys/emoticon_wink.png new file mode 100644 index 0000000..2cac936 Binary files /dev/null and b/instmess/themes/default/smileys/emoticon_wink.png differ diff --git a/instmess/themes/default/smileys/exclamation.png b/instmess/themes/default/smileys/exclamation.png new file mode 100644 index 0000000..7c2fc3e Binary files /dev/null and b/instmess/themes/default/smileys/exclamation.png differ diff --git a/instmess/themes/default/smileys/lightbulb.png b/instmess/themes/default/smileys/lightbulb.png new file mode 100644 index 0000000..e4ef4f3 Binary files /dev/null and b/instmess/themes/default/smileys/lightbulb.png differ diff --git a/instmess/themes/default/smileys/theme.txt b/instmess/themes/default/smileys/theme.txt new file mode 100644 index 0000000..95a91ad --- /dev/null +++ b/instmess/themes/default/smileys/theme.txt @@ -0,0 +1,27 @@ +# This theme set based on the icons at +# http://www.famfamfam.com/lab/icons/silk/ +# +# Ported to PHPFreeChat by Robin Monks +# +# These icons are under a Creative Commons Attribution 2.5 +# License http://creativecommons.org/licenses/by/2.5/ + +emoticon_smile.png :-) ^_^ :) +emoticon_evilgrin.png >( +emoticon_surprised.png :S :s :-S :-s :-/ +emoticon_grin.png :-D :D +emoticon_unhappy.png :'( :-( :o( :-< :( +emoticon_happy.png :lol: +emoticon_waii.png :{} :-{} :razz: :} :-} +emoticon_wink.png ;-) ;o) ;) +emoticon_tongue.png :P :-P :-p :p +weather_rain.png /// \\\ ||| :rain: :drizzle: +weather_snow.png :***: +weather_sun.png >O< +weather_clouds.png :""": :cloud: :clouds: +weather_cloudy.png :"O": :cloudly: +weather_lightning.png :$: +arrow_right.png => -> --> ==> >>> +arrow_left.png <= <- <-- <== <<< +exclamation.png :!: +lightbulb.png *) 0= diff --git a/instmess/themes/default/smileys/weather_clouds.png b/instmess/themes/default/smileys/weather_clouds.png new file mode 100644 index 0000000..a606797 Binary files /dev/null and b/instmess/themes/default/smileys/weather_clouds.png differ diff --git a/instmess/themes/default/smileys/weather_cloudy.png b/instmess/themes/default/smileys/weather_cloudy.png new file mode 100644 index 0000000..e99d3db Binary files /dev/null and b/instmess/themes/default/smileys/weather_cloudy.png differ diff --git a/instmess/themes/default/smileys/weather_lightning.png b/instmess/themes/default/smileys/weather_lightning.png new file mode 100644 index 0000000..b5d32dd Binary files /dev/null and b/instmess/themes/default/smileys/weather_lightning.png differ diff --git a/instmess/themes/default/smileys/weather_rain.png b/instmess/themes/default/smileys/weather_rain.png new file mode 100644 index 0000000..2008953 Binary files /dev/null and b/instmess/themes/default/smileys/weather_rain.png differ diff --git a/instmess/themes/default/smileys/weather_snow.png b/instmess/themes/default/smileys/weather_snow.png new file mode 100644 index 0000000..769fc31 Binary files /dev/null and b/instmess/themes/default/smileys/weather_snow.png differ diff --git a/instmess/themes/default/smileys/weather_sun.png b/instmess/themes/default/smileys/weather_sun.png new file mode 100644 index 0000000..bb4f307 Binary files /dev/null and b/instmess/themes/default/smileys/weather_sun.png differ diff --git a/instmess/themes/default/sound.swf b/instmess/themes/default/sound.swf new file mode 100644 index 0000000..a28e305 Binary files /dev/null and b/instmess/themes/default/sound.swf differ diff --git a/instmess/themes/default/style.css.php b/instmess/themes/default/style.css.php new file mode 100644 index 0000000..60fa9fa --- /dev/null +++ b/instmess/themes/default/style.css.php @@ -0,0 +1,446 @@ +/* used to enable png transparency for IE6 */ +div#pfc_container img, div#pfc_container div { + behavior: url("getFileUrlFromTheme('iepngfix.htc'); ?>"); +} + +div#pfc_container { + margin: 0; padding: 0; + border: 1px solid #555; + color: #748DE7; + padding: 10px; + min-height: 20px; + background-color: #000000; +/* background-image: url("getFileUrlFromTheme('images/background.gif'); ?>"); + background-position: right; + background-repeat: repeat-xy;*/ + font-family: Verdana, Sans-Serif; /* without this rule, the tabs are not correctly display on FF */ +} + +div#pfc_container a img { border: 0px; } + +#pfc_minmax { + margin: 0; padding: 0; + cursor: pointer; +} +div#pfc_content_expandable { + margin: 0; padding: 0; + margin-top: 0.2em; +} + +div#pfc_channels_content { + margin: 0; padding: 0; + z-index: 20; + position: relative; + width: 100%; + border-right: 1px solid #555; + border-left: 1px solid #555; + border-bottom: 1px solid #555; + background-color: #000000; + height: height!=''?$c->height:'300px'); ?>; +} +div.pfc_content { + margin: 0; padding: 0; +} + +/* channels tabpanes */ +ul#pfc_channels_list { + margin: 0; padding: 0; + list-style-type: none; + display: block; + z-index: 50; + border-bottom: 1px solid #555; + /* margin-bottom: -5px;*/ + line-height: 100%; +} +ul#pfc_channels_list li { + margin: 0; padding: 0; + display: inline; + margin-left: 5px; +} +ul#pfc_channels_list li img { + margin: 0; padding: 0; + vertical-align: bottom; +} +ul#pfc_channels_list li div { + margin: 0; padding: 0; + display: inline; + padding: 0 4px 0 4px; + border-top: 1px solid #555; + border-right: 1px solid #555; + border-left: 1px solid #555; + border-bottom: 1px solid #555; + background-color: #DDD; + vertical-align: bottom; +} +ul#pfc_channels_list li.selected div { + background-color: #000000; + border-bottom: 1px solid #000000; + color: #748DE7; + font-weight: bold; +} +/* this rule does not work on ie6 ( :hover ) */ +ul#pfc_channels_list li div:hover { + background-color: #000000; +} +ul#pfc_channels_list li a { + margin: 0; padding: 0; + color: #748DE7; + text-decoration: none; +} +ul#pfc_channels_list li a.pfc_tabtitle { + cursor: pointer; +} +ul#pfc_channels_list li a.pfc_tabtitle img { + padding-right: 4px; +} +ul#pfc_channels_list li a.pfc_tabclose { + margin-left: 4px; + cursor: pointer; +} +/* blinking stuff (tab notifications) */ +ul#pfc_channels_list li div.pfc_tabblink2 { + background-color: #000000; +} + + +div.pfc_chat { + margin: 0; padding: 0; + z-index: 100; + position: absolute; + top: 0; + left: 0; + width: 80%; +/* WARNING: do not fix height in % because it will display blank screens on IE6 */ +/* height: 100%;*/ + overflow: auto; + word-wrap: break-word; +} +div.pfc_chat div { + margin: 0; padding: 0; border: none; +} + +div.pfc_online { + margin: 0; padding: 0; + position: absolute; + right: 0; + top: 0; + overflow: auto; + width: 20%; +/* WARNING: do not fix height in % because it will display blank screens on IE6 */ +/* height: 100%;*/ + color: #748DE7; /* colors can be overriden by js nickname colorization */ + background-color: #000000; + + /* borders are drawn by this image background */ + background-image: url("getFileUrlFromTheme('images/online-separator.gif'); ?>"); + background-position: left; + background-repeat: repeat-y; +} +div.pfc_online ul { + margin: 4px; padding: 0; + list-style-type: none; + font-size: 90%; + font-weight: bold; +} +ul.pfc_nicklist li { + margin: 0 0 5px 0; padding: 0; + border-bottom: 1px solid #AAA; + background-image: none; +} +ul.pfc_nicklist img { + vertical-align: middle; /* fix icon position problem in IE6 */ +} +ul.pfc_nicklist a { + text-decoration: none; +} +ul.pfc_nicklist nobr span { + margin: 0; padding: 0; + display: inline; + text-decoration: none; +} + + + + +h2#pfc_title { + margin:0; padding:0; border: none; + font-size: 110%; +} + +img#pfc_minmax { + float: right; +} + +.pfc_invisible { + display: none; +} + +div.pfc_message { + margin: 0; padding: 0; + background-image: url("getFileUrlFromTheme('images/newmsg.gif'); ?>"); + background-position: right; + background-repeat: no-repeat; +} +div.pfc_message img { + margin: 0; padding: 0; + vertical-align: middle; +} +div.pfc_oldmsg { + background-image: url("getFileUrlFromTheme('images/oldmsg.gif'); ?>"); + background-position: right; + background-repeat: no-repeat; +} + +span.pfc_date, span.pfc_heure { + color: #bebebe; + font-size: 70%; +} +span.pfc_nick { + color: #fbac17; + font-weight: bold; +} + +div#pfc_input_container { + margin: 5px 0 0 0; padding: 0; +} +div#pfc_input_container input { + margin: 0; padding: 0; +} + +div#pfc_input_container table { border: none; margin: 0; padding: 0; } +div#pfc_input_container tbody { border: none; margin: 0; padding: 0; } +div#pfc_input_container td { border: none; margin: 0; padding: 0; } + +div#pfc_input_container td.pfc_td2 { + padding-right: 5px; + width: 100%; +} + +input#pfc_words { + margin: 0; padding: 0; + border: #555 solid 1px; + background-color: #FAFAFA; + width: 100%; + font-size: 12px; + height: 20px; + vertical-align: bottom; + font-size: 1em; + height: 1.2em; +} + +input#pfc_send { + margin: 0; padding: 0; + display: block; + padding: 2px; + border: 1px solid #555; + background-color: #CCC; + font-size: 10px; + vertical-align: bottom; + font-size: 0.7em; + height: 1.9em; + cursor: pointer; +} + +div#pfc_cmd_container { + position: relative; + margin: 4px 0 0 0; padding: 0; +} + +p#pfc_handle { + margin: 0; padding: 0; + display: inline; + margin-right: 5px; + color: #748DE7; + font-weight: bold; + /*background-color: #EEE;*/ + font-size: 70%; /* these two line fix a display problem in IE6 : */ + vertical-align: top; + white-space: pre; +} + +a#pfc_logo { + margin: 0; padding: 0; + float: right; +} +#pfc_ping { + margin: 0 5px 0 0; padding: 0; + float:right; + font-size: 80%; +} +a#pfc_logo img { + margin: 0; padding: 0; +} +div.pfc_btn { + margin: 0; padding: 0; + display: inline; + cursor: pointer; +} +div.pfc_btn img { + margin: 0; padding: 0; border: none; + vertical-align: middle; +} + +div#pfc_bbcode_container { + margin: 4px 0 4px 0; padding: 0; +} + +div#pfc_errors { + margin: 0 0 4px 0; padding: 5px; + display: none; + border: 1px solid #555; + color: #EC4B0F; + background-color: #FFBB77; + font-style: italic; + font-family: monospace; + font-size: 90%; +} + +/* commands */ +.pfc_cmd_msg { + color: black; +} +.pfc_cmd_me { + font-style: italic; + color: black; +} +.pfc_cmd_notice { + font-style: italic; + color: #888; +} + +/* commands info */ +.pfc_info { + color: #888; + + /* to fix IE6 display bug */ + /* http://sourceforge.net/tracker/index.php?func=detail&aid=1545403&group_id=158880&atid=809601 */ + font-family: sans-serif; /* do NOT setup monospace font or it will not work in IE6 */ + + font-style: italic; + background-color: #EEE; + font-size: 80%; +} + +div#pfc_colorlist { + margin:0; padding:0; + display: none; +} +img.pfc_color { + margin: 1px;padding: 1px; + cursor: pointer; + vertical-align: middle; +} + +.pfc_nickmarker { + white-space: pre; +} + +div#pfc_smileys { + margin: 0; padding: 0; + display: none; /* will be shown by javascript routines */ + background-color: #000000; + border: 1px solid #555; + padding: 4px; +} +div#pfc_smileys img { + margin: 0; padding: 0; + margin-right: 2px; + cursor: pointer; + vertical-align: middle; +} + +div.pfc_nickwhois { padding: 0; margin: 0; } +div.pfc_nickwhois a img { border: none; } +div.pfc_nickwhois { + border: 1px solid #444; + background-color: #999999; + font-size: 75%; +} +.pfc_nickwhois_header { + margin: 0; padding: 0; + background-color: #EEE; + border-bottom: 1px solid #444; + text-align: center; + font-weight: bold; + vertical-align: middle; +} +.pfc_nickwhois_header img { + float: left; + cursor: pointer; + vertical-align: middle; + margin: 3px 0 3px 2px; +} +div.pfc_nickwhois table { + width: 120px; +} + +div.pfc_nickwhois table { border: none; margin: 0; padding: 0; } +div.pfc_nickwhois tbody { border: none; margin: 0; padding: 0; } +div.pfc_nickwhois td { border: none; margin: 0; padding: 0 0 0 2px; } + +td.pfc_nickwhois_c1 { + font-weight: bold; +} +td.pfc_nickwhois_c2 { +} +.pfc_nickwhois_pv { + margin:0; padding: 0 0 0 2px; + text-align: left; +} +.pfc_nickwhois_pv a { + text-decoration: none; +} + + +img.pfc_nickbutton { + cursor: pointer; +} + +div#pfc_debug { + font-size: 11px; +} +div#pfc_sound_container { + position: absolute; + top: 0; + left: 0; + visibility:hidden; /* this box is hidden because it contains a flash sound media (sound.swf)*/ + width: 0; + height: 0; +} + + +/* The DHTML prompt */ +div#pfc_promptbox { + border: 2px solid #748DE7; + background-color: #DDD; + width: 350px; +} +div#pfc_promptbox h2 { + margin: 0; + width: 100%; + background-color: #888; + color: white; + font-family: verdana; + font-size: 10pt; + font-weight: bold; + height: 20px; +} +div#pfc_promptbox p { + margin: 10px 0 0 10px; +} +div#pfc_promptbox form { + margin: 0 10px 10px 10px; + text-align: right; +} +div#pfc_promptbox input { + border: 1px solid #748DE7; +} +input#pfc_promptbox_field { + width: 100%; +} +input#pfc_promptbox_submit { + margin: 0; +} +input#pfc_promptbox_cancel { + margin: 5px 10px 0 0; +} diff --git a/instmess/themes/phpbb2/smileys/eusa_angel.gif b/instmess/themes/phpbb2/smileys/eusa_angel.gif new file mode 100644 index 0000000..f52c820 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_angel.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_boohoo.gif b/instmess/themes/phpbb2/smileys/eusa_boohoo.gif new file mode 100644 index 0000000..b62c7e5 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_boohoo.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_clap.gif b/instmess/themes/phpbb2/smileys/eusa_clap.gif new file mode 100644 index 0000000..f720fa0 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_clap.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_dance.gif b/instmess/themes/phpbb2/smileys/eusa_dance.gif new file mode 100644 index 0000000..1305996 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_dance.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_doh.gif b/instmess/themes/phpbb2/smileys/eusa_doh.gif new file mode 100644 index 0000000..127fd82 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_doh.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_drool.gif b/instmess/themes/phpbb2/smileys/eusa_drool.gif new file mode 100644 index 0000000..568b189 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_drool.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_eh.gif b/instmess/themes/phpbb2/smileys/eusa_eh.gif new file mode 100644 index 0000000..05721b0 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_eh.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_hand.gif b/instmess/themes/phpbb2/smileys/eusa_hand.gif new file mode 100644 index 0000000..10b9670 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_hand.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_liar.gif b/instmess/themes/phpbb2/smileys/eusa_liar.gif new file mode 100644 index 0000000..7c109b4 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_liar.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_naughty.gif b/instmess/themes/phpbb2/smileys/eusa_naughty.gif new file mode 100644 index 0000000..1ba12ce Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_naughty.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_pray.gif b/instmess/themes/phpbb2/smileys/eusa_pray.gif new file mode 100644 index 0000000..0eef95c Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_pray.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_shhh.gif b/instmess/themes/phpbb2/smileys/eusa_shhh.gif new file mode 100644 index 0000000..fa3ab99 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_shhh.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_shifty.gif b/instmess/themes/phpbb2/smileys/eusa_shifty.gif new file mode 100644 index 0000000..5bd60ec Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_shifty.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_sick.gif b/instmess/themes/phpbb2/smileys/eusa_sick.gif new file mode 100644 index 0000000..d46efa4 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_sick.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_silenced.gif b/instmess/themes/phpbb2/smileys/eusa_silenced.gif new file mode 100644 index 0000000..448399b Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_silenced.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_snooty.gif b/instmess/themes/phpbb2/smileys/eusa_snooty.gif new file mode 100644 index 0000000..c8a1527 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_snooty.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_think.gif b/instmess/themes/phpbb2/smileys/eusa_think.gif new file mode 100644 index 0000000..4781d9f Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_think.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_wall.gif b/instmess/themes/phpbb2/smileys/eusa_wall.gif new file mode 100644 index 0000000..59fbcfe Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_wall.gif differ diff --git a/instmess/themes/phpbb2/smileys/eusa_whistle.gif b/instmess/themes/phpbb2/smileys/eusa_whistle.gif new file mode 100644 index 0000000..05b4f52 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/eusa_whistle.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_arrow.gif b/instmess/themes/phpbb2/smileys/icon_arrow.gif new file mode 100644 index 0000000..2880055 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_arrow.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_biggrin.gif b/instmess/themes/phpbb2/smileys/icon_biggrin.gif new file mode 100644 index 0000000..d352772 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_biggrin.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_confused.gif b/instmess/themes/phpbb2/smileys/icon_confused.gif new file mode 100644 index 0000000..0c49e06 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_confused.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_cool.gif b/instmess/themes/phpbb2/smileys/icon_cool.gif new file mode 100644 index 0000000..cead030 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_cool.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_cry.gif b/instmess/themes/phpbb2/smileys/icon_cry.gif new file mode 100644 index 0000000..7d54b1f Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_cry.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_eek.gif b/instmess/themes/phpbb2/smileys/icon_eek.gif new file mode 100644 index 0000000..5d39781 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_eek.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_evil.gif b/instmess/themes/phpbb2/smileys/icon_evil.gif new file mode 100644 index 0000000..ab1aa8e Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_evil.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_exclaim.gif b/instmess/themes/phpbb2/smileys/icon_exclaim.gif new file mode 100644 index 0000000..6e50e2e Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_exclaim.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_frown.gif b/instmess/themes/phpbb2/smileys/icon_frown.gif new file mode 100644 index 0000000..d2ac78c Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_frown.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_idea.gif b/instmess/themes/phpbb2/smileys/icon_idea.gif new file mode 100644 index 0000000..a40ae0d Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_idea.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_lol.gif b/instmess/themes/phpbb2/smileys/icon_lol.gif new file mode 100644 index 0000000..374ba15 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_lol.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_mad.gif b/instmess/themes/phpbb2/smileys/icon_mad.gif new file mode 100644 index 0000000..1f6c3c2 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_mad.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_mrgreen.gif b/instmess/themes/phpbb2/smileys/icon_mrgreen.gif new file mode 100644 index 0000000..b54cd0f Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_mrgreen.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_neutral.gif b/instmess/themes/phpbb2/smileys/icon_neutral.gif new file mode 100644 index 0000000..4f31156 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_neutral.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_question.gif b/instmess/themes/phpbb2/smileys/icon_question.gif new file mode 100644 index 0000000..9d07226 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_question.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_razz.gif b/instmess/themes/phpbb2/smileys/icon_razz.gif new file mode 100644 index 0000000..29da2a2 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_razz.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_redface.gif b/instmess/themes/phpbb2/smileys/icon_redface.gif new file mode 100644 index 0000000..ad76283 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_redface.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_rolleyes.gif b/instmess/themes/phpbb2/smileys/icon_rolleyes.gif new file mode 100644 index 0000000..d7f5f2f Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_rolleyes.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_sad.gif b/instmess/themes/phpbb2/smileys/icon_sad.gif new file mode 100644 index 0000000..d2ac78c Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_sad.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_smile.gif b/instmess/themes/phpbb2/smileys/icon_smile.gif new file mode 100644 index 0000000..7b1f6d3 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_smile.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_surprised.gif b/instmess/themes/phpbb2/smileys/icon_surprised.gif new file mode 100644 index 0000000..cb21424 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_surprised.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_twisted.gif b/instmess/themes/phpbb2/smileys/icon_twisted.gif new file mode 100644 index 0000000..502fe24 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_twisted.gif differ diff --git a/instmess/themes/phpbb2/smileys/icon_wink.gif b/instmess/themes/phpbb2/smileys/icon_wink.gif new file mode 100644 index 0000000..d148288 Binary files /dev/null and b/instmess/themes/phpbb2/smileys/icon_wink.gif differ diff --git a/instmess/themes/phpbb2/smileys/theme.txt b/instmess/themes/phpbb2/smileys/theme.txt new file mode 100644 index 0000000..75f50dc --- /dev/null +++ b/instmess/themes/phpbb2/smileys/theme.txt @@ -0,0 +1,37 @@ +icon_biggrin.gif :-D :grin: :D +icon_smile.gif :smile: :-) :) +icon_sad.gif :( :-( :sad: +icon_surprised.gif :o :-o :eek: +icon_eek.gif :shock: +icon_confused.gif :-? +icon_cool.gif :cool: 8) 8-) +icon_lol.gif :lol: +icon_mad.gif :x :-x :mad: +icon_razz.gif :P :-P :razz: +icon_redface.gif +icon_cry.gif :cry: +icon_evil.gif :evil: +icon_twisted.gif :twisted: +icon_rolleyes.gif :roll: +icon_wink.gif :wink: ;) ;-) +icon_exclaim.gif :!: +icon_question.gif :?: +icon_idea.gif :idea: +icon_arrow.gif :arrow: +icon_neutral.gif :| :-| :neutral: +icon_mrgreen.gif :mrgreen: +eusa_liar.gif :---) :^o +eusa_clap.gif =D< +eusa_doh.gif #-o +eusa_drool.gif =P~ +eusa_hand.gif =; +eusa_sick.gif :-& +eusa_boohoo.gif :boohoo: +eusa_dance.gif :dance: +eusa_silenced.gif :-# +eusa_whistle.gif :-" +eusa_wall.gif ](*,) +eusa_think.gif :-k +eusa_shifty.gif 8-[ +eusa_pray.gif [-o> +eusa_naughty.gif [-X \ No newline at end of file diff --git a/instmess/version.txt b/instmess/version.txt new file mode 100644 index 0000000..7e32cd5 --- /dev/null +++ b/instmess/version.txt @@ -0,0 +1 @@ +1.3 diff --git a/media/index.html b/media/index.html new file mode 100644 index 0000000..ce952e5 --- /dev/null +++ b/media/index.html @@ -0,0 +1,21 @@ + + + +Video Player + + + + + +
    + +
    + + \ No newline at end of file diff --git a/media/mediaplayer.swf b/media/mediaplayer.swf new file mode 100644 index 0000000..a20ee38 Binary files /dev/null and b/media/mediaplayer.swf differ diff --git a/media/sks.flv b/media/sks.flv new file mode 100644 index 0000000..665943b Binary files /dev/null and b/media/sks.flv differ diff --git a/media/source/Kroeger_563.ttf b/media/source/Kroeger_563.ttf new file mode 100644 index 0000000..b484157 Binary files /dev/null and b/media/source/Kroeger_563.ttf differ diff --git a/media/source/com/jeroenwijering/feeds/ASXParser.as b/media/source/com/jeroenwijering/feeds/ASXParser.as new file mode 100644 index 0000000..d767e1d --- /dev/null +++ b/media/source/com/jeroenwijering/feeds/ASXParser.as @@ -0,0 +1,69 @@ +/** +* Parses ASX feeds and returns an indexed array with all elements +* +* @author Jeroen Wijering +* @version 1.0 +**/ + + +import com.jeroenwijering.feeds.AbstractParser; +import com.jeroenwijering.utils.StringMagic; + + +class com.jeroenwijering.feeds.ASXParser extends AbstractParser { + + + /** Contructor **/ + function ASXParser() { super(); }; + + + /** build an array with all regular elements **/ + private function setElements() { + elements = new Object(); + elements["title"] = "title"; + elements["author"] = "author"; + elements["abstract"] = "description"; + }; + + + /** Convert RSS structure to array **/ + private function parse(xml:XML):Array { + var arr = new Array(); + var tpl = xml.firstChild.firstChild; + while(tpl != null) { + if (tpl.nodeName.toLowerCase() == "entry") { + var obj = new Object(); + for(var j=0; j -1) { + obj["type"] = "youtube"; + } + } else if(nnm == "param") { + obj[nod.attributes.name] = nod.attributes.value; + } + } + arr.push(obj); + } + tpl = tpl.nextSibling; + } + return arr; + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/feeds/ATOMParser.as b/media/source/com/jeroenwijering/feeds/ATOMParser.as new file mode 100644 index 0000000..a15f51e --- /dev/null +++ b/media/source/com/jeroenwijering/feeds/ATOMParser.as @@ -0,0 +1,91 @@ +/** +* Parses ATOM feeds and returns an indexed array with all elements +* +* @author Jeroen Wijering +* @version 1.4 +**/ + + +import com.jeroenwijering.feeds.AbstractParser; +import com.jeroenwijering.utils.StringMagic; + + +class com.jeroenwijering.feeds.ATOMParser extends AbstractParser { + + + /** Contructor **/ + function ATOMParser() { super(); }; + + + /** build an array with all regular elements **/ + private function setElements() { + elements = new Object(); + elements["title"] = "title"; + elements["id"] = "id"; + }; + + + /** Convert ATOM structure to array **/ + private function parse(xml:XML):Array { + var arr = new Array(); + var tpl = xml.firstChild.firstChild; + var ttl; + while(tpl != null) { + if (tpl.nodeName.toLowerCase() == "entry") { + var obj = new Object(); + for(var j=0; j -1) { + var idx = dat.indexOf(" "); + dat = dat.substr(0,idx) + dat.substr(idx+1); + } + var myDate = new Date(dat.substr(0,4),dat.substr(5,2)-1, + dat.substr(8,2),dat.substr(11,2),dat.substr(14,2), + dat.substr(17,2)); + var stamp = Math.round(myDate.valueOf()/1000) - + myDate.getTimezoneOffset()*60; + if(dat.length > 20) { + var hr:Number = Number(dat.substr(20,2)); + var mn:Number = Number(dat.substr(23,2)); + if(dat.charAt(19) == "-") { + stamp = stamp - hr*3600 - mn*60; + } else { + stamp += hr*3600 + mn*60; + } + } + return stamp; + } else { + return dat; + } + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/feeds/AgriyaParser.as b/media/source/com/jeroenwijering/feeds/AgriyaParser.as new file mode 100644 index 0000000..9598a6a --- /dev/null +++ b/media/source/com/jeroenwijering/feeds/AgriyaParser.as @@ -0,0 +1,45 @@ +/** +* Parses Agriya playlists and returns an indexed array with all elements +* +* @author Jeroen Wijering +* @version 1.0 +**/ + + +import com.jeroenwijering.feeds.AbstractParser; +import com.jeroenwijering.utils.StringMagic; + + +class com.jeroenwijering.feeds.AgriyaParser extends AbstractParser { + + + /** Contructor **/ + function AgriyaParser() { super(); }; + + + /** Convert Agriya structure to array **/ + private function parse(xml:XML):Array { + var arr = new Array(); + var tpl = xml.firstChild.firstChild.firstChild; + while(tpl != null) { + if (tpl.nodeName.toLowerCase() == "video") { + var obj = new Object(); + obj['file'] = tpl.attributes.Path; + obj['image'] = tpl.attributes.Thumbnail; + obj['title'] = tpl.attributes.Description; + if(obj["file"].substr(0,4) == "rtmp") { + obj["type"] = "rtmp"; + } else if(obj['file'].indexOf('youtube.com') > -1) { + obj["type"] = "youtube"; + } else { + obj['type'] = obj["file"].substr(-3).toLowerCase(); + } + arr.push(obj); + } + tpl = tpl.nextSibling; + } + return arr; + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/feeds/FeedListener.as b/media/source/com/jeroenwijering/feeds/FeedListener.as new file mode 100644 index 0000000..b2c6836 --- /dev/null +++ b/media/source/com/jeroenwijering/feeds/FeedListener.as @@ -0,0 +1,19 @@ +/** +* Interface for all objects that need real-time feed updates. +* +* @author Jeroen Wijering +* @version 1.0 +**/ + + +import com.jeroenwijering.feeds.*; + + +interface com.jeroenwijering.feeds.FeedListener { + + + /** invoked when the feed object has updated **/ + function onFeedUpdate(typ:String); + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/feeds/FeedManager.as b/media/source/com/jeroenwijering/feeds/FeedManager.as new file mode 100644 index 0000000..e453566 --- /dev/null +++ b/media/source/com/jeroenwijering/feeds/FeedManager.as @@ -0,0 +1,257 @@ +/** +* Parses RSS, ATOM and XSPF lists and returns them as a numerical array. +* +* @author Jeroen Wijering +* @version 1.7 +**/ + + +import com.jeroenwijering.feeds.*; + + +class com.jeroenwijering.feeds.FeedManager { + + + /** The array the XML is parsed into. **/ + public var feed:Array; + /** XML file **/ + private var feedXML:XML; + /** Flag for captions. **/ + public var captions:Boolean; + /** Flag for extra audiotrack. **/ + public var audio:Boolean; + /** Flag for all items in mp3 **/ + public var onlymp3s:Boolean; + /** Flag for chapter index **/ + public var ischapters:Boolean; + /** Flag for enclosures **/ + private var enclosures:Boolean; + /** Reference to the parser object **/ + private var parser:AbstractParser; + /** An array with objects listening to feed updates **/ + private var listeners:Array; + /** A prefix string for all files **/ + private var prefix:String = ""; + /** Stream to use **/ + private var stream:String; + /** Array with all file elements **/ + private var elements:Object = { + file:"", + fallback:"", + title:"", + link:"", + id:"", + image:"", + author:"", + captions:"", + audio:"", + category:"", + start:"", + type:"", + duration:"" + }; + /** array with all supported filetypes **/ + private var filetypes:Array = new Array( + "flv","mp3","rbs","jpg","gif","png","rtmp", + "swf","mp4","m4v","m4a","mov","3gp","3g2" + ); + + + /** Constructor. **/ + function FeedManager(enc:Boolean,jvs:String,pre:String,str:String) { + enc == true ? enclosures = true: enclosures = false; + if(jvs == "true") { enableJavascript(); } + pre == undefined ? null: prefix = pre; + str == undefined ? null: stream = "_"+str; + listeners = new Array(); + }; + + + /** Enable javascript access to loadFile command. **/ + private function enableJavascript() { + if(flash.external.ExternalInterface.available) { + flash.external.ExternalInterface.addCallback( + "loadFile",this,loadFile); + flash.external.ExternalInterface.addCallback( + "addItem",this,addItem); + flash.external.ExternalInterface.addCallback( + "removeItem",this,removeItem); + flash.external.ExternalInterface.addCallback( + "itemData",this,itemData); + flash.external.ExternalInterface.addCallback( + "getLength",this,getLength); + } + }; + + + /** Load an XML playlist or single media file. **/ + public function loadFile(obj:Object) { + feed = new Array(); + var ftp = "xml"; + for(var i = filetypes.length; --i >= 0;) { + if(obj['file'].substr(0,4).toLowerCase() == "rtmp") { + ftp = "rtmp"; + } else if(obj['file'].indexOf('youtube.com') > -1) { + ftp = "youtube"; + } else if(obj['type'] == filetypes[i]) { + ftp = filetypes[i]; + } else if (obj['file'].substr(-3).toLowerCase() == filetypes[i]) { + ftp = filetypes[i]; + } + } + if (ftp == "xml") { + loadXML(unescape(obj['file'])); + } else { + feed[0] = new Object(); + feed[0]['type'] = ftp; + for(var itm in elements) { + if(obj[itm] != undefined) { + feed[0][itm] = obj[itm]; + } + } + playersPostProcess(); + } + }; + + + /** Parse an XML file, return the array when done. **/ + private function loadXML(url:String) { + var ref = this; + feedXML = new XML(); + feedXML.ignoreWhite = true; + feedXML.onLoad = function(scs:Boolean) { + if(scs) { + var fmt = this.firstChild.nodeName.toLowerCase(); + if( fmt == 'rss') { + ref.parser = new RSSParser(ref.prefix); + ref.feed = ref.parser.parse(this); + } else if (fmt == 'feed') { + ref.parser = new ATOMParser(ref.prefix); + ref.feed = ref.parser.parse(this); + } else if (fmt == 'playlist') { + ref.parser = new XSPFParser(ref.prefix); + ref.feed = ref.parser.parse(this); + } else if (fmt == 'asx') { + ref.parser = new ASXParser(ref.prefix); + ref.feed = ref.parser.parse(this); + } else if (fmt == 'videolist') { + ref.parser = new AgriyaParser(ref.prefix); + ref.feed = ref.parser.parse(this); + } + if(_root.audio != undefined) { + ref.feed[0]["audio"] = unescape(_root.audio); + } + ref.playersPostProcess(url); + } + }; + if(_root._url.indexOf("file://") > -1) { feedXML.load(url); } + else if(url.indexOf('?') > -1) { feedXML.load(url+'&'+random(999)); } + else { feedXML.load(url+'?'+random(999)); } + }; + + + /** set a number of flags specifically used by the players **/ + private function playersPostProcess(url:String) { + onlymp3s = true; + feed.length > 1 ? ischapters = true: ischapters = false; + captions = false; + audio = false; + for(var i=0; i= feed.length) { + feed.push(obj); + } else { + var arr1 = feed.slice(0,idx); + var arr2 = feed.slice(idx); + arr1.push(obj); + feed = arr1.concat(arr2); + } + updateListeners('add'); + }; + + + /** Remove an item from the feed **/ + public function removeItem(idx:Number) { + if(feed.length == 1) { + return; + } else if(arguments.length == 0 || idx >= feed.length) { + feed.pop(); + } else { + feed.splice(idx,1); + } + updateListeners('remove'); + }; + + + /** Retrieve playlist data for a specific item **/ + public function itemData(idx:Number):Object { + return feed[idx]; + }; + + + /** Add a feed update listener. **/ + public function addListener(lst:Object) { + listeners.push(lst); + }; + + + /** Remove a feed update listener. **/ + public function removeListener(lst:Object) { + for(var i = listeners.length; --i >= 0; ) { + if(listeners[i] == lst) { + listeners.splice(i,1); + return; + } + } + }; + + + /** Notify all listeners of a feed update **/ + private function updateListeners(typ:String) { + for(var i = listeners.length; --i >= 0; ) { + listeners[i].onFeedUpdate(typ); + } + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/feeds/RSSParser.as b/media/source/com/jeroenwijering/feeds/RSSParser.as new file mode 100644 index 0000000..5d32394 --- /dev/null +++ b/media/source/com/jeroenwijering/feeds/RSSParser.as @@ -0,0 +1,132 @@ +/** +* Parses ATOM feeds and returns an indexed array with all elements +* +* @author Jeroen Wijering +* @version 1.5 +**/ + + +import com.jeroenwijering.feeds.AbstractParser; +import com.jeroenwijering.utils.StringMagic; + + +class com.jeroenwijering.feeds.RSSParser extends AbstractParser { + + + /** Contructor **/ + function RSSParser() { super(); }; + + + /** build an array with all regular elements **/ + private function setElements() { + elements = new Object(); + elements["title"] = "title"; + elements["guid"] = "id"; + elements["category"] = "category"; + elements["link"] = "link"; + elements["geo:lat"] = "latitude"; + elements["geo:long"] = "longitude"; + elements["geo:city"] = "city"; + }; + + + /** Convert RSS structure to array **/ + private function parse(xml:XML):Array { + var arr = new Array(); + var tpl = xml.firstChild.firstChild.firstChild; + var ttl; + while(tpl != null) { + if (tpl.nodeName.toLowerCase() == "item") { + var obj = new Object(); + for(var j=0; j -1) { + obj["type"] = "youtube"; + } + if(nod.childNodes[0].nodeName=="media:thumbnail"){ + obj["image"]=nod.childNodes[0].attributes.url; + } + } else if(obj["type"] != undefined && typ == "video/x-flv") { + obj['fallback'] = nod.attributes.url; + } else if(typ == "captions") { + obj["captions"] = nod.attributes.url; + } else if(typ == "audio") { + obj["audio"] = nod.attributes.url; + } + } else if(nnm == "media:group") { + for(var k=0; k< nod.childNodes.length; k++) { + var ncn=nod.childNodes[k].nodeName.toLowerCase(); + if(ncn == "media:content") { + var ftp = nod.childNodes[k].attributes.type.toLowerCase(); + if(mimetypes[ftp] != undefined && obj["type"] == undefined) { + obj["file"] = nod.childNodes[k].attributes.url; + obj['duration'] = StringMagic.toSeconds( + nod.attributes.duration); + obj["type"]=mimetypes[ftp]; + if(obj["file"].substr(0,4) == "rtmp") { + obj["type"] = "rtmp"; + } else if(obj['file'].indexOf('youtube.com') > -1) { + obj["type"] = "youtube"; + } + } + if(obj["type"] != undefined && ftp == "video/x-flv") { + obj['fallback'] = nod.childNodes[k].attributes.url; + } + } + if(ncn == "media:thumbnail") { + obj["image"]=nod.childNodes[k].attributes.url; + } + if(ncn == "media:credit") { + obj["author"]=nod.childNodes[k].firstChild.nodeValue; + } + } + } + } + if(obj["image"] == undefined) { + if(obj["file"].indexOf(".jpg") > 0 || + obj["file"].indexOf(".png") > 0 || + obj["file"].indexOf(".gif") > 0) { + obj["image"] = obj["file"]; + } + } + if(obj["author"] == undefined) { obj["author"] = ttl; } + arr.push(obj); + } else if (tpl.nodeName == "title") { + ttl = tpl.firstChild.nodeValue; + } + tpl = tpl.nextSibling; + } + return arr; + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/feeds/XSPFParser.as b/media/source/com/jeroenwijering/feeds/XSPFParser.as new file mode 100644 index 0000000..cce2fe6 --- /dev/null +++ b/media/source/com/jeroenwijering/feeds/XSPFParser.as @@ -0,0 +1,77 @@ +/** +* Parses ATOM feeds and returns an indexed array with all elements. +* +* @author Jeroen Wijering +* @version 1.5 +**/ + + +import com.jeroenwijering.feeds.AbstractParser; +import com.jeroenwijering.utils.StringMagic; + + +class com.jeroenwijering.feeds.XSPFParser extends AbstractParser { + + + /** Contructor **/ + function XSPFParser() { super(); }; + + + /** build an array with all regular elements **/ + private function setElements() { + elements = new Object(); + elements["title"] = "title"; + elements["creator"] = "author"; + elements["info"] = "link"; + elements["image"] = "image"; + elements["identifier"] = "id"; + elements["album"] = "category"; + }; + + + /** Convert ATOM structure to array **/ + private function parse(xml:XML):Array { + var arr = new Array(); + var tpl = xml.firstChild.firstChild; + while(tpl != null) { + if (tpl.nodeName == 'trackList') { + for(var i=0; i -1) { + obj["type"] = "youtube"; + } else if(mimetypes[typ] != undefined) { + obj["type"] = mimetypes[typ]; + } + } else if(nnm == "annotation") { + obj["description"] = StringMagic.stripTagsBreaks( + nod.firstChild.nodeValue); + } else if(nnm == "link" && + nod.attributes.rel == "captions") { + obj["captions"] = nod.firstChild.nodeValue; + } else if(nnm == "link" && + nod.attributes.rel == "audio") { + obj["audio"] = nod.firstChild.nodeValue; + } else if(nnm == "meta") { + obj[nod.attributes.rel] = nod.firstChild.nodeValue; + } + } + arr.push(obj); + } + } + tpl = tpl.nextSibling; + } + return arr; + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/AbstractController.as b/media/source/com/jeroenwijering/players/AbstractController.as new file mode 100644 index 0000000..a582f42 --- /dev/null +++ b/media/source/com/jeroenwijering/players/AbstractController.as @@ -0,0 +1,207 @@ +/** +* Abstract controller class of the MCV pattern, extended by all controlllers. +* +* @author Jeroen Wijering +* @version 1.8 +**/ + + +import com.jeroenwijering.players.*; +import com.jeroenwijering.utils.*; + + +class com.jeroenwijering.players.AbstractController + implements com.jeroenwijering.feeds.FeedListener { + + + /** Randomizer instance **/ + private var randomizer:Randomizer; + /** array with all registered models **/ + private var registeredModels:Array; + /** reference to the config array **/ + private var config:Object; + /** reference to the feed array **/ + private var feeder:Object; + /** Current item **/ + private var currentItem:Number; + /** Current item **/ + private var currentURL:String; + /** Current item **/ + private var isPlaying:Boolean; + /** Number of items played: used for repeat=list **/ + private var itemsPlayed:Number; + /** Array that saves the original video dimensions. **/ + private var sizes:Array; + + + /** Constructor. **/ + function AbstractController(cfg:Object,fed:Object) { + config = cfg; + feeder = fed; + feeder.addListener(this); + }; + + + /** Complete the build of the MCV cycle and start flow of events. **/ + public function startMCV(mar:Array) {}; + + + /** Receive events from the views. **/ + public function getEvent(typ:String,prm:Number,pr2:Number) { + //trace("controller: "+typ+": "+prm); + switch(typ) { + case "playpause": + setPlaypause(); + break; + case "prev": + if(noCommercial()) { setPrev(); } + break; + case "next": + if(noCommercial()) { setNext(); } + break; + case "stop": + if(noCommercial()) { setStop(); } + break; + case "scrub": + if(noCommercial()) { setScrub(prm); } + break; + case "volume": + setVolume(prm); + break; + case "playitem": + if(noCommercial()) { setPlayitem(prm); } + break; + case "getlink": + setGetlink(prm); + break; + case "fullscreen": + setFullscreen(); + break; + case "complete": + setComplete(); + break; + case "captions": + setCaptions(); + break; + case "audio": + setAudio(); + break; + case "size": + saveSizes(prm,pr2); + break; + default: + trace("controller: incompatible event received"); + break; + } + }; + + + /** Check for commercial **/ + private function noCommercial():Boolean { + if(feeder.feed[currentItem]['category'] == 'commercial' || + feeder.feed[currentItem]['category'] == 'preroll' || + feeder.feed[currentItem]['category'] == 'postroll') { + return false; + } else { + return true; + } + }; + + + /** PlayPause switch **/ + private function setPlaypause() {}; + + + /** Play previous item. **/ + private function setPrev() {}; + + + /** Play next item. **/ + private function setNext() {}; + + + /** Stop and clear item. **/ + private function setStop() {}; + + + /** Forward scrub number to model. **/ + private function setScrub(prm:Number) {}; + + + /** Play a new item. **/ + private function setPlayitem(itm:Number) { + currentURL = feeder.feed[itm]['file']; + }; + + + /** Get url from an item if link exists, else playpause. **/ + private function setGetlink(idx:Number) {}; + + + /** Determine what to do if an item is completed. **/ + private function setComplete() {}; + + + /** Volume event handler **/ + private function setVolume(prm:Number) {}; + + + /** Switch fullscreen mode **/ + private function setFullscreen() {}; + + + /** Switch captions on and off **/ + private function setCaptions() {}; + + + /** Switch captions on and off **/ + private function saveSizes(pr1:Number,pr2:Number) { + sizes = new Array(pr1,pr2); + }; + + + /** Switch audiotrack on and off **/ + private function setAudio() {}; + + + /** Sending changes to all registered models. **/ + private function sendChange(typ:String,prm:Number):Void { + for(var i=0; i 0) { + currentPosition = feeder.feed[idx]["start"]; + } + if(fnd == true) { + isActive = true; + sendUpdate("item",idx); + } else { + isActive = false; + } + }; + + + /** Start function. **/ + private function setStart(prm:Number) {}; + + + /** Pause function. **/ + private function setPause(prm:Number) {}; + + + /** Stop function. **/ + private function setStop() {}; + + + /** Set volume and pass through if active. **/ + private function setVolume(vol:Number) { + if(isActive == true) { sendUpdate("volume",vol); } + }; + + + /** Send updates to the views. **/ + private function sendUpdate(typ:String,prm:Number,pr2:Number) { + for(var i=0; i 50) { + config["searchbar"] = 28; + } else { + config["searchbar"] = 0; + } + if (config["displayheight"] == undefined) { + config["displayheight"] = config["height"] - config['controlbar'] - config["searchbar"]; + } else if(config["displayheight"] >= config["height"] - config["searchbar"]) { + config["displayheight"] = config["height"] - config["searchbar"]; + } else { + config["displayheight"] = Number(config["displayheight"]); + } + if (config["displaywidth"] == undefined) { + config["displaywidth"] = config["width"]; + } else { + config["displaywidth"] = Number(config["displaywidth"]); + } + config["bwstreams"] == undefined ? loadFile(): checkStream(); + }; + + + + /** Placeholder function for bandwidth checking **/ + private function checkStream() {}; + + + /** Load the file or playlist **/ + private function loadFile(str:String) { + feeder = new FeedManager(true,config["enablejs"],config['prefix'],str); + feeder.addListener(this); + feeder.loadFile(config); + }; + + + /** Invoked by the feedmanager **/ + public function onFeedUpdate(typ:String) { + if(controller == undefined) { + config["clip"]._visible = true; + config["clip"]._parent.activity._visible = false; + setupMCV(); + } + }; + + + /** Setup all necessary MCV blocks. **/ + private function setupMCV() { + controller = new AbstractController(config,feeder); + var asv = new AbstractView(controller,config,feeder); + var vws:Array = new Array(asv); + var asm = new AbstractModel(vws,controller,config,feeder); + var mds:Array = new Array(asm); + controller.startMCV(mds); + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/AbstractView.as b/media/source/com/jeroenwijering/players/AbstractView.as new file mode 100644 index 0000000..16f3189 --- /dev/null +++ b/media/source/com/jeroenwijering/players/AbstractView.as @@ -0,0 +1,91 @@ +/** +* Basic view class of the players MCV pattern, extended by all views. +* Create you own views by extending this one. +* +* @author Jeroen Wijering +* @version 1.2 +**/ + + +import com.jeroenwijering.players.*; + + +class com.jeroenwijering.players.AbstractView { + + + /** Controller reference **/ + private var controller:AbstractController; + /** reference to config Array **/ + private var config:Object; + /** reference to feed Array **/ + private var feeder:Object; + + + /** Constructor **/ + function AbstractView(ctr:AbstractController,cfg:Object,fed:Object) { + controller = ctr; + config = cfg; + feeder = fed; + }; + + + /** Receive updates from the models. **/ + public function getUpdate(typ:String,pr1:Number,pr2:Number):Void { + //trace("view: "+typ+": "+pr1+","+pr2); + switch(typ) { + case "state": + setState(pr1); + break; + case "load": + setLoad(pr1); + break; + case "time": + setTime(pr1,pr2); + break; + case "item": + setItem(pr1); + break; + case "size": + setSize(pr1,pr2); + break; + case "volume": + setVolume(pr1); + break; + default: + trace("View: incompatible update received"); + break; + } + }; + + + /** Empty state handler **/ + private function setState(pr1:Number) {}; + + + /** Empty load handler **/ + private function setLoad(pr1:Number) {}; + + + /** Empty time handler **/ + private function setTime(pr1:Number,pr2:Number) {}; + + + /** Empty item handler **/ + private function setItem(pr1:Number) {}; + + + /** Empty item handler **/ + private function setSize(pr1:Number,pr2:Number) {}; + + + /** Empty volume handler **/ + private function setVolume(pr1:Number) {}; + + + /** Send event to the controller. **/ + private function sendEvent(typ:String,prm:Number) { + controller.getEvent(typ,prm); + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/AudioView.as b/media/source/com/jeroenwijering/players/AudioView.as new file mode 100644 index 0000000..05aa838 --- /dev/null +++ b/media/source/com/jeroenwijering/players/AudioView.as @@ -0,0 +1,103 @@ +/** +* Extra audiotrack management of the players MCV pattern. +* +* @author Jeroen Wijering +* @version 1.1 +**/ + + +import com.jeroenwijering.players.*; + +class com.jeroenwijering.players.AudioView extends AbstractView { + + + /** The MovieClip to which the sounds will be attached **/ + private var audioClip:MovieClip; + /** The Sound object we'll use**/ + private var audioObject:Sound; + /** Currently active feeditem **/ + private var currentItem:Number; + /** The current elapsed time **/ + private var currentTime:Number = 0; + /** The last stop position **/ + private var stopTime:Number; + /** The current audio time **/ + private var audioTime:Number; + /** Save the current state **/ + private var currentState:Number; + /** Check whether an MP3 file is loaded **/ + private var isLoaded:String; + /** Sync the audio with emtry or not **/ + private var sync:Boolean; + + + /** Constructor, loads caption file. **/ + function AudioView(ctr:AbstractController,cfg:Object,fed:Object, + snc:Boolean) { + super(ctr,cfg,fed); + sync = snc; + var ref = this; + audioClip = config['clip'].createEmptyMovieClip('audio', + config['clip'].getNextHighestDepth()); + audioClip.setStart = function() { + if(ref.stopTime == undefined && ref.sync == false) { + ref.audioObject.loadSound(ref.feeder.feed[0]['audio'],true); + ref.audioObject.setVolume(Number(ref.config['volume'])); + ref.audioObject.start(0); + } else if (ref.sync == false) { + ref.audioObject.start(ref.stopTime); + } else if(ref.currentState == 2) { + ref.audioObject.start(ref.currentTime); + } + }; + audioClip.setStop = function() { + ref.audioObject.stop(); + ref.stopTime = ref.audioObject.position/1000; + }; + audioObject = new Sound (audioClip); + if(config['useaudio'] == "true" && sync == false) { + audioClip.setStart(); + } + if(sync == false) { + audioObject.onSoundComplete = function() { + this.start(); + }; + } + }; + + + private function setItem(idx:Number) { + currentItem = idx; + }; + + + private function setState(stt:Number) { + currentState = stt; + if(sync == false) { return; } + if(stt == 2 && config['useaudio'] == "true") { + audioObject.start(currentTime); + } else { + audioObject.stop(); + } + }; + + + private function setTime(elp:Number,rem:Number) { + if(sync == false) { return; } + if(Math.abs(elp-currentTime) > 1) { + currentTime = elp; + audioTime = audioObject.position/1000; + if(Math.abs(currentTime - audioTime) > 1 && + config['useaudio'] == "true") { + audioObject.start(currentTime); + } + } + if (isLoaded != feeder.feed[currentItem]['audio']) { + isLoaded = feeder.feed[currentItem]['audio']; + audioObject.loadSound(isLoaded,true); + audioObject.setVolume(Number(config['volume'])); + } + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/CallbackView.as b/media/source/com/jeroenwijering/players/CallbackView.as new file mode 100644 index 0000000..7294710 --- /dev/null +++ b/media/source/com/jeroenwijering/players/CallbackView.as @@ -0,0 +1,93 @@ +/** +* Callback to serverside script for statistics handling. +* It sends the current file,title,id and state on start and complete. +* +* @author Jeroen Wijering +* @author Nate Hanna +* @version 1.7 +**/ + + +import com.jeroenwijering.players.*; + + +class com.jeroenwijering.players.CallbackView extends AbstractView { + + + /** Currently playing item **/ + private var currentItem:Number; + /** Currently playing item **/ + private var varsObject:LoadVars; + /** Boolean for if a start call has already been sent for an item. **/ + private var playSent:Boolean = false; + /** Small interval so both complete and play events won't be issued **/ + private var playSentInt:Number; + /** Timestamp of the start of the movie **/ + private var startStamp:Number; + + + /** Constructor **/ + function CallbackView(ctr:AbstractController,cfg:Object,fed:Object) { + super(ctr,cfg,fed); + if(config['callback'] != "analytics") { + varsObject = new LoadVars(); + } + }; + + + /** Send a callback on state change **/ + private function setState(pr1:Number) { + var dat = new Date(); + if(pr1 == 3) { + var dur = Math.round(dat.valueOf()/1000 - startStamp); + sendVars("stop",dur,true); + playSent = false; + } else if (pr1 == 2 && playSent == false) { + playSentInt = setInterval(this,"sendVars",500,"start",0); + playSent = true; + startStamp = dat.valueOf()/1000; + } + }; + + + /** save the currently playing item **/ + private function setItem(pr1:Number) { + if(playSent == true && currentItem != undefined) { + var dat = new Date(); + var dur = Math.round(dat.valueOf()/1000 - startStamp); + sendVars("stop",dur,false); + playSent = false; + } + currentItem = pr1; + }; + + + /** sending the current file,title,id,state,timestamp to callback **/ + private function sendVars(stt:String,dur:Number,cpl:Boolean) { + clearInterval(playSentInt); + if(config['callback'] == "urchin" || config['callback'] == "analytics") { + var fil = feeder.feed[currentItem]["file"]; + var fcn = "javascript:pageTracker._trackPageview"; + if(config['callback'] == "urchin") { + fcn = "javascript:urchinTracker"; + } + if(fil.indexOf('http') != undefined) { + fil = fil.substring(fil.indexOf('/',7)+1); + } + if(stt == "start") { + getURL(fcn+"('/start_stream/"+fil+"');"); + } else if (stt == "stop" && cpl == true) { + getURL(fcn+"('/end_stream/"+fil+"');"); + } + } else { + varsObject.file = feeder.feed[currentItem]["file"]; + varsObject.title = feeder.feed[currentItem]["title"]; + varsObject.id = feeder.feed[currentItem]["id"]; + varsObject.state = stt; + varsObject.duration = dur; + varsObject.sendAndLoad(config["callback"],varsObject,"POST"); + } + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/CaptionsParser.as b/media/source/com/jeroenwijering/players/CaptionsParser.as new file mode 100644 index 0000000..9ac924c --- /dev/null +++ b/media/source/com/jeroenwijering/players/CaptionsParser.as @@ -0,0 +1,157 @@ +/** +* Parses SRT lists and W3C Timed Text captions. +* +* @author Jeroen Wijering +* @version 1.3 +**/ + + +import com.jeroenwijering.utils.StringMagic; + + +class com.jeroenwijering.players.CaptionsParser { + + + /** URL of the xml file to parse. **/ + private var parseURL:String; + /** The array the XML is parsed into **/ + public var parseArray:Array; + /** LoadVars Object the SRT file is loaded into. **/ + private var parseLV:LoadVars; + /** Flash XML object the TT file is loaded into. **/ + private var parseXML:XML; + + + /** Constructor. **/ + function CaptionsParser() {}; + + + /** Parse an XML list. **/ + public function parse(url:String):Void { + parseURL = url; + parseArray = new Array(); + parseURL.indexOf(".srt") == -1 ? parseTT(): parseSRT(); + }; + + + /** Convert SRT file to subtitle array **/ + private function parseSRT() { + var ref = this; + parseLV = new LoadVars(); + parseLV.onLoad = function(scs:Boolean) { + if(scs) { + var str = ""; + var j = -2; + while(j < unescape(this).length) { + var oj = j; + j = unescape(this).indexOf('=&',j+2); + j == -1 ? j = unescape(this).length: null; + str = "&"+unescape(this).substring(oj+2,j) + str; + } + var arr = str.split("\r\n\r\n"); + for(var i=0; i -1) { + var brp = arr[i].indexOf("\r\n",tst+5); + arr[i] = arr[i].substr(0,brp)+"
    " + + arr[i].substr(brp+2); + } + obj["txt"] = arr[i].substr(tst+2); + if(!isNaN(obj['bgn'])) { + ref.parseArray.push(obj); + } + delete obj; + } + } else { + ref.parseArray.push( {txt:"File not found: " + + ref.parseURL,bgn:1,dur:5}); + } + if(ref.parseArray.length == 0) { + ref.parseArray.push({txt:"Empty file: " + + ref.parseURL,bgn:1,dur:5}); + } + delete ref.parseLV; + ref.onParseComplete(); + }; + if(_root._url.indexOf("file://") > -1) { + parseLV.load(parseURL); + } else if(parseURL.indexOf('?') > -1) { + parseLV.load(parseURL+'&'+random(999)); + } else { + parseLV.load(parseURL+'?'+random(999)); + } + }; + + + /** Covert TimedText file to subtitle array. **/ + private function parseTT():Void { + var ref = this; + parseXML = new XML(); + parseXML.ignoreWhite = true; + parseXML.onLoad = function(scs:Boolean) { + if(scs) { + if(this.firstChild.nodeName.toLowerCase() == "tt") { + var bdy = this.firstChild.childNodes[1]; + if(bdy.firstChild.firstChild.attributes.begin==undefined){ + for(var i=0; i -1) { + parseXML.load(parseURL); + } else if(parseURL.indexOf('?') > -1) { + parseXML.load(parseURL+'&'+random(999)); + } else { + parseXML.load(parseURL+'?'+random(999)); + } + }; + + + /** Invoked when parsing is completed. **/ + public function onParseComplete() { }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/CaptionsView.as b/media/source/com/jeroenwijering/players/CaptionsView.as new file mode 100644 index 0000000..f78b3ca --- /dev/null +++ b/media/source/com/jeroenwijering/players/CaptionsView.as @@ -0,0 +1,144 @@ +/** +* Captions display management of the players MCV pattern. +* +* @author Jeroen Wijering +* @version 1.4 +**/ + + +import com.jeroenwijering.players.*; +import flash.filters.DropShadowFilter; + + +class com.jeroenwijering.players.CaptionsView extends AbstractView { + + + /** The current volume **/ + private var parser:CaptionsParser; + /** The captions array **/ + private var captions:Array; + /** The current elapsed time **/ + private var currentTime:Number; + /** The captions textfield **/ + private var clip:MovieClip; + /** Boolean for captionate captions **/ + private var captionate:Boolean = false; + /** Time of last caption **/ + private var capTime:Number; + /** Captionate track to use **/ + private var capTrack:Number = 0; + + + /** Constructor, loads caption file. **/ + function CaptionsView(ctr:AbstractController,cfg:Object,fed:Object) { + super(ctr,cfg,fed); + var ref = this; + Stage.addListener(this); + parser = new CaptionsParser(); + parser.onParseComplete = function() { + this.parseArray.sortOn("bgn",Array.NUMERIC); + ref.captions = this.parseArray; + delete this; + } + clip = config["clip"].captions; + setDimensions(); + }; + + + /** onLoad override, sets capture sizes. **/ + private function setDimensions() { + clip.txt.autoSize = "center"; + clip.bck._height = clip.txt._height + 10; + if(Stage["displayState"] == "fullScreen") { + clip._width = Stage.width; + clip._yscale= clip._xscale; + clip._y = Stage.height - clip._height; + } else { + clip._width = config["displaywidth"]; + clip._yscale = clip._xscale; + clip._y = config["displayheight"] - clip._height; + } + if(System.capabilities.version.indexOf("7,0,") == -1) { + var blr = 2 + Math.round(clip._yscale/100); + var flt = new flash.filters.DropShadowFilter( + 0,0,0x000000,1,blr,blr,50,2); + clip.filters = new Array(flt); + } + }; + + + /** parse a new captions file every time an item is set **/ + private function setItem(idx:Number) { + captions = new Array(); + if(feeder.feed[idx]["captions"] == undefined) { + clip.bck._alpha = 0; + } else if(feeder.feed[idx]["captions"].indexOf("captionate") > -1 || + feeder.feed[idx]["captions"] == "true") { + captionate = true; + var tck = Number(feeder.feed[idx]["captions"].substr(-1)); + if(isNaN(tck)) { + capTrack = 0; + } else { + capTrack = tck; + } + } else { + parser.parse(feeder.feed[idx]["captions"]); + } + }; + + + /** Check elapsed time, evaluate captions every second. **/ + private function setTime(elp:Number,rem:Number) { + currentTime = elp; + if (captionate == false) { + setCaption(); + } + }; + + + /** Check if a new caption should be displayed **/ + private function setCaption() { + var nxt:Number = captions.length; + for (var i=0; i currentTime) { + nxt = i; + break; + } + } + if(captions[nxt-1]["bgn"] + captions[nxt-1]["dur"] > currentTime) { + clip.txt.htmlText = captions[nxt-1]["txt"]; + if(System.capabilities.version.indexOf("7,0,") > -1) { + clip.bck._alpha = 50; + clip.bck._height = Math.round(clip.txt._height + 10); + } else { + clip.bck._height = Math.round(clip.txt._height + 15); + } + if(Stage["displayState"] == "fullScreen") { + clip._y = Stage.height - clip._height; + } else { + clip._y = config["displayheight"] - clip._height; + } + } else { + clip.txt.htmlText = ""; + } + }; + + + /** Captionate input **/ + public function onCaptionate(cap:Array) { + clip.txt.htmlText = cap[capTrack]; + capTime = currentTime; + }; + + + /** OnResize Handler: catches stage resizing **/ + public function onResize() { setDimensions(); }; + + + /** Catches fullscreen escape **/ + public function onFullScreen(fs:Boolean) { + if(fs == false) { setDimensions(); } + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/ControlbarView.as b/media/source/com/jeroenwijering/players/ControlbarView.as new file mode 100644 index 0000000..10fc000 --- /dev/null +++ b/media/source/com/jeroenwijering/players/ControlbarView.as @@ -0,0 +1,448 @@ +/** +* Controlbar user interface management of the players MCV pattern. +* +* @author Jeroen Wijering +* @version 1.13 +**/ + + +import com.jeroenwijering.players.*; +import com.jeroenwijering.utils.*; +import com.jeroenwijering.feeds.FeedListener; + + +class com.jeroenwijering.players.ControlbarView extends AbstractView + implements FeedListener { + + + /** currently active item **/ + private var currentItem:Number; + /** full width of the scrubbars **/ + private var barWidths:Number; + /** duration of the currently playing item **/ + private var itemLength:Number; + /** progress of the currently playing item **/ + private var itemProgress:Number = 0 + /** do not rescale loadbar on rebuffering **/ + private var wasLoaded:Boolean = false; + /** interval for hiding the display **/ + private var hideInt:Number; + + + /** Constructor **/ + function ControlbarView(ctr:AbstractController,cfg:Object,fed:Object) { + super(ctr,cfg,fed); + setColorsClicks(); + setDimensions(); + Stage.addListener(this); + feeder.addListener(this); + Mouse.addListener(this); + }; + + + /** Sets up colors and clicks of all controlbar items. **/ + private function setColorsClicks() { + var ref = this; + var tgt = config["clip"].controlbar; + tgt.col = new Color(tgt.back); + tgt.col.setRGB(config["backcolor"]); + tgt.playpause.col1 = new Color(tgt.playpause.ply); + tgt.playpause.col1.setRGB(config["frontcolor"]); + tgt.playpause.col2 = new Color(tgt.playpause.pas); + tgt.playpause.col2.setRGB(config["frontcolor"]); + tgt.playpause.pas._visible = false; + tgt.playpause.onRollOver = function() { + this.col1.setRGB(ref.config["lightcolor"]); + this.col2.setRGB(ref.config["lightcolor"]); + }; + tgt.playpause.onRollOut = function() { + this.col1.setRGB(ref.config["frontcolor"]); + this.col2.setRGB(ref.config["frontcolor"]); + }; + tgt.playpause.onRelease = function() { ref.sendEvent("playpause"); }; + tgt.stop.col = new Color(tgt.stop.icn); + tgt.stop.col.setRGB(config["frontcolor"]); + tgt.stop.onRollOver = function() { + this.col.setRGB(ref.config["lightcolor"]); + }; + tgt.stop.onRollOut = function() { + this.col.setRGB(ref.config["frontcolor"]); + }; + tgt.stop.onRelease = function() { ref.sendEvent("stop"); }; + tgt.prev.col = new Color(tgt.prev.icn); + tgt.prev.col.setRGB(config["frontcolor"]); + tgt.prev.onRollOver = function() { + this.col.setRGB(ref.config["lightcolor"]); + }; + tgt.prev.onRollOut = function() { + this.col.setRGB(ref.config["frontcolor"]); + }; + tgt.prev.onRelease = function() { ref.sendEvent("prev"); }; + tgt.next.col = new Color(tgt.next.icn); + tgt.next.col.setRGB(config["frontcolor"]); + tgt.next.onRollOver = function() { + this.col.setRGB(ref.config["lightcolor"]); + }; + tgt.next.onRollOut = function() { + this.col.setRGB(ref.config["frontcolor"]); + }; + tgt.next.onRelease = function() { ref.sendEvent("next"); }; + tgt.scrub.elpTxt.textColor = config["frontcolor"]; + tgt.scrub.remTxt.textColor = config["frontcolor"]; + tgt.scrub.col = new Color(tgt.scrub.icn); + tgt.scrub.col.setRGB(config["frontcolor"]); + tgt.scrub.col2 = new Color(tgt.scrub.bar); + tgt.scrub.col2.setRGB(config["frontcolor"]); + tgt.scrub.col3 = new Color(tgt.scrub.bck); + tgt.scrub.col3.setRGB(config["frontcolor"]); + tgt.scrub.bck.onRollOver = function() { + this._parent.col.setRGB(ref.config["lightcolor"]); + }; + tgt.scrub.bck.onRollOut = function() { + this._parent.col.setRGB(ref.config["frontcolor"]); + }; + tgt.scrub.bck.onPress= function() { + this.onEnterFrame = function() { + var xm = this._parent._xmouse; + if(xm < this._parent.bck._width + this._parent.bck._x && + xm > this._parent.bck._x) { + this._parent.icn._x = this._parent._xmouse - 1; + } + } + }; + tgt.scrub.bck.onRelease= tgt.scrub.bck.onReleaseOutside= function() { + var sec = (this._parent._xmouse-this._parent.bar._x) / + ref.barWidths*ref.itemLength; + ref.sendEvent("scrub",Math.round(sec)); + delete this.onEnterFrame; + }; + tgt.scrub.bck.tabEnabled = false; + tgt.fs.col1 = new Color(tgt.fs.ns); + tgt.fs.col2 = new Color(tgt.fs.fs); + tgt.fs.col.setRGB(ref.config["frontcolor"]); + tgt.fs.col2.setRGB(ref.config["frontcolor"]); + tgt.fs.onRollOver = function() { + this.col1.setRGB(ref.config["lightcolor"]); + this.col2.setRGB(ref.config["lightcolor"]); + }; + tgt.fs.onRollOut = function() { + this.col1.setRGB(ref.config["frontcolor"]); + this.col2.setRGB(ref.config["frontcolor"]); + }; + tgt.fs.onRelease = function() { + ref.sendEvent("fullscreen"); + this.col1.setRGB(ref.config["frontcolor"]); + this.col2.setRGB(ref.config["frontcolor"]); + }; + tgt.cc.col = new Color(tgt.cc.icn); + tgt.cc.col.setRGB(ref.config["frontcolor"]); + tgt.cc.onRollOver = function() { + this.col.setRGB(ref.config["lightcolor"]); + }; + tgt.cc.onRollOut = function() { + this.col.setRGB(ref.config["frontcolor"]); + }; + tgt.cc.onRelease = function() { + ref.sendEvent("captions"); + }; + tgt.au.col = new Color(tgt.au.icn); + tgt.au.col.setRGB(ref.config["frontcolor"]); + tgt.au.onRollOver = function() { + this.col.setRGB(ref.config["lightcolor"]); + }; + tgt.au.onRollOut = function() { + this.col.setRGB(ref.config["frontcolor"]); + }; + tgt.au.onRelease = function() { + ref.sendEvent("audio"); + }; + tgt.dl.col = new Color(tgt.dl.icn); + tgt.dl.col.setRGB(ref.config["frontcolor"]); + tgt.dl.onRollOver = function() { + this.col.setRGB(ref.config["lightcolor"]); + }; + tgt.dl.onRollOut = function() { + this.col.setRGB(ref.config["frontcolor"]); + }; + tgt.dl.onRelease = function() { + ref.sendEvent("getlink",ref.currentItem); + }; + tgt.vol.col = new Color(tgt.vol.bar); + tgt.vol.col.setRGB(config["frontcolor"]); + tgt.vol.col2 = new Color(tgt.vol.bck); + tgt.vol.col2.setRGB(config["frontcolor"]); + tgt.vol.col3 = new Color(tgt.vol.icn); + tgt.vol.col3.setRGB(config["frontcolor"]); + tgt.vol.onRollOver = function() { + this.col.setRGB(ref.config["lightcolor"]); + this.col3.setRGB(ref.config["lightcolor"]); + }; + tgt.vol.onRollOut = function() { + this.col.setRGB(ref.config["frontcolor"]); + this.col3.setRGB(ref.config["frontcolor"]); + }; + tgt.vol.onRelease = function() { + this.onEnterFrame = function() { + this.msk._width = this._xmouse-12; + }; + }; + tgt.vol.onRelease = tgt.vol.onReleaseOutside = function() { + ref.sendEvent("volume",(this._xmouse-12)*5); + delete this.onEnterFrame; + }; + }; + + + /** Sets up dimensions of all controlbar items. **/ + private function setDimensions() { + clearInterval(hideInt); + var tgt = config["clip"].controlbar; + var cbw = 400; + // overall position and width + if(Stage["displayState"] == "fullScreen") { + tgt._x = Math.round(Stage.width/2-200); + tgt._y = Stage.height - 40; + tgt._alpha = 100; + tgt.back._alpha = 50; + tgt.fs.fs._visible = false; + tgt.fs.ns._visible = true; + } else if(config["displayheight"] == config["height"]-config['searchbar']) { + tgt._y = config["displayheight"] - 40; + if(config["displaywidth"] > 450 && + config["displaywidth"] == config["width"]) { + tgt._x = Math.round(Stage.width/2-200); + } else { + tgt._x = 20; + cbw = config["displaywidth"] - 40; + } + tgt.back._alpha = 40; + tgt.fs.fs._visible = true; + tgt.fs.ns._visible = false; + } else { + tgt._x = 0; + tgt._y = config["displayheight"]; + cbw = config["width"]; + tgt._alpha = 100; + tgt.back._alpha = 100; + tgt.fs.fs._visible = true; + tgt.fs.ns._visible = false; + } + if(config["largecontrols"] == "true") { + tgt._xscale = tgt._yscale = 200; + if(Stage["displayState"] == "fullScreen") { + tgt._y = Stage.height - 60; + cbw = 300; + tgt._x = Math.round(Stage.width/2 - 300); + } else { + cbw /= 2; + } + } + tgt.back._width = cbw; + // all buttons + if(feeder.feed.length < 2) { + tgt.prev._visible = tgt.next._visible = false; + tgt.scrub.shd._width = cbw-17; + tgt.scrub._x = 17; + } else { + tgt.prev._visible = tgt.next._visible = true; + tgt.scrub.shd._width = cbw-51; + tgt.scrub._x = 51; + } + if(config['showstop'] == 'true') { + tgt.scrub.shd._width -= 17; + tgt.scrub._x += 17; + } else { + tgt.stop._visible = false; + tgt.prev._x = 17; + tgt.next._x = 34; + } + var xp = cbw; + if(cbw > 50) { + xp -= 37; + tgt.scrub.shd._width -= 37; + tgt.vol._x = xp; + } else { + xp -= 1; + tgt.scrub.shd._width -= 1; + tgt.vol._x = xp; + } + if (feeder.audio == true) { + xp -= 17; + tgt.scrub.shd._width -= 17; + tgt.au._x = xp; + tgt.au._visible = true; + } else { + tgt.au._visible = false; + } + if (feeder.captions == true) { + xp -= 17; + tgt.scrub.shd._width -= 17; + tgt.cc._x = xp; + tgt.cc._visible = true; + } else { + tgt.cc._visible = false; + } + if (config["showdownload"] == "true") { + xp -= 17; + tgt.scrub.shd._width -= 17; + tgt.dl._x = xp; + } else { + tgt.dl._visible = false; + } + if((Stage["displayState"] == undefined || + config["usefullscreen"] == "false" || + feeder.onlymp3s == true) && + config["fsbuttonlink"] == undefined) { + tgt.fs._visible = false; + } else { + xp -= 18; + tgt.scrub.shd._width -= 18; + tgt.fs._x = xp; + } + if(config["showdigits"] == "false" || tgt.scrub.shd._width < 120 || + System.capabilities.version.indexOf("7,0,") > -1) { + tgt.scrub.elpTxt._visible = tgt.scrub.remTxt._visible = false; + tgt.scrub.bar._x = tgt.scrub.bck._x = tgt.scrub.icn._x = 5; + barWidths = tgt.scrub.bck._width = tgt.scrub.shd._width - 10; + } else { + tgt.scrub.elpTxt._visible = tgt.scrub.remTxt._visible = true; + tgt.scrub.bar._x = tgt.scrub.bck._x = tgt.scrub.icn._x = 42; + barWidths = tgt.scrub.bck._width = tgt.scrub.shd._width - 84; + tgt.scrub.remTxt._x = tgt.scrub.shd._width - 39; + } + tgt.scrub.bar._width = 0; + }; + + + /** Show and hide the play/pause button and show activity icon **/ + private function setState(stt:Number) { + var tgt = config["clip"].controlbar.playpause; + switch(stt) { + case 0: + tgt.ply._visible = true; + tgt.pas._visible = false; + break; + case 1: + tgt.pas._visible = true; + tgt.ply._visible = false; + break; + case 2: + tgt.pas._visible = true; + tgt.ply._visible = false; + break; + } + }; + + + /** Print current time to controlBar **/ + private function setTime(elp:Number,rem:Number) { + itemLength = elp + rem; + itemProgress = Math.round(rem/(itemLength)*100); + var tgt = config["clip"].controlbar.scrub; + var w = Math.floor(elp/(elp+rem)*barWidths) - 2; + if(rem > 0) { + tgt.icn._visible = true; + tgt.bar._visible = true; + elp == 0 || w < 2 ? tgt.bar._width = 0: tgt.bar._width = w - 2; + tgt.icn._x = tgt.bar._width + tgt.bar._x + 1; + } else { + tgt.icn._visible = false; + tgt.bar._visible = false; + } + tgt.elpTxt.text = StringMagic.addLeading(elp/60) + ":" + + StringMagic.addLeading(elp%60); + if(tgt.bck._width == barWidths) { + if(config['showdigits'] == "total") { + tgt.remTxt.text = StringMagic.addLeading((elp+rem)/60)+ ":" + + StringMagic.addLeading((elp+rem)%60); + } else { + tgt.remTxt.text = StringMagic.addLeading(rem/60)+ ":" + + StringMagic.addLeading(rem%60); + } + } + }; + + + /** New item is loaded **/ + private function setItem(prm:Number) { + wasLoaded = false; + currentItem = prm; + config["clip"].controlbar.scrub.icn._alpha = 100; + }; + + + /** Print current buffer amount to controlbar **/ + private function setLoad(pct:Number) { + var tgt = config["clip"].controlbar.scrub; + if(wasLoaded == false) { + tgt.bck._width = Math.round(barWidths*pct/100); + } + tgt.remTxt.text = Math.round(pct)+" %"; + pct == 100 ? wasLoaded = true: null; + }; + + + /** Reflect current volume in volumebar **/ + private function setVolume(pr1:Number) { + var tgt = config["clip"].controlbar.vol; + tgt.msk._width = Math.round(pr1/5); + if(pr1 == 0) { + tgt.icn._alpha = 40; + } else { + tgt.icn._alpha = 100; + } + }; + + + /** Catches stage resizing **/ + public function onResize() { + if(config['displayheight'] > config["height"]+10) { + config["height"] = config["displayheight"] = Stage.height; + config["width"] = Stage.width; + if(config['displaywidth'] == config["width"]) { + config["displaywidth"] = Stage.width; + } + } + setDimensions(); + }; + + + /** Catches fullscreen escape **/ + public function onFullScreen(fs:Boolean) { + if(fs == false) { + setDimensions(); + Animations.fadeIn(config['clip'].controlbar); + } else { + hideInt = setInterval(this,"hideBar",1000); + } + }; + + + /** after a delay, the controlbar is hidden **/ + private function hideBar() { + Animations.fadeOut(config['clip'].controlbar); + Mouse.hide(); + clearInterval(hideInt); + } + + + /** Mouse move shows controlbar **/ + public function onMouseMove() { + Mouse.show(); + if(config["displayheight"] == config["height"]-config['searchbar'] || + Stage["displayState"] == "fullScreen") { + Animations.fadeIn(config['clip'].controlbar); + clearInterval(hideInt); + if(!config["clip"].controlbar.hitTest(_root._xmouse,_root._ymouse)) { + hideInt = setInterval(this,"hideBar",500); + } + } + }; + + + public function onFeedUpdate(typ:String) { + setDimensions(); + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/DisplayView.as b/media/source/com/jeroenwijering/players/DisplayView.as new file mode 100644 index 0000000..24c73cd --- /dev/null +++ b/media/source/com/jeroenwijering/players/DisplayView.as @@ -0,0 +1,271 @@ +/** +* Display user interface management of the players MCV pattern. +* +* @author Jeroen Wijering +* @version 1.8 +**/ + + +import com.jeroenwijering.players.*; +import com.jeroenwijering.utils.*; + + +class com.jeroenwijering.players.DisplayView extends AbstractView { + + + /** reference to the imageloader object **/ + private var imageLoader:ImageLoader; + /** Reference to the currently active item **/ + private var currentItem; + /** Reference to the currently active item **/ + private var itemSize:Array; + /** Reference to the currently active item **/ + private var thumbSize:Array; + /** Starting position of the players **/ + private var startPos:Array; + + + /** Constructor **/ + function DisplayView(ctr:AbstractController,cfg:Object,fed:Object) { + super(ctr,cfg,fed); + Stage.addListener(this); + itemSize = new Array(config['displaywidth'],config['displayheight']); + thumbSize = new Array(config['displaywidth'],config['displayheight']); + var ref = this; + var tgt = config["clip"]; + imageLoader = new ImageLoader(tgt.display.thumb); + imageLoader.onLoadFinished = function() { + ref.thumbSize = new Array(this.targetClip._width, + this.targetClip._height); + ref.scaleClip(tgt.display.thumb,this.targetClip._width, + this.targetClip._height); + } + startPos = new Array(tgt._x,tgt._y); + setColorsClicks(); + setDimensions(); + }; + + + /** Sets up colors and clicks of all display items. **/ + private function setColorsClicks() { + var ref = this; + // background + var tgt = config["clip"].back; + tgt.col = new Color(tgt); + tgt.col.setRGB(config["backcolor"]); + // display items + tgt = config["clip"].display; + tgt.col = new Color(tgt.back); + tgt.col.setRGB(config["screencolor"]); + tgt.setMask(config["clip"].mask); + if(config["showicons"] == "false") { + tgt.playicon._visible = false; + tgt.muteicon._visible = false; + } + tgt.activity._visible = false; + tgt.link.tabEnabled = false; + if(config["autostart"] == "muted") { + tgt.link.onRelease = function() { + ref.sendEvent("volume",80); + ref.firstClick(); + }; + } else if (config["autostart"] == "false") { + tgt.muteicon._visible = false; + tgt.link.onRelease = function() { + ref.sendEvent("playpause"); + ref.firstClick(); + }; + } else { + ref.firstClick(); + } + if(config["logo"] != "undefined") { + var lll = new ImageLoader(tgt.logo,"none"); + lll.onLoadFinished = function() { + tgt.logo._x = ref.config["displaywidth"] - + tgt.logo._width - 10; + tgt.logo._y = 10; + }; + lll.loadImage(config["logo"]); + } + }; + + + /** Sets up dimensions of all controlbar items. **/ + private function setDimensions() { + var tgt = config["clip"].back; + if(Stage["displayState"] == "fullScreen") { + config["clip"]._x = config["clip"]._y = 0; + tgt._width = Stage.width; + tgt._height = Stage.height; + } else { + config["clip"]._x = startPos[0]; + config["clip"]._y = startPos[1]; + tgt._width = config["width"]; + tgt._height = config["height"]; + } + tgt = config["clip"].display; + scaleClip(tgt.thumb,thumbSize[0],thumbSize[1]); + scaleClip(tgt.image,itemSize[0],itemSize[1]); + scaleClip(tgt.video,itemSize[0],itemSize[1]); + if(Stage["displayState"] == "fullScreen") { + tgt.youtube.setSize(Stage.width,Stage.height); + } else { + tgt.youtube.setSize(config['displaywidth'],config['displayheight']); + } + if(Stage["displayState"] == "fullScreen") { + config["clip"].mask._width = + tgt.back._width = tgt.link._width = Stage.width; + config["clip"].mask._height = + tgt.back._height = tgt.link._height = Stage.height; + } else { + config["clip"].mask._width = + tgt.back._width = tgt.link._width = config["displaywidth"]; + config["clip"].mask._height = + tgt.back._height = tgt.link._height = config["displayheight"]; + } + tgt.playicon._x = tgt.activity._x = tgt.muteicon._x = + Math.round(tgt.back._width/2); + tgt.playicon._y = tgt.activity._y = tgt.muteicon._y = + Math.round(tgt.back._height/2); + if(Stage["displayState"] == "fullScreen") { + tgt.playicon._xscale = tgt.playicon._yscale = + tgt.muteicon._xscale = tgt.muteicon._yscale = + tgt.activity._xscale = tgt.activity._yscale = + tgt.logo._xscale = tgt.logo._yscale = 200; + tgt.logo._x = Stage.width - tgt.logo._width - 20; + tgt.logo._y = 20; + } else { + tgt.playicon._xscale = tgt.playicon._yscale = + tgt.muteicon._xscale = tgt.muteicon._yscale = + tgt.activity._xscale = tgt.activity._yscale = + tgt.logo._xscale = tgt.logo._yscale = 100; + if(tgt.logo._height > 1) { + tgt.logo._x= config["displaywidth"]-tgt.logo._width -10; + tgt.logo._y = 10; + } + } + }; + + + /** Show and hide the play/pause button and show activity icon **/ + private function setState(stt:Number) { + var tgt = config["clip"].display; + switch(stt) { + case 0: + if (config["linkfromdisplay"] == "false" && + config["showicons"] == "true") { + tgt.playicon._visible = true; + } + tgt.activity._visible = false; + break; + case 1: + tgt.playicon._visible = false; + if (config["showicons"] == "true" && feeder.feed[currentItem]['type'] != 'youtube') { + tgt.activity._visible = true; + } + break; + case 2: + tgt.playicon._visible = false; + tgt.activity._visible = false; + break; + } + }; + + + /** save size information and rescale accordingly **/ + private function setSize(wid:Number,hei:Number) { + itemSize = new Array (wid,hei); + var tgt = config["clip"].display; + scaleClip(tgt.image,itemSize[0],itemSize[1]); + scaleClip(tgt.video,itemSize[0],itemSize[1]); + tgt.youtube.setSize(config['displaywidth'],config['displayheight']); + }; + + + /** Scale movie according to overstretch setting **/ + private function scaleClip(tgt:MovieClip,wid:Number,hei:Number):Void { + var tcf = tgt.mc._currentframe; + tgt.mc.gotoAndStop(1); + var stw = config["displaywidth"]; + var sth = config["displayheight"]; + if(Stage["displayState"] == "fullScreen") { + stw = Stage.width; + sth = Stage.height; + } + var xsr = stw/wid; + var ysr = sth/hei; + var mxm = Math.max(xsr,ysr); + if ((Math.abs(xsr-ysr)/mxm < 0.1 && config["overstretch"] != "none") + || config["overstretch"] == "fit") { + tgt._width = stw; + tgt._height = sth; + } else if (xsr < ysr && config["overstretch"] == "false" || + ysr < xsr && config["overstretch"] == "true") { + tgt._width = wid*xsr; + tgt._height = hei*xsr; + } else if(config["overstretch"] == "none") { + tgt._width = wid; + tgt._height = hei; + } else { + tgt._width = wid*ysr; + tgt._height = hei*ysr; + } + tgt._x = stw/2 - tgt._width/2; + tgt._y = sth/2 - tgt._height/2; + tgt.mc.gotoAndPlay(tcf); + }; + + + /** Load Thumbnail image if available. **/ + private function setItem(idx:Number) { + currentItem = idx; + var tgt = config["clip"].display; + if(feeder.feed[idx]["image"] == "undefined") { + tgt.thumb.clear(); + tgt.thumb._visible = false; + } else { + imageLoader.loadImage(feeder.feed[idx]["image"]); + tgt.thumb._visible = true; + } + }; + + + /** OnResize Handler: catches stage resizing **/ + public function onResize() { + if(config['displayheight'] >= config["height"]) { + config["height"] = config["displayheight"] = Stage.height; + if(config['displaywidth'] == config["width"]) { + config["displaywidth"] = Stage.width; + } + config["width"] = Stage.width; + } + setDimensions(); + }; + + + /** Catches fullscreen escape **/ + public function onFullScreen(fs:Boolean) { + if(fs == false) { setDimensions(); } + }; + + + /** Catches the first display click to reset unmute / displayclick **/ + private function firstClick() { + var ref = this; + var tgt = config["clip"].display; + tgt.playicon._visible = false; + tgt.muteicon._visible = false; + if(config["linkfromdisplay"] == "true") { + tgt.link.onRelease = function() { + ref.sendEvent("getlink",ref.currentItem); + }; + } else { + tgt.link.onRelease = function() { + ref.sendEvent("playpause",1); + }; + } + + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/EqualizerView.as b/media/source/com/jeroenwijering/players/EqualizerView.as new file mode 100644 index 0000000..2fd2e5d --- /dev/null +++ b/media/source/com/jeroenwijering/players/EqualizerView.as @@ -0,0 +1,98 @@ +/** +* View for an actionscript-drawn equalizer (thanks to Brewer). +* The eq. is fake, but it considers playstate and volume. +* +* @author Jeroen Wijering +* @version 1.1 +**/ + + +import com.jeroenwijering.players.*; + + +class com.jeroenwijering.players.EqualizerView extends AbstractView { + + + /** EQ movieclip reference **/ + private var eqClip:MovieClip; + /** current volume **/ + private var currentVolume:Number; + /** number of stripes to display in the EQ **/ + private var eqStripes:Number; + + + /** Constructor; just inheriting. **/ + function EqualizerView(ctr:AbstractController,cfg:Object,fed:Object) { + super(ctr,cfg,fed); + setupEQ(); + Stage.addListener(this); + }; + + + /** setup EQ **/ + private function setupEQ() { + eqClip = config["clip"].equalizer; + eqClip._y = config["displayheight"] - 50; + eqStripes = Math.floor((config['displaywidth'] - 20)/6); + eqClip.stripes.duplicateMovieClip("stripes2",1); + eqClip.mask.duplicateMovieClip("mask2",3); + eqClip.stripes._width = eqClip.stripes2._width = + config['displaywidth']-20; + eqClip.stripes.top.col = new Color(eqClip.stripes.top); + eqClip.stripes.top.col.setRGB(config['lightcolor']); + eqClip.stripes.bottom.col = new Color(eqClip.stripes.bottom); + eqClip.stripes.bottom.col.setRGB(0xFFFFFF); + eqClip.stripes2.top.col = new Color(eqClip.stripes2.top); + eqClip.stripes2.top.col.setRGB(config['lightcolor']); + eqClip.stripes2.bottom.col = new Color(eqClip.stripes2.bottom); + eqClip.stripes2.bottom.col.setRGB(0xFFFFFF); + eqClip.stripes.setMask(eqClip.mask); + eqClip.stripes2.setMask(eqClip.mask2); + eqClip.stripes._alpha = eqClip.stripes2._alpha = 50; + setInterval(this,"drawEqualizer",100,eqClip.mask); + setInterval(this,"drawEqualizer",100,eqClip.mask2); + }; + + + /** Draw a random frame for the equalizer **/ + private function drawEqualizer(tgt:MovieClip) { + tgt.clear(); + tgt.beginFill(0x000000, 100); + tgt.moveTo(0,0); + var h = Math.round(currentVolume/4); + for (var j=0; j< eqStripes; j++) { + var z = random(h)+h/2 + 2; + if(j == Math.floor(eqStripes/2)) { z = 0; } + tgt.lineTo(j*6,-1); + tgt.lineTo(j*6,-z); + tgt.lineTo(j*6+4,-z); + tgt.lineTo(j*6+4,-1); + tgt.lineTo(j*6,-1); + } + tgt.lineTo(eqStripes*6,0); + tgt.lineTo(0,0); + tgt.endFill(); + }; + + + /** Change the height to reflect the volume **/ + private function setVolume(vol:Number) { currentVolume = vol; }; + + + /** Only display the eq if a song is playing **/ + private function setState(stt:Number) { + stt == 2 ? eqClip._visible = true: eqClip._visible = false; + }; + + + /** Hide the EQ on fullscreen view **/ + public function onFullScreen(fs:Boolean) { + if(fs == true) { + eqClip._visible = false; + } else { + eqClip._visible = true; + } + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/FLVModel.as b/media/source/com/jeroenwijering/players/FLVModel.as new file mode 100644 index 0000000..9162eb5 --- /dev/null +++ b/media/source/com/jeroenwijering/players/FLVModel.as @@ -0,0 +1,335 @@ +/** +* FLV model class of the players MCV pattern. +* Handles playback of FLV files, HTTP streams and RTMP streams. +* +* @author Jeroen Wijering +* @version 1.13 +**/ + + +import com.jeroenwijering.players.*; + + +class com.jeroenwijering.players.FLVModel extends AbstractModel { + + + /** array with extensions used by this model **/ + private var mediatypes:Array = new Array( + "flv","rtmp","mp4","m4v","m4a","mov","3gp","3g2"); + /** NetConnection object reference **/ + private var connectObject:NetConnection; + /** NetStream object reference **/ + private var streamObject:NetStream; + /** Sound object reference **/ + private var soundObject:Sound; + /** interval ID of the buffer update function **/ + private var loadedInterval:Number; + /** current percentage of the video that's loaded **/ + private var currentLoaded:Number = 0; + /** interval ID of the position update function **/ + private var positionInterval:Number; + /** current state of the video that is playing **/ + private var currentState:Number; + /** Current volume **/ + private var currentVolume:Number; + /** MovieClip with "display" video Object **/ + private var videoClip:MovieClip; + /** object with keyframe times and positions, saved for PHP streaming **/ + private var metaKeyframes:Object = new Object(); + /** Boolean to check whether a stop event is fired **/ + private var stopFired:Boolean = false; + /** Boolean to check whether a flusk event is fired **/ + private var flushFired:Boolean = false; + /** Switch for FLV type currently played **/ + private var flvType:String; + /** check h264 for time offset **/ + private var isH264:Boolean; + /** check h264 for time offset **/ + private var timeOffset:Number = 0; + /** reference to the captions object for parsing captionate data **/ + public var capView:Object; + /** buffer iterator (prevents buffericon showing on slow PC's) **/ + public var bufferCount:Number = 0; + + + + /** Constructor **/ + function FLVModel(vws:Array,ctr:AbstractController,cfg:Object, + fed:Object,fcl:MovieClip) { + super(vws,ctr,cfg,fed); + connectObject = new NetConnection(); + videoClip = fcl; + if(config["smoothing"] == "false") { + videoClip.display.smoothing = false; + videoClip.display.deblocking = 1; + } else { + videoClip.display.smoothing = true; + videoClip.display.deblocking = 3; + } + videoClip.createEmptyMovieClip("snd",videoClip.getNextHighestDepth()); + soundObject = new Sound(videoClip.snd); + }; + + + /** Check which FLV type we use **/ + private function setItem(idx:Number) { + super.setItem(idx); + if(isActive == true) { + if(config["streamscript"] != undefined) { + flvType = "HTTP"; + } else if(feeder.feed[currentItem]["type"] == "rtmp") { + flvType = "RTMP"; + } else { + flvType = "FLV"; + } + } + }; + + + /** Start a specific video **/ + private function setStart(pos:Number) { + stopFired = false; + flushFired = false; + if (pos != undefined) { currentPosition = pos; } + if(pos < 1) { + pos = 0; + } else if (pos > feeder.feed[currentItem]["duration"] - 1) { + pos = feeder.feed[currentItem]["duration"] - 1; + } + if (flvType=="RTMP" && feeder.feed[currentItem]["id"] != currentURL) { + connectObject.connect(feeder.feed[currentItem]["file"]); + currentURL = feeder.feed[currentItem]["id"]; + setStreamObject(connectObject); + streamObject.play(currentURL); + } else if(flvType != "RTMP" && + feeder.feed[currentItem]["file"] != currentURL) { + connectObject.connect(null); + currentURL = feeder.feed[currentItem]["file"]; + if(flvType == "HTTP" ) { + setStreamObject(connectObject); + if(config["streamscript"] == "lighttpd") { + streamObject.play(currentURL); + } else { + streamObject.play(config["streamscript"] + + "?file=" + currentURL); + } + } else { + setStreamObject(connectObject); + streamObject.play(currentURL); + } + } else { + if(flvType == "HTTP" && pos != undefined) { + playKeyframe(currentPosition); + } else if (flvType != "HTTP" && pos != undefined) { + streamObject.seek(currentPosition); + streamObject.pause(false); + } else if(flvType == "RTMP" && currentPosition > 0 && + feeder.feed[currentItem]["duration"] == 0) { + connectObject.connect(feeder.feed[currentItem]["file"]); + setStreamObject(connectObject); + streamObject.play(currentURL); + } else { + streamObject.pause(false); + } + } + videoClip._visible = true; + videoClip._parent.thumb._visible = false; + clearInterval(positionInterval); + positionInterval = setInterval(this,"updatePosition",100); + clearInterval(loadedInterval); + loadedInterval = setInterval(this,"updateLoaded",100); + }; + + + /** Read and broadcast the amount of the flv that's currently loaded **/ + private function updateLoaded() { + var pct:Number = Math.round(streamObject.bufferLength/ + streamObject.bufferTime*100); + if(flvType == "FLV") { + pct = Math.round(streamObject.bytesLoaded/ + streamObject.bytesTotal*100); + } + if(isNaN(pct)) { + currentLoaded = 0; + sendUpdate("load",0); + } else if (pct > 95) { + clearInterval(loadedInterval); + currentLoaded = 100; + sendUpdate("load",100); + } else if (pct != currentLoaded) { + currentLoaded= pct; + sendUpdate("load",currentLoaded); + } + }; + + + /** Read and broadcast the current position of the song **/ + private function updatePosition() { + var pos = streamObject.time + timeOffset; + if(pos == currentPosition && currentState != 1 && stopFired != true) { + if(bufferCount == 5) { + currentState = 1; + sendUpdate("state",1); + bufferCount = 0; + } else { + bufferCount++; + } + } else if (pos != currentPosition && currentState != 2) { + bufferCount = 0; + currentState = 2; + sendUpdate("state",2); + } else { + bufferCount = 0; + } + if (pos != currentPosition) { + currentPosition = pos; + sendUpdate("time",currentPosition, + Math.max(feeder.feed[currentItem]["duration"]-currentPosition,0)); + } else if (stopFired == true || + (flushFired == true && flvType != "RTMP" && bufferCount == 5)) { + currentState = 3; + videoClip._visible = false; + videoClip._parent.thumb._visible = true; + sendUpdate("state",3); + sendCompleteEvent(); + stopFired = false; + flushFired = false; + } + }; + + + /** Pause the video that's currently playing. **/ + private function setPause(pos:Number) { + if(pos < 1) { pos = 0; } + clearInterval(positionInterval); + if(pos != undefined) { + currentPosition = pos; + sendUpdate("time",currentPosition, + Math.abs(feeder.feed[currentItem]["duration"]-currentPosition)); + streamObject.seek(currentPosition); + } + streamObject.pause(true); + currentState = 0; + sendUpdate("state",0); + }; + + + /** Stop video and clear data. **/ + private function setStop(pos:Number) { + clearInterval(loadedInterval); + clearInterval(positionInterval); + videoClip._visible = false; + delete currentURL; + delete currentLoaded; + delete currentPosition; + delete metaKeyframes; + currentLoaded = 0; + stopFired = false; + timeOffset = 0; + streamObject.close(); + delete streamObject; + videoClip.display.clear(); + }; + + + /** Set volume of the sound object. **/ + private function setVolume(vol:Number) { + super.setVolume(vol); + currentVolume = vol; + soundObject.setVolume(vol); + }; + + + /** Connect a new stream object to video/audio/callbacks **/ + private function setStreamObject(cnt:NetConnection) { + _root.tf.text = 'metadata!'; + var ref = this; + currentLoaded = 0; + sendUpdate("load",0); + streamObject = new NetStream(cnt); + streamObject.setBufferTime(config["bufferlength"]); + streamObject.onMetaData = function(obj) { + for (var i in obj) { + trace(i+': '+obj[i]); + } + if(obj.duration > 1) { + ref.feeder.feed[ref.currentItem]["duration"] = obj.duration; + } + if(obj.width > 10) { + ref.sendUpdate("size",obj.width,obj.height); + } + if(obj.seekpoints != undefined) { + ref.isH264 = true; + ref.metaKeyframes = new Object(); + ref.metaKeyframes.times = new Array(); + ref.metaKeyframes.filepositions = new Array(); + for (var j in obj.seekpoints) { + ref.metaKeyframes.times.unshift( + Number(obj.seekpoints[j]['time'])); + ref.metaKeyframes.filepositions.unshift( + Number(obj.seekpoints[j]['time'])); + } + } else { + ref.metaKeyframes = obj.keyframes; + } + if(ref.feeder.feed[ref.currentItem]['start'] > 0) { + if(ref.flvType == "HTTP") { + ref.playKeyframe(ref.feeder.feed[ref.currentItem]['start']); + } else if (ref.flvType == "RTMP") { + ref.setStart(ref.feeder.feed[ref.currentItem]['start']); + } + } + delete obj; + delete this.onMetaData; + }; + streamObject.onStatus = function(object) { + trace("status: "+object.code); + if(object.code == "NetStream.Play.Stop" && ref.flvType!='RTMP') { + ref.stopFired = true; + } else if (object.code == "NetStream.Play.StreamNotFound") { + ref.currentState = 3; + ref.videoClip._visible = false; + ref.sendUpdate("state",3); + ref.sendCompleteEvent(); + ref.stopFired = false; + ref.flushFired = false; + } else if (object.code == "NetStream.Buffer.Flush") { + ref.flushFired = true; + } + }; + streamObject.onPlayStatus = function(object) { + if( object.code == "NetStream.Play.Complete" || + object.code == "NetStream.Play.Stop") { + ref.stopFired = true; + } + }; + streamObject.onCaption = function(cap:Array) { + ref.capView.onCaptionate(cap); + }; + videoClip.display.attachVideo(streamObject); + videoClip.snd.attachAudio(streamObject); + }; + + + /** Play from keyframe position from metadata **/ + private function playKeyframe(pos:Number) { + for (var i=0; i< metaKeyframes.times.length; i++) { + if((metaKeyframes.times[i] <= pos) && + (metaKeyframes.times[i+1] >= pos)) { + if(config["streamscript"] == "lighttpd") { + streamObject.play(currentURL+"?start="+ + metaKeyframes.filepositions[i]); + if(isH264 == true) { + timeOffset = metaKeyframes.filepositions[i]; + } + } else { + streamObject.play(config["streamscript"]+"?file="+ + currentURL+"&pos="+metaKeyframes.filepositions[i]); + } + break; + } + } + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/ImageModel.as b/media/source/com/jeroenwijering/players/ImageModel.as new file mode 100644 index 0000000..73bb61a --- /dev/null +++ b/media/source/com/jeroenwijering/players/ImageModel.as @@ -0,0 +1,151 @@ +/** +* Image model class of the players MCV pattern. +* +* @author Jeroen Wijering +* @version 1.5 +**/ + + +import com.jeroenwijering.players.*; +import com.jeroenwijering.utils.ImageLoader; + + +class com.jeroenwijering.players.ImageModel extends AbstractModel { + + + /** array with extensions used by this model **/ + private var mediatypes:Array = new Array("jpg","gif","png","swf"); + /** ImageLoader instance **/ + private var imageLoader:ImageLoader; + /** Clip to load the image into **/ + private var imageClip:MovieClip; + /** interval ID of image duration function **/ + private var positionInterval:Number; + /** current state **/ + private var currentState:Number; + /** boolean to check for current SWF **/ + private var isSWF:Boolean; + + + /** Constructor **/ + function ImageModel(vws:Array,ctr:AbstractController,cfg:Object, + fed:Object,imc:MovieClip,scl:Boolean) { + super(vws,ctr,cfg,fed); + imageClip = imc; + var ref = this; + if(arguments[5] == true) { + imageLoader = new ImageLoader(imageClip,config["overstretch"], + config["width"],config["height"]); + } else { + imageLoader = new ImageLoader(imageClip); + } + imageLoader.onLoadFinished = function() { + ref.currentState = 2; + ref.sendUpdate("state",2); + ref.sendUpdate("load",100); + }; + imageLoader.onLoadProgress = function(tgt,btl,btt) { + ref.sendUpdate("load",Math.round(btl/btt*100)); + }; + imageLoader.onMetaData = function() { + ref.sendUpdate("size",this.sourceWidth,this.sourceHeight); + if(this.sourceLength > ref.feeder.feed[ref.currentItem]["duration"]) { + ref.feeder.feed[ref.currentItem]["duration"] = this.sourceLength; + } + }; + }; + + + /** Start display interval for a specific image **/ + private function setStart(pos:Number) { + if(pos < 1 ) { + pos = 0; + } else if (pos > feeder.feed[currentItem]["duration"] - 1) { + pos = feeder.feed[currentItem]["duration"] - 1; + } + clearInterval(positionInterval); + if(feeder.feed[currentItem]["file"] != currentURL) { + imageClip._visible = true; + currentURL = feeder.feed[currentItem]["file"]; + if(feeder.feed[currentItem]["file"].indexOf(".swf") == -1) { + isSWF = false; + } else { + isSWF = true; + } + imageLoader.loadImage(feeder.feed[currentItem]["file"]); + currentState = 1; + sendUpdate("state",1); + sendUpdate("load",0); + } else { + currentState = 2; + sendUpdate("state",2); + } + if (pos != undefined) { + currentPosition = pos; + isSWF == true ? imageClip.mc.gotoAndPlay(pos*20): null; + if(pos == 0) {sendUpdate("time",0,feeder.feed[currentItem]["duration"]); } + } else { + isSWF == true ? imageClip.mc.play(): null; + } + positionInterval = setInterval(this,"updatePosition",100); + }; + + + /** Read and broadcast the current position of the song **/ + private function updatePosition() { + if(currentState == 2) { + currentPosition += 0.1; + if(currentPosition >= feeder.feed[currentItem]["duration"]) { + currentState = 3; + sendUpdate("state",3); + sendCompleteEvent(); + } else { + sendUpdate("time",currentPosition,feeder.feed[currentItem]["duration"]-currentPosition); + } + } + }; + + + /** stop the image display interval **/ + private function setPause(pos:Number) { + if(pos < 1 ) { + pos = 0; + } else if (pos > feeder.feed[currentItem]["duration"] - 1) { + pos = feeder.feed[currentItem]["duration"] - 1; + } + clearInterval(positionInterval); + currentState = 0; + sendUpdate("state",0); + if(pos != undefined) { + currentPosition = pos; + sendUpdate("time",currentPosition,feeder.feed[currentItem]["duration"]-currentPosition); + isSWF == true ? imageClip.mc.gotoAndStop(pos*20+1): null; + } else { + isSWF == true ? imageClip.mc.stop(): null; + } + }; + + + /** stop display of the item altogether **/ + private function setStop() { + delete currentURL; + clearInterval(positionInterval); + currentPosition = 0; + isSWF == true ? imageClip.mc.gotoAndStop(1): null; + if (imageClip.bg == undefined) { + imageClip.mc.removeMovieClip(); + imageClip.smc.removeMovieClip(); + imageClip._visible = false; + } + }; + + + /** set duration to rotatetime for images **/ + private function setItem(idx:Number) { + super.setItem(idx); + if(feeder.feed[currentItem]["duration"] == 0 && isActive == true) { + feeder.feed[currentItem]["duration"] = config['rotatetime']; + } + } + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/ImageRotator.as b/media/source/com/jeroenwijering/players/ImageRotator.as new file mode 100644 index 0000000..89d7bf3 --- /dev/null +++ b/media/source/com/jeroenwijering/players/ImageRotator.as @@ -0,0 +1,94 @@ +/** +* Manages startup and overall control of the Flash Image Rotator +* +* @author Jeroen Wijering +* @version 1.7 +**/ + + +import com.jeroenwijering.players.*; + + +class com.jeroenwijering.players.ImageRotator extends AbstractPlayer { + + + /** Array with all config values **/ + public var config:Object = { + clip:undefined, + height:200, + width:400, + + file:undefined, + image:undefined, + link:undefined, + id:undefined, + type:undefined, + captions:undefined, + audio:undefined, + + backcolor:0x000000, + frontcolor:0xffffff, + lightcolor:0xffffff, + screencolor:0x000000, + + kenburns:"false", + logo:undefined, + overstretch:"false", + showicons:"true", + shownavigation:"true", + transition:"random", + + autostart:"true", + repeat:"true", + rotatetime:5, + shuffle:"true", + volume:80, + + enablejs:"false", + javascriptid:undefined, + linkfromdisplay:"false", + linktarget:"_self", + useaudio:"true", + + abouttxt:"JW Image Rotator 3.16", + aboutlnk:"http://www.jeroenwijering.com/?about=JW_Image_Rotator" + }; + + + /** Constructor **/ + function ImageRotator(tgt:MovieClip) { + super(tgt); + }; + + + /** Setup all necessary MCV blocks. **/ + private function setupMCV():Void { + controller = new RotatorController(config,feeder); + var rov = new RotatorView(controller,config,feeder); + var ipv = new InputView(controller,config,feeder); + var vws:Array = new Array(rov,ipv); + if(config["enablejs"] == "true") { + var jsv = new JavascriptView(controller,config,feeder); + vws.push(jsv); + } + if(feeder.audio == true) { + var bav = new AudioView(controller,config,feeder,false); + vws.push(bav); + } + config["displayheight"] = config["height"]; + var im1=new ImageModel(vws,controller,config,feeder, + config["clip"].img1,true); + var im2=new ImageModel(vws,controller,config,feeder, + config["clip"].img2,true); + var mds:Array = new Array(im1,im2); + controller.startMCV(mds); + }; + + + /** Application startup, used for MTASC compilation **/ + public static function main() { + var irt = new ImageRotator(_root.rotator); + } + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/InputView.as b/media/source/com/jeroenwijering/players/InputView.as new file mode 100644 index 0000000..c5a36d3 --- /dev/null +++ b/media/source/com/jeroenwijering/players/InputView.as @@ -0,0 +1,63 @@ +/** +* Keyboard input management of the players MCV pattern. +* SPACE: playpause,UP:prev,DOWN:next,LEFT:volume-10,RIGHT:volume+10 +* +* @author Jeroen Wijering +* @version 1.3 +**/ + + +import com.jeroenwijering.players.*; + + +class com.jeroenwijering.players.InputView extends AbstractView { + + + /** The current volume **/ + private var currentVolume:Number; + /** The current elapsed time **/ + private var currentTime:Number; + + + /** Constructor **/ + function InputView(ctr:AbstractController,cfg:Object,fed:Object) { + super(ctr,cfg,fed); + Key.addListener(this); + }; + + + /** Save current elapsed time **/ + private function setTime(elp:Number,rem:Number) { currentTime = elp; }; + + + /** Save current volume **/ + private function setVolume(vol:Number) { currentVolume = vol; }; + + + /** KeyDown handler, forwarded by Key object **/ + public function onKeyDown() { + if (Key.getCode() == 32 && SearchView.focussed != true) { + sendEvent("playpause"); + } else if (Key.getCode() == 37) { + if(feeder.feed.length == 1) { + sendEvent("scrub",currentTime-15); + } else { + sendEvent("prev"); + } + } else if (Key.getCode() == 39) { + if(feeder.feed.length == 1) { + sendEvent("scrub",currentTime+15); + } else { + sendEvent("next"); + } + } else if (Key.getCode() == 38) { + sendEvent("volume",currentVolume+10); + } else if (Key.getCode() == 40) { + sendEvent("volume",currentVolume-10); + } else if (Key.getCode() == 77) { + sendEvent("volume",0); + } + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/JavascriptView.as b/media/source/com/jeroenwijering/players/JavascriptView.as new file mode 100644 index 0000000..1b44ee4 --- /dev/null +++ b/media/source/com/jeroenwijering/players/JavascriptView.as @@ -0,0 +1,90 @@ +/** +* Javascript user interface management of the players MCV pattern. +* +* @author Jeroen Wijering +* @version 1.5 +**/ + + +import com.jeroenwijering.players.*; +import flash.external.ExternalInterface; + + +class com.jeroenwijering.players.JavascriptView extends AbstractView { + + + /** Previous loading value **/ + private var loads:Number; + /** Previous elapsed value **/ + private var elaps:Number; + /** Previous remaining value **/ + private var remain:Number; + /** Previous state value **/ + private var state:Number = 4; + /** Status change abbreviations **/ + private var statuses:Array = new Array( + 'PAUSED', + 'BUFFERING', + 'PLAYING', + 'COMPLETE', + 'NOT STARTED' + ); + + + /** Constructor **/ + function JavascriptView(ctr:AbstractController,cfg:Object,fed:Object) { + super(ctr,cfg,fed); + if(ExternalInterface.available) { + ExternalInterface.addCallback("sendEvent",this,sendEvent); + } + }; + + + /** Override of the update receiver; forwarding all to javascript **/ + public function getUpdate(typ:String,pr1:Number,pr2:Number) { + if(ExternalInterface.available) { + switch(typ) { + case "load": + if(Math.round(pr1) != loads) { + loads = Math.round(pr1); + ExternalInterface.call("getUpdate",typ,loads,pr2, + config["javascriptid"]); + } + break; + case "time": + if(Math.round(pr1)!=elaps || Math.round(pr2)!=remain) { + elaps = Math.round(pr1); + remain = Math.round(pr2); + ExternalInterface.call("getUpdate",typ,elaps,remain, + config["javascriptid"]); + } + break; + case "item": + ExternalInterface.call("getUpdate",typ,pr1,pr2, + config["javascriptid"]); + break; + case "state": + sendStatusChange(pr1); + ExternalInterface.call("getUpdate",typ,pr1,pr2, + config["javascriptid"]); + break; + default: + ExternalInterface.call("getUpdate",typ,pr1,pr2, + config["javascriptid"]); + break; + } + } + }; + + + /** state change function for longtail **/ + private function sendStatusChange(stt) { + if(!(state == 3 && stt == 0)) { + ExternalInterface.call("playerStatusChange", + statuses[state],statuses[stt]); + } + state = stt; + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/MP3Model.as b/media/source/com/jeroenwijering/players/MP3Model.as new file mode 100644 index 0000000..1e53b2c --- /dev/null +++ b/media/source/com/jeroenwijering/players/MP3Model.as @@ -0,0 +1,154 @@ +/** +* MP3 model class of the players MCV pattern. +* +* @author Jeroen Wijering +* @version 1.4 +**/ + + +import com.jeroenwijering.players.*; + + +class com.jeroenwijering.players.MP3Model extends AbstractModel { + + + /** array with extensions used by this model **/ + private var mediatypes:Array = new Array("mp3","rbs"); + /** Sound instance **/ + private var soundObject:Sound; + /** MovieClip to apply the sound object to **/ + private var soundClip:MovieClip; + /** interval ID of the buffer update function **/ + private var loadedInterval:Number; + /** currently loaded percentage **/ + private var currentLoaded:Number = 0; + /** interval ID of the position update function **/ + private var positionInterval:Number; + /** current state of the sound that is playing **/ + private var currentState:Number; + /** Current volume **/ + private var currentVolume:Number; + + + /** Constructor **/ + function MP3Model(vws:Array,ctr:AbstractController, + cfg:Object,fed:Object,scl:MovieClip) { + super(vws,ctr,cfg,fed); + soundClip = scl; + }; + + + /** Start a specific sound **/ + private function setStart(pos:Number) { + if(pos < 1 ) { + pos = 0; + } else if (pos > feeder.feed[currentItem]["duration"] - 1) { + pos = feeder.feed[currentItem]["duration"] - 1; + } + clearInterval(positionInterval); + if(feeder.feed[currentItem]["file"] != currentURL) { + var ref = this; + currentURL = feeder.feed[currentItem]["file"]; + soundObject = new Sound(soundClip); + soundObject.onSoundComplete = function() { + ref.currentState = 3; + ref.sendUpdate("state",3); + ref.sendCompleteEvent(); + }; + soundObject.onLoad = function(scs:Boolean) { + if(scs == false) { + ref.currentState = 3; + ref.sendUpdate("state",3); + ref.sendCompleteEvent(); + } + }; + soundObject.loadSound(currentURL,true); + soundObject.setVolume(currentVolume); + sendUpdate("load",0); + loadedInterval = setInterval(this,"updateLoaded",100); + } + if(pos != undefined) { + currentPosition = pos; + if(pos == 0) { sendUpdate("time",0,feeder.feed[currentItem]["duration"]); } + } + soundObject.start(currentPosition); + updatePosition(); + sendUpdate("size",0,0); + positionInterval = setInterval(this,"updatePosition",100); + }; + + + /** Read and broadcast the amount of the mp3 that's currently loaded **/ + private function updateLoaded() { + var pct:Number = Math.round(soundObject.getBytesLoaded() / + soundObject.getBytesTotal()*100); + if(isNaN(pct)) { + currentLoaded = 0; + sendUpdate("load",0); + } else if (pct != currentLoaded) { + sendUpdate("load",pct); + currentLoaded = pct; + } else if(pct >= 100) { + clearInterval(loadedInterval); + currentLoaded = 100; + sendUpdate("load",100); + } + }; + + + /** Read and broadcast the current position of the song **/ + private function updatePosition() { + var pos = soundObject.position/1000; + feeder.feed[currentItem]["duration"] = soundObject.duration/(10*currentLoaded); + if(pos == currentPosition && currentState != 1) { + currentState = 1; + sendUpdate("state",1); + } else if (pos != currentPosition && currentState != 2) { + currentState = 2; + sendUpdate("state",2); + } + if (pos != currentPosition) { + currentPosition = pos; + sendUpdate("time",currentPosition,feeder.feed[currentItem]["duration"]-currentPosition); + } + }; + + + /** Pause the sound that's currently playing. **/ + private function setPause(pos:Number) { + if(pos < 1) { + pos = 0; + } else if (pos > feeder.feed[currentItem]["duration"] - 1) { + pos = feeder.feed[currentItem]["duration"] - 1; + } + soundObject.stop(); + clearInterval(positionInterval); + currentState = 0; + sendUpdate("state",0); + if(pos != undefined) { + currentPosition = pos; + sendUpdate("time",currentPosition,feeder.feed[currentItem]["duration"]-currentPosition); + } + }; + + + /** stop and unload the sound **/ + private function setStop() { + soundObject.stop(); + clearInterval(positionInterval); + clearInterval(loadedInterval); + delete currentURL; + delete soundObject; + currentLoaded = 0; + }; + + + /** Set volume of the sound object. **/ + private function setVolume(vol:Number) { + super.setVolume(vol); + currentVolume = vol; + soundObject.setVolume(vol); + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/MediaPlayer.as b/media/source/com/jeroenwijering/players/MediaPlayer.as new file mode 100644 index 0000000..0d4e323 --- /dev/null +++ b/media/source/com/jeroenwijering/players/MediaPlayer.as @@ -0,0 +1,198 @@ +/** +* Player that reads all media formats Flash can read. +* +* @author Jeroen Wijering +* @version 1.10 +**/ + + +import com.jeroenwijering.players.*; +import com.jeroenwijering.utils.BandwidthCheck; + + +class com.jeroenwijering.players.MediaPlayer extends AbstractPlayer { + + + /** Array with all config values **/ + private var config:Object = { + clip:undefined, + height:260, + width:320, + controlbar:20, + displayheight:undefined, + displaywidth:undefined, + searchbar:'false', + + file:undefined, + fallback:undefined, + image:undefined, + link:undefined, + id:undefined, + type:undefined, + captions:undefined, + audio:undefined, + category:undefined, + + frontcolor:0x000000, + backcolor:0xffffff, + lightcolor:0x000000, + screencolor:0x000000, + + autoscroll:"false", + largecontrols:"false", + logo:undefined, + showdigits:'true', + showdownload:'false', + showeq:'false', + showicons:'true', + shownavigation:'true', + showstop:'false', + thumbsinplaylist:'true', + usefullscreen:'true', + fsbuttonlink:undefined, + + autostart:'false', + bufferlength:3, + overstretch:'false', + repeat:'list', + rotatetime:5, + shuffle:'false', + smoothing:'true', + volume:80, + + bwfile:"100k.jpg", + bwstreams:undefined, + callback:undefined, + enablejs:'false', + javascriptid:'', + linkfromdisplay:'false', + linktarget:'_blank', + midroll:undefined, + prefix:'', + recommendations:undefined, + searchlink:'http://search.longtail.tv/?q=', + streamscript:undefined, + useaudio:'true', + usecaptions:'true', + usemute:'false', + usekeys:'true', + + abouttxt:'JW Player 3.16', + aboutlnk:'http://www.jeroenwijering.com/?about=JW_FLV_Media_Player' + }; + + + /** Constructor **/ + public function MediaPlayer(tgt:MovieClip) { + super(tgt); + }; + + + /** check bandwidth for streaming **/ + private function checkStream() { + var ref = this; + var str = config["bwstreams"].split(","); + var bwc = new BandwidthCheck(config["bwfile"]); + bwc.onComplete = function(kbps) { + trace("bandwidth: "+kbps); + var bwc = new ContextMenuItem("Detected bandwidth: "+kbps+" kbps"); + bwc.separatorBefore = true; + ref.manager.context.customItems.push(bwc); + if(ref.config['enablejs'] == "true" && + flash.external.ExternalInterface.available) { + flash.external.ExternalInterface.call("getBandwidth",kbps); + } + for (var i=1; i 0) { + var sev = new SearchView(controller,config,feeder); + vws.push(sev); + } else { + config["clip"].search._visible = false; + } + if(config['midroll'] != undefined) { + var mrv = new MidrollView(controller,config,feeder); + vws.push(mrv); + } else { + config["clip"].midroll._visible = false; + } + if(feeder.audio == true) { + var adv = new AudioView(controller,config,feeder,true); + vws.push(adv); + } + if(config["enablejs"] == "true") { + var jsv = new JavascriptView(controller,config,feeder); + vws.push(jsv); + } + if(config["callback"] != undefined) { + var cav = new CallbackView(controller,config,feeder); + vws.push(cav); + } + // set models + var mp3 = new MP3Model(vws,controller,config,feeder,config["clip"]); + var flv = new FLVModel(vws,controller,config,feeder,config["clip"].display.video); + var img = new ImageModel(vws,controller,config,feeder,config["clip"].display.image); + var ytm = new YoutubeModel(vws,controller,config,feeder,config["clip"].display.youtube); + var mds:Array = new Array(mp3,flv,img,ytm); + if(feeder.captions == true) { flv.capView = cpv; } + // start mcv cycle + controller.startMCV(mds); + }; + + + /** Application startup, used for MTASC compilation **/ + public static function main() { + var mpl = new MediaPlayer(_root.player); + } + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/MidrollView.as b/media/source/com/jeroenwijering/players/MidrollView.as new file mode 100644 index 0000000..d79dcf7 --- /dev/null +++ b/media/source/com/jeroenwijering/players/MidrollView.as @@ -0,0 +1,319 @@ +/** + +* Displays XML-fed text advertisements. +* +* @author Jeroen Wijering +* @version 1.0 +**/ + + +import com.jeroenwijering.players.*; +import com.jeroenwijering.utils.*; + + +class com.jeroenwijering.players.MidrollView extends AbstractView { + + + /** Prefix for the midroll XML **/ + private var prefix = "http://www.ltassrv.com/midroll/config.asp?cid="; + /** Reference to the XML parser. **/ + private var parser:XMLParser; + /** Reference to the image loader. **/ + private var loader:ImageLoader; + /** A list with all the configuration parameters. **/ + private var adconfig:Object; + /** A list with all the advertisements. **/ + private var advertisements:Array; + /** A reference to the midroll clip **/ + private var clip:MovieClip; + /** Currently active ad **/ + private var currentAd:Number; + /** Current playback time. **/ + private var currentTime:Number; + /** Current playback state **/ + private var currentState:Number; + /** Ad showing interval delay **/ + private var interval:Number; + /** Hae we rotated though all ads? **/ + private var rotated:Boolean; + + + /** Constructor; loads the ads and sets up the display **/ + function MidrollView(ctr:AbstractController,cfg:Object,fed:Object) { + super(ctr,cfg,fed); + var ref = this; + clip = config['clip'].midroll; + clip._visible = false; + parser = new XMLParser(); + parser.onComplete = function() { + ref.saveConfig(this.output['childs'][0]); + ref.saveAds(this.output['childs'][1]); + }; + trace(prefix+config['midroll']); + parser.parse(prefix+config['midroll']); + loader = new ImageLoader(clip.ovl.img.img,'false',50,50); + loader.onLoadFinished = function() { + Animations.fadeIn(ref.clip.ovl.img.img); + }; + Stage.addListener(this); + }; + + + /** Save the configuration options. **/ + function saveConfig(cfg:Object) { + adconfig = new Object(); + for (var i=0; i 0) { sendEvent('playpause'); } + getURL(advertisements[currentAd]['click_url'],'_blank'); + }; + + + /** Change the height to reflect the volume **/ + private function setTime(elp:Number) { + if(elp > adconfig['initial_delay'] && currentAd == undefined) { + currentAd = 0; + showMidroll(); + } + }; + + + /** Set a specific ad in the midroll **/ + private function setAd(idx:Number,man:Boolean) { + if(advertisements[idx]['image'].length > 10) { + clip.ovl.tit._x = clip.ovl.dsc._x = clip.ovl.lnk._x = 68; + clip.ovl.img._visible = true; + loader.loadImage(advertisements[idx]['image']); + clip.ovl.dsc.tf._width = clip.ovl.bck._width - 120; + } else { + clip.ovl.tit._x = clip.ovl.dsc._x = clip.ovl.lnk._x = 8; + clip.ovl.img.img._alpha = 0; + clip.ovl.img._visible = false; + clip.ovl.dsc.tf._width = clip.ovl.bck._width - 60; + } + var num = Math.round((clip.ovl.bck._width - clip.ovl.dsc._x)/6); + var dsc = StringMagic.chopString(advertisements[idx]['description'],num,1); + if( dsc != advertisements[idx]['description']) { dsc += ' ..'; } + Animations.easeText(clip.ovl.tit,advertisements[idx]['title']); + Animations.easeText(clip.ovl.dsc,dsc); + Animations.easeText(clip.ovl.lnk,advertisements[idx]['display_url']); + currentAd = idx; + clearInterval(interval); + if (rotated == true && man != true) { + rotated = false; + hideMidroll(); + idx = 0; + return; + } else if(currentAd == advertisements.length-1) { + if (man != true) { + rotated = true; + } + idx = 0; + } else { + idx++; + } + interval = setInterval(this,'setAd',adconfig['display_duration']*1000,idx); + } + + + /** Only display the eq if a song is playing **/ + private function setState(stt:Number) { + currentState = stt; + if(stt == 3) { + hideMidroll(); + } + }; + + + /** Catches stage resizing **/ + public function onResize() { setDimensions(); }; + + + /** Catches fullscreen escape **/ + public function onFullScreen(fs:Boolean) { + if(fs == false) { setDimensions(); } + }; + + +}; \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/PlayerController.as b/media/source/com/jeroenwijering/players/PlayerController.as new file mode 100644 index 0000000..d2f3bb7 --- /dev/null +++ b/media/source/com/jeroenwijering/players/PlayerController.as @@ -0,0 +1,239 @@ +/** +* User input management of the players MCV pattern. +* +* @author Jeroen Wijering +* @version 1.12 +**/ + + +import com.jeroenwijering.players.AbstractController; +import com.jeroenwijering.utils.Randomizer; +import flash.geom.Rectangle; + + +class com.jeroenwijering.players.PlayerController extends AbstractController { + + + /** use SharedObject to save current file, item and volume **/ + private var playerSO:SharedObject; + /** save independent mute state **/ + private var muted:Boolean; + + + /** Constructor, save arrays and set currentItem. **/ + function PlayerController(cfg:Object,fed:Object) { + super(cfg,fed); + playerSO = SharedObject.getLocal("com.jeroenwijering.players", "/"); + }; + + + /** Complete the build of the MCV cycle and start flow of events. **/ + public function startMCV(mar:Array) { + if(mar != undefined) { registeredModels = mar; } + itemsPlayed = 0; + if(config["shuffle"] == "true") { + randomizer = new Randomizer(feeder.feed); + currentItem = randomizer.pick(); + } else { + currentItem = 0; + } + sendChange("item",currentItem); + if(config["autostart"] == "muted") { + sendChange("volume",0); + } else { + sendChange("volume",Number(config["volume"])); + } + if(config["usecaptions"] == "false") { + config["clip"].captions._visible = false; + config["clip"].controlbar.cc.icn._alpha = 40; + } + if(config["useaudio"] == "false") { + config["clip"].audio.setStop(); + config["clip"].controlbar.au.icn._alpha = 40; + } + if(config["autostart"] == "false") { + sendChange("pause",feeder.feed[currentItem]['start']); + isPlaying = false; + } else { + sendChange("start",feeder.feed[currentItem]['start']); + isPlaying = true; + } + }; + + + /** PlayPause switch **/ + private function setPlaypause() { + if(isPlaying == true) { + isPlaying = false; + sendChange("pause"); + } else { + isPlaying = true; + sendChange("start"); + } + }; + + + /** Play previous item. **/ + private function setPrev() { + if(currentItem == 0) { + setPlayitem(feeder.feed.length - 1); + } else { + setPlayitem(currentItem-1); + } + }; + + + /** Play next item. **/ + private function setNext() { + if(currentItem == feeder.feed.length - 1) { + setPlayitem(0); + } else { + setPlayitem(currentItem+1); + } + }; + + + /** Stop and clear item. **/ + private function setStop() { + sendChange("pause",0); + sendChange("stop"); + sendChange("item",currentItem); + isPlaying = false; + }; + + + /** Forward scrub number to model. **/ + private function setScrub(prm) { + if(isPlaying == true) { + sendChange("start",prm); + } else { + sendChange("pause",prm); + } + }; + + + /** Play a new item. **/ + private function setPlayitem(itm:Number) { + if(itm != currentItem) { + itm > feeder.feed.length-1 ? itm = feeder.feed.length-1: null; + if(feeder.feed[currentItem]['file'] != feeder.feed[itm]['file']) { + sendChange("stop"); + } + currentItem = itm; + sendChange("item",itm); + + } + sendChange("start",feeder.feed[itm]["start"]); + currentURL = feeder.feed[itm]['file']; + isPlaying = true; + }; + + + /** Get url from an item if link exists, else playpause. **/ + private function setGetlink(idx:Number) { + if(feeder.feed[idx]["link"] == undefined) { + setPlaypause(); + } else { + getURL(feeder.feed[idx]["link"],config["linktarget"]); + } + }; + + + /** Determine what to do if an item is completed. **/ + private function setComplete() { + itemsPlayed++; + if(feeder.feed[currentItem]['type'] == "rtmp" || + config["streamscript"] != undefined) { + sendChange("stop"); + } + if(config["repeat"] == "false" || (config["repeat"] == "list" + && itemsPlayed >= feeder.feed.length)) { + sendChange("pause",0); + isPlaying = false; + itemsPlayed = 0; + } else { + var itm; + if(config["shuffle"] == "true") { + itm = randomizer.pick(); + } else if(currentItem == feeder.feed.length - 1) { + itm = 0; + } else { + itm = currentItem+1; + } + setPlayitem(itm); + } + }; + + + /** Fullscreen switch function. **/ + private function setFullscreen() { + if(Stage["displayState"] == "normal" && + config["usefullscreen"] == "true") { + if(sizes[0] > 400) { + Stage["fullScreenSourceRect"] = new Rectangle(0,0,sizes[0],sizes[1]); + } + Stage["displayState"] = "fullScreen"; + } else if (Stage["displayState"] == "fullScreen" && + config["usefullscreen"] == "true") { + Stage["displayState"] = "normal"; + } else if (config["fsbuttonlink"] != undefined) { + sendChange("stop"); + getURL(config["fsbuttonlink"],config["linktarget"]); + } + }; + + + /** Captions toggle **/ + private function setCaptions() { + if(config["usecaptions"] == "true") { + config["usecaptions"] = "false"; + config["clip"].captions._visible = false; + config["clip"].controlbar.cc.icn._alpha = 40; + } else { + config["usecaptions"] = "true"; + config["clip"].captions._visible = true; + config["clip"].controlbar.cc.icn._alpha = 100; + } + playerSO.data.usecaptions = config["usecaptions"]; + playerSO.flush(); + }; + + + /** Audiotrack toggle **/ + private function setAudio() { + if(config["useaudio"] == "true") { + config["useaudio"] = "false"; + config["clip"].audio.setStop(); + config["clip"].controlbar.au.icn._alpha = 40; + } else { + config["useaudio"] = "true"; + config["clip"].audio.setStart(); + config["clip"].controlbar.au.icn._alpha = 100; + } + playerSO.data.useaudio = config["useaudio"]; + playerSO.flush(); + }; + + + /** Check volume percentage and forward to models. **/ + private function setVolume(prm) { + if (prm < 0 ) { prm = 0; } else if (prm > 100) { prm = 100; } + if(prm == 0) { + if(muted == true) { + muted = false; + sendChange("volume",config['volume']); + } else { + muted = true; + sendChange("volume",0); + } + } else { + sendChange("volume",prm); + config['volume'] = prm; + muted = false; + } + playerSO.data.volume = config["volume"]; + playerSO.flush(); + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/PlaylistView.as b/media/source/com/jeroenwijering/players/PlaylistView.as new file mode 100644 index 0000000..1dd4188 --- /dev/null +++ b/media/source/com/jeroenwijering/players/PlaylistView.as @@ -0,0 +1,238 @@ +/** +* Playlist view management of the players MCV pattern. +* +* @author Jeroen Wijering +* @version 1.9 +**/ + + +import com.jeroenwijering.players.*; +import com.jeroenwijering.utils.*; +import com.jeroenwijering.feeds.FeedListener; + + +class com.jeroenwijering.players.PlaylistView extends AbstractView + implements FeedListener { + + + /** ImageLoader **/ + private var thumbLoader:ImageLoader; + /** Scroller instance **/ + private var listScroller:Scroller; + /** Position of the playlist **/ + private var listRight:Boolean; + /** Position of the playlist **/ + private var listWidth:Number; + /** number of items in the playlist **/ + private var listLength:Number; + /** Currently highlighted playlist item **/ + private var currentItem:Number; + /** Save the current time **/ + private var currentTime:Number = -10; + + + + /** Constructor **/ + function PlaylistView(ctr:AbstractController,cfg:Object,fed:Object) { + super(ctr,cfg,fed); + if(config["displaywidth"] < config["width"]) { + listRight = true; + listWidth = config["width"]-config["displaywidth"]-1; + } else { + listRight = false; + listWidth = config["width"]; + } + setButtons(); + Stage.addListener(this); + feeder.addListener(this); + }; + + + /** OnLoad event handler; sets up the playlist sizes and colors. **/ + private function setButtons() { + var ref = this; + var tgt = config["clip"].playlist; + tgt.btn._visible = false; + // iterate playlist and setup each button + listLength = feeder.feed.length; + var num = 0; + for(var i=0; i:
    "+ + feeder.feed[i]["title"]; + } else { + tgt["btn"+i].txt.htmlText = "" + + feeder.feed[i]["author"] + ":
    " + + feeder.feed[i]["title"]; + } + if(feeder.feed[i]["image"] != undefined) { + tgt["btn"+i].txt._x += 60; + tgt["btn"+i].txt._width -= 60; + thumbLoader = + new ImageLoader(tgt["btn"+i].img,"true",60,40); + thumbLoader.loadImage(feeder.feed[i]["image"]); + tgt["btn"+i].img.setMask(tgt["btn"+i].msk); + } else { + tgt["btn"+i].msk._height = 10; + tgt["btn"+i].img._visible = false; + tgt["btn"+i].msk._visible = false; + } + } else { + tgt["btn"+i]._y = num*23; + if(feeder.feed[i]["author"] == undefined) { + tgt["btn"+i].txt.htmlText = feeder.feed[i]["title"]; + } else { + tgt["btn"+i].txt.htmlText = feeder.feed[i]["author"] + + " - " + feeder.feed[i]["title"]; + } + tgt["btn"+i].msk._height = 10; + tgt["btn"+i].img._visible = + tgt["btn"+i].msk._visible = false; + } + tgt["btn"+i].txt.textColor = config["frontcolor"]; + // set link icon + if(feeder.feed[i]["link"] != undefined) { + tgt["btn"+i].txt._width -= 20; + tgt["btn"+i].icn._x = listWidth - 24; + tgt["btn"+i].icn.onRollOver = function() { + this._parent.col2.setRGB(ref.config["lightcolor"]); + }; + tgt["btn"+i].icn.onRollOut = function() { + if(ref.currentItem == this._parent.getDepth()) { + this._parent.col2.setRGB(ref.config["backcolor"]); + } else { + this._parent.col2.setRGB(ref.config["frontcolor"]); + } + }; + tgt["btn"+i].icn.onRelease = function() { + ref.sendEvent("getlink",this._parent.getDepth()); + }; + } else { + tgt["btn"+i].icn._visible = false; + } + num++; + } + } + // setup mask and scrollbar if needed + var msk = config["clip"].playlistmask; + if(listRight == true) { + msk._x = tgt._x = Number(config["displaywidth"]) + 1; + msk._y = tgt._y = 0; + msk._height = config["displayheight"]; + } else { + msk._y = tgt._y = config["displayheight"] + + config["controlbar"] + config["searchbar"]; + msk._height = config["height"] - msk._y; + } + msk._width = listWidth; + tgt.setMask(msk); + if(tgt._height > msk._height + 2 && feeder.feed.length > 1) { + if(config["autoscroll"] == "false") { + msk._width -= 10; + for(var i=0; i 5) { + currentTime = elp; + for (var i=0; i currentTime) { + if(i != currentItem+1) { setItem(i-1); } + break; + } + } + } + }; + + + /** Hide the scrollbar on fullscreen **/ + public function onFullScreen(fs:Boolean) { + if(listScroller == undefined) { + break; + } else if(fs == true) { + config["clip"].scrollbar._visible = false; + } else { + config["clip"].scrollbar._visible = true; + } + }; + + + /** Render a new playlist when the feed updates **/ + public function onFeedUpdate(typ:String) { + listScroller.purgeScrollbar(); + delete listScroller; + var tgt = config["clip"].playlist; + for(var i=0; i<999; i++) { + tgt["btn"+i].removeMovieClip(); + } + setButtons(); + setItem(currentItem); + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/RecommendationsView.as b/media/source/com/jeroenwijering/players/RecommendationsView.as new file mode 100644 index 0000000..eac7b81 --- /dev/null +++ b/media/source/com/jeroenwijering/players/RecommendationsView.as @@ -0,0 +1,204 @@ +/** +* Thumbnailbar with recommended videos. +* +* @author Jeroen Wijering +* @version 1.0 +**/ + + +import com.jeroenwijering.utils.ImageLoader; +import com.jeroenwijering.utils.Animations; +import com.jeroenwijering.utils.XMLParser; +import com.jeroenwijering.players.AbstractController; +import com.jeroenwijering.players.AbstractView; + + +class com.jeroenwijering.players.RecommendationsView extends AbstractView { + + + /** reference to config Array **/ + private var config:Object; + /** reference to feed Array **/ + private var feeder:Object; + /** Reference to the movieclip **/ + private var clip:MovieClip; + /** XML parser reference **/ + private var parser:XMLParser; + /** Recommendations array **/ + private var recommendations:Array; + /** Current show offset **/ + private var offset:Number = 0; + /** Number of thumbs maximum on screen **/ + private var maximum:Number; + + + + /** Constructor **/ + function RecommendationsView(ctr:AbstractController, + cfg:Object,fed:Object) { + config = cfg; + feeder = fed; + clip = config["clip"].recommendations; + var ref = this; + parser = new XMLParser(); + parser.onComplete = function() { + ref.loadRecommendations(this.output); + }; + Stage.addListener(this); + setButtons(); + }; + + + /** Set the colors, clicks and dimensions of the buttons. **/ + private function setButtons() { + var ref = this; + maximum = Math.floor((config['displaywidth']-44)/70); + clip._visible = false; + clip.txt._x = 10; + clip.txt._width = config['displaywidth'] -20; + clip.txt.textColor = config['backcolor']; + clip.prv._x = config['displaywidth']/2 - maximum*35; + clip.nxt._x = config['displaywidth']/2 + maximum*35; + clip.prv.col = new Color(clip.prv); + clip.prv.col.setRGB(config['backcolor']); + clip.prv.onRelease = function() { + this.col.setRGB(ref.config['backcolor']); + ref.showRecommendations(ref.offset - ref.maximum); + }; + clip.prv._visible = false; + clip.nxt.col = new Color(clip.nxt); + clip.nxt.col.setRGB(config['backcolor']); + clip.nxt.onRelease = function() { + this.col.setRGB(ref.config['backcolor']); + ref.showRecommendations(ref.offset + ref.maximum); + }; + clip.nxt._visible = false; + clip.itm._visible = false; + for(var i=0; i= recommendations.length-maximum ? clip.nxt._visible = false: clip.nxt._visible = true; + for(var i=0; i= config["height"]) { + config["height"] = config["displayheight"] = Stage.height; + config["width"] = config["displaywidth"] = Stage.width; + } + showRecommendations(); + }; + + + /** Catches fullscreen escape. **/ + public function onFullScreen(fs:Boolean) { + showRecommendations(); + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/RotatorController.as b/media/source/com/jeroenwijering/players/RotatorController.as new file mode 100644 index 0000000..33d4438 --- /dev/null +++ b/media/source/com/jeroenwijering/players/RotatorController.as @@ -0,0 +1,180 @@ +/** +* Rotator extension of the controller. +* +* @author Jeroen Wijering +* @version 1.6 +**/ + + +import com.jeroenwijering.players.AbstractController; +import com.jeroenwijering.utils.Randomizer; + + +class com.jeroenwijering.players.RotatorController extends AbstractController{ + + + /** Which one of the models to send the changes to **/ + private var currentModel:Number; + /** use SharedObject to save current file, item and volume **/ + private var playerSO:SharedObject; + + + /** Constructor, inherited from super **/ + function RotatorController(car:Object,ply:Object) { + super(car,ply); + playerSO = SharedObject.getLocal("com.jeroenwijerin.players", "/"); + if(playerSO.data.volume != undefined && _root.volume == undefined) { + config["volume"] = playerSO.data.volume; + } + if(playerSO.data.useaudio != undefined && + _root.useaudio == undefined) { + config["useaudio"] = playerSO.data.useaudio; + } + }; + + + /** Complete the build of the MCV cycle and start flow of events. **/ + public function startMCV(mar:Array) { + if(mar != undefined) { registeredModels = mar; } + itemsPlayed = 0; + if(config["shuffle"] == "true") { + randomizer = new Randomizer(feeder.feed); + currentItem = randomizer.pick(); + } else { + currentItem = 0; + } + sendChange("item",currentItem); + if(config["autostart"] == "false") { + sendChange("start",0); + sendChange("pause",0); + isPlaying = false; + } else { + sendChange("start",0); + isPlaying = true; + } + }; + + + /** PlayPause switch **/ + private function setPlaypause() { + if(isPlaying == true) { + isPlaying = false; + sendChange("pause"); + } else { + isPlaying = true; + sendChange("start"); + } + }; + + + /** Play previous item. **/ + private function setPrev() { + if(currentItem == 0) { + setPlayitem(feeder.feed.length - 1); + } else { + setPlayitem(currentItem-1); + } + }; + + + /** Play next item. **/ + private function setNext() { + if(currentItem == feeder.feed.length - 1) { + setPlayitem(0); + } else { + setPlayitem(currentItem+1); + } + }; + + + /** Stop and clear item. **/ + private function setStop() { + sendChange("pause",0); + sendChange("stop"); + sendChange("item",currentItem); + isPlaying = false; + }; + + + /** Forward scrub number to model. **/ + private function setScrub(prm) { + isPlaying == true ? sendChange("start",prm): sendChange("pause",prm); + }; + + + /** Play a new item. **/ + private function setPlayitem(itm:Number) { + if(itm != currentItem) { + sendChange("stop"); + itm > feeder.feed.length-1 ? itm = feeder.feed.length-1: null; + currentItem = itm; + sendChange("item",itm); + } + if(feeder.feed[itm]["start"] == undefined) { + sendChange("start",0); + } else { + sendChange("start",feeder.feed[itm]["start"]); + } + currentURL = feeder.feed[itm]['file']; + isPlaying = true; + }; + + + /** Get url from an item if link exists, else playpause. **/ + private function setGetlink(idx:Number) { + if(feeder.feed[idx]["link"] == undefined) { + setPlaypause(); + } else { + getURL(feeder.feed[idx]["link"],config["linktarget"]); + } + }; + + + /** Determine what to do if an item is completed. **/ + private function setComplete() { + itemsPlayed++; + if(config["repeat"]=="false" || (config["repeat"] == "list" + && itemsPlayed >= feeder.feed.length)) { + sendChange("pause",0); + isPlaying = false; + itemsPlayed = 0; + } else { + if(config["shuffle"] == "true") { + setPlayitem(randomizer.pick()); + } else if(currentItem == feeder.feed.length - 1) { + setPlayitem(0); + } else { + setPlayitem(currentItem+1); + } + } + }; + + + /** Audiotrack toggle **/ + private function setAudio() { + if(config["useaudio"] == "true") { + config["useaudio"] = "false"; + config["clip"].audio.setStop(); + config["clip"].navigation.audioBtn.icnOff._visible = true; + config["clip"].navigation.audioBtn.icnOn._visible = false; + } else { + config["useaudio"] = "true"; + config["clip"].audio.setStart(); + config["clip"].navigation.audioBtn.icnOff._visible = false; + config["clip"].navigation.audioBtn.icnOn._visible = true; + } + playerSO.data.useaudio = config["useaudio"]; + playerSO.flush(); + }; + + + /** Switch active model and send changes. **/ + private function sendChange(typ:String,prm:Number):Void { + if(typ == "item") { + currentModel == 0 ? currentModel = 1: currentModel = 0; + } + registeredModels[currentModel].getChange(typ,prm); + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/RotatorView.as b/media/source/com/jeroenwijering/players/RotatorView.as new file mode 100644 index 0000000..45ffd7c --- /dev/null +++ b/media/source/com/jeroenwijering/players/RotatorView.as @@ -0,0 +1,518 @@ +/** +* Rotator user interface View of the MCV cycle. +* +* @author Jeroen Wijering +* @version 1.5 +**/ + + +import com.jeroenwijering.players.*; +import com.jeroenwijering.utils.ImageLoader; +import com.jeroenwijering.utils.Animations; +import flash.geom.Transform; +import flash.geom.ColorTransform; + +class com.jeroenwijering.players.RotatorView extends AbstractView { + + + /** full width of the scrubbars **/ + private var currentItem:Number; + /** clip that's currently active **/ + private var upClip:MovieClip; + /** clip that's currently inactive **/ + private var downClip:MovieClip; + /** boolean for whether to use the title display **/ + private var useTitle:Boolean; + /** boolean to see if the transition is done **/ + private var transitionDone:Boolean = false; + /** boolean to detect first run **/ + private var firstRun:Boolean = true; + /** interval for hiding the display **/ + private var hideInt:Number; + /** array with all transitions **/ + private var allTransitions:Array = new Array( + "bgfade", + "blocks", + "bubbles", + "circles", + "fade", + "flash", + "fluids", + "lines", + "slowfade" + ); + + + /** Constructor **/ + function RotatorView(ctr:AbstractController,cfg:Object,fed:Object) { + super(ctr,cfg,fed); + setColorsClicks(); + if(config["shownavigation"] == "true") { + Mouse.addListener(this); + } + }; + + + /** Sets up visibility, sizes and colors of all display items **/ + private function setColorsClicks() { + var ref = this; + var tgt:MovieClip = config["clip"]; + tgt.button._width = config["width"]; + tgt.button._height = config["height"]; + if(config['overstretch']=='true' || config['overstretch']=='fit') { + tgt.img1.bg._visible = tgt.img2.bg._visible = false; + } else { + tgt.img1.bg._width = tgt.img2.bg._width = config["width"]; + tgt.img1.bg._height = tgt.img2.bg._height = config["height"]; + tgt.img1.col = new Color(tgt.img1.bg); + tgt.img1.col.setRGB(config["screencolor"]); + tgt.img2.col = new Color(tgt.img2.bg); + tgt.img2.col.setRGB(config["screencolor"]); + } + if(config["linkfromdisplay"] == "true") { + tgt.button.onRelease = function() { + ref.sendEvent("getlink",ref.currentItem); + }; + tgt.playicon._visible = false; + } else { + tgt.button.onRelease = function() { + ref.sendEvent("next"); + }; + } + tgt.img1.swapDepths(1); + tgt.img2.swapDepths(2); + tgt.playicon.swapDepths(4); + tgt.activity.swapDepths(5); + tgt.navigation.swapDepths(6); + tgt.logo.swapDepths(7); + tgt.playicon._x=tgt.activity._x = Math.round(config["width"]/2); + tgt.playicon._y=tgt.activity._y = Math.round(config["height"]/2); + if(config["logo"] != undefined) { + var lll = new ImageLoader(tgt.logo,"none"); + lll.onLoadFinished = function() { + ref.config['clip'].logo._x = ref.config["displaywidth"] - + ref.config['clip'].logo._width -10; + ref.config['clip'].logo._y = 10; + }; + lll.loadImage(config["logo"]); + } + tgt = config["clip"].navigation; + if (config["shownavigation"] == "true") { + tgt._y = config["height"] - 40; + tgt._x = config["width"]/2 - 50; + tgt.prevBtn.col1 = new Color(tgt.prevBtn.bck); + tgt.prevBtn.col1.setRGB(config["backcolor"]); + tgt.prevBtn.col2 = new Color(tgt.prevBtn.icn); + tgt.prevBtn.col2.setRGB(config["frontcolor"]); + tgt.itmBtn.col1 = new Color(tgt.itmBtn.bck); + tgt.itmBtn.col1.setRGB(config["backcolor"]); + tgt.itmBtn.txt.textColor = config["frontcolor"]; + tgt.nextBtn.col1 = new Color(tgt.nextBtn.bck); + tgt.nextBtn.col1.setRGB(config["backcolor"]); + tgt.nextBtn.col2 = new Color(tgt.nextBtn.icn); + tgt.nextBtn.col2.setRGB(config["frontcolor"]); + tgt.prevBtn.onRollOver = tgt.nextBtn.onRollOver = function() { + this.col2.setRGB(ref.config["lightcolor"]); + }; + tgt.prevBtn.onRollOut = tgt.nextBtn.onRollOut = function() { + this.col2.setRGB(ref.config["frontcolor"]); + }; + tgt.itmBtn.onRollOver = function() { + this.txt.textColor = ref.config["lightcolor"]; + }; + tgt.itmBtn.onRollOut = function() { + this.txt.textColor = ref.config["frontcolor"]; + }; + tgt.prevBtn.onRelease = function() { + ref.sendEvent("prev"); + this.col2.setRGB(ref.config["frontcolor"]); + }; + tgt.itmBtn.onRelease = function() { ref.sendEvent("playpause"); }; + tgt.nextBtn.onRelease = function() { + ref.sendEvent("next"); + this.col2.setRGB(ref.config["frontcolor"]); + }; + // set sizes, colors and buttons for image title + var len = 0; + for(var i=0; i len) { + len = feeder.feed[i]['title'].length; + } + } + if(len == 0) { + useTitle = false; + tgt.titleBtn._visible = false; + } else { + useTitle = true; + tgt.titleBtn._x = 74; + tgt.titleBtn.col1 = new Color(tgt.titleBtn.left); + tgt.titleBtn.col1.setRGB(config["backcolor"]); + tgt.titleBtn.col2 = new Color(tgt.titleBtn.mid); + tgt.titleBtn.col2.setRGB(config["backcolor"]); + tgt.titleBtn.col3 = new Color(tgt.titleBtn.right); + tgt.titleBtn.col3.setRGB(config["backcolor"]); + tgt.titleBtn.tf._width = len*6; + tgt.titleBtn.tf.textColor = config["frontcolor"]; + if(feeder.feed[0]["link"] != undefined) { + tgt.titleBtn.onRollOver = function() { + this.tf.textColor = ref.config["lightcolor"]; + }; + tgt.titleBtn.onRollOut = function() { + this.tf.textColor = ref.config["frontcolor"]; + }; + tgt.titleBtn.onRelease = function() { + ref.sendEvent("getlink",ref.currentItem); + }; + }; + tgt.titleBtn.mid._width = len*6; + tgt.titleBtn.right._x = len*6+4; + tgt.nextBtn._x = len*6 + 79; + } + if(feeder.audio == true) { + tgt.audioBtn.col1 = new Color(tgt.audioBtn.bck); + tgt.audioBtn.col2 = new Color(tgt.audioBtn.icnOn); + tgt.audioBtn.col3 = new Color(tgt.audioBtn.icnOff); + tgt.audioBtn.col1.setRGB(config["backcolor"]); + tgt.audioBtn.col2.setRGB(config["frontcolor"]); + tgt.audioBtn.col3.setRGB(config["frontcolor"]); + tgt.audioBtn.onRollOver = function() { + this.col2.setRGB(ref.config["lightcolor"]); + this.col3.setRGB(ref.config["lightcolor"]); + }; + tgt.audioBtn.onRollOut = function() { + this.col2.setRGB(ref.config["frontcolor"]); + this.col3.setRGB(ref.config["frontcolor"]); + }; + tgt.audioBtn.onRelease = function() { + ref.sendEvent("audio"); + this.col2.setRGB(ref.config["frontcolor"]); + this.col3.setRGB(ref.config["frontcolor"]); + }; + if(config['useaudio'] == "true") { + tgt.audioBtn.icnOff._visible = false; + } else { + tgt.audioBtn.icnOn._visible = false; + } + tgt.audioBtn._x = len*6 + 104; + } else { + tgt.audioBtn._x = 0; + tgt.audioBtn._visible = false; + } + tgt._x = Math.round(config["width"]/2 - tgt._width/2); + } else { + tgt._visible = false; + } + }; + + + /** New item: switch clips and ready transition **/ + private function setItem(pr1) { + currentItem = pr1; + transitionDone = false; + var tgt = config["clip"]; + tgt.navigation.itmBtn.txt.text = (currentItem+1) + " / " + + feeder.feed.length; + if (useTitle == true) { + tgt.navigation.titleBtn.tf.text=feeder.feed[currentItem]["title"]; + } + tgt.img1.swapDepths(tgt.img2); + downClip = upClip; + if (upClip == tgt.img1) { + upClip = tgt.img2; + } else { + upClip = tgt.img1; + } + }; + + + /** State switch; start the transition **/ + private function setState(stt:Number) { + switch(stt) { + case 0: + if(config["showicons"] == "true") { + config["clip"].playicon._visible = true; + } + config["clip"].activity._visible = false; + break; + case 1: + config["clip"].playicon._visible = false; + if(config["showicons"] == "true") { + config["clip"].activity._visible = true; + } + break; + case 2: + config["clip"].playicon._visible = false; + config["clip"].activity._visible = false; + if(transitionDone == false) { + doTransition(); + if(config["kenburns"] == "true") { + moveClip(); + } + } + break; + } + }; + + + /** (Re)set the ken burns fade **/ + private function moveClip() { + var dir = random(4); + var clp = upClip.smc; + if(upClip.smc == undefined) { clp = upClip.mc; } + clp._xscale *= config['rotatetime']/20 + 1; + clp._yscale *= config['rotatetime']/20 + 1; + if(dir == 0) { + clp._x = 0; + } else if (dir == 1) { + clp._y = 0; + } else if (dir == 2) { + clp._x = config['width'] - upClip._width; + } else { + clp._y = config['height'] - upClip._height; + } + clp.onEnterFrame = function() { + if(dir == 0) { + this._x -= 0.3; + } else if (dir == 1) { + this._y -= 0.3; + } else if (dir == 2) { + this._x += 0.3; + } else { + this._y += 0.3; + } + }; + }; + + + /** Start a transition **/ + private function doTransition() { + transitionDone = true; + if(firstRun == true) { + config["clip"].img1._alpha = 100; + config["clip"].img2._alpha = 0; + firstRun = false; + } else { + var trs = config["transition"]; + if(trs == "random") { + trs = allTransitions[random(allTransitions.length)]; + } + switch (trs) { + case "bgfade": + doBGFade(); + break; + case "blocks": + doBlocks(); + break; + case "bubbles": + doBubbles(); + break; + case "circles": + doCircles(); + break; + case "fade": + doFade(); + break; + case "flash": + doFlash(); + break; + case "fluids": + doFluids(); + break; + case "lines": + doLines(); + break; + case "slowfade": + doSlowfade(); + break; + default: + doFade(); + break; + } + } + }; + + + /** Function for the fade transition **/ + private function doFade() { + upClip.ref = this; + upClip._alpha = 0; + upClip.onEnterFrame = function() { + this._alpha +=5; + if(this._alpha >= 100) { + delete this.onEnterFrame; + this.ref.downClip._alpha = 0; + } + }; + }; + + + /** Function for the bgfade transition **/ + private function doBGFade() { + downClip.ref = upClip.ref = this; + downClip.onEnterFrame = function() { + this._alpha -=5; + if(this._alpha <= 0) { + delete this.onEnterFrame; + this.ref.upClip.onEnterFrame = function() { + if(this._alpha >= 100) { + delete this.onEnterFrame; + } else { + this._alpha +=5; + } + }; + } + }; + }; + + + /** Function for the blocks transition **/ + private function doBlocks() { + upClip._alpha = 100; + config["clip"].attachMovie("blocksMask","mask",3); + var msk:MovieClip = config["clip"].mask; + if (config["width"] > config["height"]) { + msk._width = msk._height = config["width"]; + } else { + msk._width = msk._height = config["height"]; + } + msk._rotation = random(4)*90; + msk._rotation == 90 ? msk._x = config["width"]: null; + msk._rotation == 180 ? msk._x = config["width"]: null; + msk._rotation == 180 ? msk._y = config["height"]: null; + msk._rotation == -90 ? msk._y = config["height"]: null; + upClip.setMask(msk); + playClip(msk); + }; + + + /** Function for the bubbles transition **/ + private function doBubbles() { + upClip._alpha = 100; + config["clip"].attachMovie("bubblesMask","mask",3); + var msk:MovieClip = config["clip"].mask; + upClip.setMask(msk); + if (config["width"] > config["height"]) { + msk._width = msk._height = config["width"]; + msk._y = config["height"]/2 - msk._height/2; + } else { + msk._width = msk._height = config["height"]; + msk._x = config["width"]/2- msk._width/2; + } + if(random(2) == 1) { + msk._xscale = -msk._xscale; + msk._x += config['width']; + } + playClip(msk); + }; + + + /** Function for the circles transition **/ + private function doCircles() { + upClip._alpha = 100; + config["clip"].attachMovie("circlesMask","mask",3); + var msk:MovieClip = config["clip"].mask; + upClip.setMask(msk); + if (config["width"] > config["height"]) { + msk._width = msk._height = config["width"]; + } else { + msk._width = msk._height = config["height"]; + } + msk._x = config["width"]/2; + msk._y = config["height"]/2; + playClip(msk,10); + }; + + + /** Function for the flash transition **/ + private function doFlash() { + upClip._alpha = 100; + upClip.col = new Color(upClip); + upClip.ctf = new Object({rb:255,gb:255,bb:255}); + upClip.col.setTransform(upClip.ctf); + upClip.onEnterFrame = function() { + if(this.ctf.rb < 1) { + this.ctf = new Object({rb:0,gb:0,bb:0}); + this.col.setTransform(this.ctf); + delete this.onEnterFrame; + } else { + this.ctf.rb /= 1.05; + this.ctf.gb /= 1.05; + this.ctf.bb /= 1.05; + this.col.setTransform(this.ctf); + } + }; + }; + + /** Function for the fluids transition **/ + private function doFluids() { + upClip._alpha = 100; + config["clip"].attachMovie("fluidsMask","mask",3); + var msk:MovieClip = config["clip"].mask; + upClip.setMask(msk); + msk._width = config["width"]; + msk._height = config["height"]; + playClip(msk); + }; + + + /** Function for the lines transition **/ + private function doLines() { + upClip._alpha = 100; + config["clip"].attachMovie("linesMask","mask",3); + var msk:MovieClip = config["clip"].mask; + upClip.setMask(msk); + msk._width = config["width"]; + msk._height = config["height"]; + playClip(msk); + }; + + + /** Function for the fade transition **/ + private function doSlowfade() { + upClip.ref = this; + upClip._alpha = 0; + upClip.onEnterFrame = function() { + this._alpha+=2; + if(this._alpha >= 100) { + delete this.onEnterFrame; + this.ref.downClip._alpha = 0; + } + }; + }; + + + /** Play a specific Movieclip and remove it once it's finished **/ + private function playClip(tgt:MovieClip,rot:Number) { + tgt.ref = this; + tgt.onEnterFrame = function() { + this.nextFrame(); + rot == undefined ? null: this._rotation +=rot; + if(this._currentframe == this._totalframes) { + this.ref.downClip._alpha = 0; + this.clear(); + this.unloadMovie(); + this.removeMovieClip(); + } + }; + }; + + + /** after a delay, the controlbar is hidden **/ + private function hideBar() { + Animations.fadeOut(config['clip'].navigation); + clearInterval(hideInt); + } + + + /** Mouse move shows controlbar **/ + public function onMouseMove() { + Animations.fadeIn(config['clip'].navigation); + clearInterval(hideInt); + if(!config["clip"].navigation.hitTest(_root._xmouse,_root._ymouse)) { + hideInt = setInterval(this,"hideBar",500); + } + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/SearchView.as b/media/source/com/jeroenwijering/players/SearchView.as new file mode 100644 index 0000000..db98a5a --- /dev/null +++ b/media/source/com/jeroenwijering/players/SearchView.as @@ -0,0 +1,69 @@ +/** +* Add a search bar to the player +**/ + + +import com.jeroenwijering.players.*; + + +class com.jeroenwijering.players.SearchView extends AbstractView { + + + /** Save the focus state (to capture enters) **/ + public static var focussed:Boolean = false; + + /** Constructor **/ + function SearchView(ctr:AbstractController,cfg:Object,fed:Object) { + super(ctr,cfg,fed); + Key.addListener(this); + setDimensions(); + }; + + + /** Setup the dimensions of the search bar **/ + private function setDimensions() { + var ref = this; + var tgt = config['clip'].search; + if(config["displayheight"] == config["height"] - config["searchbar"]) { + tgt._y = config['displayheight']; + } else { + tgt._y = config['displayheight'] + config['controlbar']; + } + tgt.ipt._width = config['width'] - 95; + tgt.ipt.onSetFocus = function() { + SearchView.focussed = true; + }; + tgt.ipt.onKillFocus = function() { + SearchView.focussed = false; + }; + tgt.bck._width = config['width']; + tgt.box._width = config['width'] - 95; + tgt.btn._x = config['width'] - 86; + tgt.btn.col = new Color(tgt.btn.icn); + tgt.btn.col.setRGB(config['frontcolor']); + tgt.btn.onRollOver = function() { + this.col.setRGB(ref.config['lightcolor']); + }; + tgt.btn.onRollOut = function() { + this.col.setRGB(ref.config['frontcolor']); + }; + tgt.btn.onRelease = function() { + ref.doSearch(); + } + }; + + + /** start the search function **/ + private function doSearch() { + var txt = escape(config['clip'].search.ipt.text); + getURL(config['searchlink']+txt,config['linktarget']); + }; + + + /** KeyDown handler, forwarded by Key object **/ + public function onKeyDown() { + if(Key.getCode() == 13 && SearchView.focussed == true) { doSearch(); } + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/players/YoutubeModel.as b/media/source/com/jeroenwijering/players/YoutubeModel.as new file mode 100644 index 0000000..304df34 --- /dev/null +++ b/media/source/com/jeroenwijering/players/YoutubeModel.as @@ -0,0 +1,203 @@ +/** +* Youtube model class of the players MCV pattern. +* Integrates Youtube playback component. +* +* @author Jeroen Wijering +* @version 1.0 +**/ + + +import com.jeroenwijering.players.*; + + +class com.jeroenwijering.players.YoutubeModel extends AbstractModel { + + + /** Array with extensions used by this model. **/ + private var mediatypes:Array = new Array("youtube"); + /** Clip the YouTube blob is loaded into **/ + private var ytplayer:MovieClip; + /** Location of the YouTube blob. **/ + private var url:String = "http://gdata.youtube.com/apiplayer"; + /** Developer key for the YouTube player **/ + private var key:String = "AI39si4CHS3-oQa0cHIhANstFbCLE71-qK6CB3mNe0lEx2h1mwXsOz6n1fkPo0yTKpZgYH4jsLgSX1Qg4jXNrYhJYKfMQiPlzw"; + /** Reference to the loader **/ + private var loader:MovieClipLoader; + /** ID of the current clip to play **/ + private var currentURL:String; + /** interval ID of the buffer update function **/ + private var loadedInterval:Number; + /** current percentage of the video that's loaded **/ + private var currentLoaded:Number = 0; + /** interval ID of the position update function **/ + private var positionInterval:Number; + /** current position of the video that is playing **/ + private var currentPosition:Number; + /** Current volume **/ + private var currentVolume:Number; + /** static referer to the model **/ + private static var instance; + /** pause-seeking **/ + private var pauseseek:Boolean; + /** has the update already been sent? **/ + private var updateSent:Boolean; + + + /** Constructor **/ + function YoutubeModel(vws:Array,ctr:AbstractController,cfg:Object,fed:Object,fcl:MovieClip) { + super(vws,ctr,cfg,fed); + ytplayer = fcl; + System.security.allowDomain("gdata.youtube.com"); + System.security.allowInsecureDomain("gdata.youtube.com"); + YoutubeModel.instance = this; + }; + + /** wait for the player to load with a loop, then remove the loop. **/ + public function onLoadInit() { + var ref = this; + ytplayer.onEnterFrame = function() { + if (this.isPlayerLoaded()) { + ref.pauseseek = false; + this.addEventListener("onStateChange",ref.onPlayerStateChange); + this.addEventListener("onError",ref.onError); + ref.onPlayerStateChange(this.getPlayerState()); + ref.setStart(ref.feeder.feed[ref.currentItem]['duration']); + delete this.onEnterFrame; + this.setSize(ref.config['displaywidth'],ref.config['displayheight']); + } + } + }; + + + /** Start a specific video **/ + private function setStart(pos:Number) { + clearInterval(positionInterval); + clearInterval(loadedInterval); + if(feeder.feed[currentItem]["file"] != currentURL) { + if(!loader) { + loader = new MovieClipLoader(); + loader.addListener(this); + loader.loadClip(url+'?key='+key,ytplayer); + } else { + currentURL = feeder.feed[currentItem]["file"]; + ytplayer.loadVideoById(getID(currentURL),pos); + ytplayer.setVolume(config['volume']); + sendUpdate("load",0); + sendUpdate("size",320,240); + } + } else if(!isNaN(pos)) { + ytplayer.seekTo(pos,true); + } else { + ytplayer.playVideo(); + } + positionInterval = setInterval(this,"updatePosition",100); + ytplayer._visible = true; + trace('true!!'); + }; + + + /** xtract the current ID from a youtube URL **/ + private function getID(url:String):String { + var arr = url.split('?'); + for (var i in arr) { + if(arr[i].substr(0,2) == 'v=') { + return arr[i].substr(2); + } + } + return ''; + }; + + + /** Listens for the player's onStateChange event **/ + public function onPlayerStateChange(stt:Number) { + var ref = YoutubeModel.instance; + if(ref.currentURL == undefined) { return; } + switch(Number(stt)) { + case 0: + if(!ref.updateSent) { + ref.sendUpdate("state",3); + ref.sendCompleteEvent(); + ref.sendUpdate("time",0,ref.feeder.feed[ref.currentItem]['duration']); + } + break; + case 1: + delete ref.updateSent; + if(ref.pauseseek == true) { + ref.pauseseek = false; + ref.updatePosition(); + ref.ytplayer.pauseVideo(); + } else { + ref.sendUpdate("load",100); + ref.sendUpdate("state",2); + } + break; + case 3: + ref.sendUpdate("state",1); + break; + default: + ref.sendUpdate("state",0); + break; + } + }; + + + /** Error received, let's move to the next video **/ + public function onError() { + var ref = YoutubeModel.instance; + ref.sendUpdate("state",3); + ref.sendCompleteEvent(); + ref.sendUpdate("time",0,0); + }; + + + /** Read and broadcast the current position of the song **/ + private function updatePosition() { + var pos = ytplayer.getCurrentTime(); + var dur = ytplayer.getDuration(); + if(isNaN(dur)) { + pos = 0; + dur = feeder.feed[currentItem]['duration']; + } else { + feeder.feed[currentItem]['duration'] = dur; + } + sendUpdate("time",pos,dur-pos); + currentPosition = pos; + }; + + + /** Pause the video that's currently playing. **/ + private function setPause(pos:Number) { + clearInterval(positionInterval); + ytplayer.pauseVideo(); + if(pos > 0) { + pauseseek = true; + ytplayer.seekTo(pos,true); + updatePosition(); + } + sendUpdate("state",0); + }; + + + /** Stop video and clear data. **/ + private function setStop(pos:Number) { + updateSent = true; + delete currentURL; + ytplayer.stopVideo(); + ytplayer._visible = false; + clearInterval(loadedInterval); + clearInterval(positionInterval); + }; + + + /** Set volume of the sound object. **/ + private function setVolume(vol:Number) { + super.setVolume(vol); + currentVolume = vol; + ytplayer.setVolume(vol); + }; + + + + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/utils/Animations.as b/media/source/com/jeroenwijering/utils/Animations.as new file mode 100644 index 0000000..415de77 --- /dev/null +++ b/media/source/com/jeroenwijering/utils/Animations.as @@ -0,0 +1,134 @@ +/** +* A couple of commonly used animation functions. +* +* @author Jeroen Wijering +* @version 1.12 +**/ + + +class com.jeroenwijering.utils.Animations { + + + /** + * Fadein function for MovieClip. + * + * @param tgt The Movieclip to fade in. + * @param end The final alpha value. + * @param spd The amount of alpha change per frame. + **/ + public static function fadeIn(tgt:MovieClip,end:Number,spd:Number) { + arguments.length < 3 ? spd = 20: null; + arguments.length < 2 ? end = 100: null; + tgt._visible = true; + tgt.onEnterFrame = function() { + if(this._alpha > end-spd) { + delete this.onEnterFrame; + this._alpha = end; + } else { + this._alpha += spd; + } + }; + }; + + + /** + * Fadeout function for MovieClip. + * + * @param tgt The Movieclip to fade out. + * @param end The final alpha value. + * @param spd The amount of alpha change per frame. + * @param rmv Removing the clip off stage switch. + **/ + public static function fadeOut(tgt:MovieClip,end:Number, + spd:Number,rmv:Boolean) { + arguments.length < 4 ? rmv = false: null; + arguments.length < 3 ? spd = 20: null; + arguments.length < 2 ? end = 0: null; + tgt.onEnterFrame = function() { + if(this._alpha < end+spd) { + delete this.onEnterFrame; + this._alpha = end; + end == 0 ? this._visible = false: null; + rmv == true ? this.removeMovieClip(): null; + } else { + this._alpha -= spd; + } + }; + }; + + + /** + * Crossfade a given MovieClip through 0. + * + * @param tgt The Movieclip to crossfade. + * @param alp The final alpha value. + **/ + public static function crossfade(tgt:MovieClip, alp:Number) { + var phs = "out"; + var pct = alp/5; + tgt.onEnterFrame = function() { + if(phs == "out") { + this._alpha -= pct; + if (this._alpha < 1) { phs = "in"; } + } else { + this._alpha += pct; + this._alpha >= alp ? delete this.onEnterFrame : null; + } + }; + }; + + + /** + * Smoothly move a Movielip to a certain position. + * + * @param tgt The Movielip to move. + * @param xps The x destination. + * @param yps The y destination. + * @param spd The movement speed (1 - 2). + **/ + public static function easeTo(tgt:MovieClip,xps:Number,yps:Number, + spd:Number) { + arguments.length < 4 ? spd = 2: null; + tgt.onEnterFrame = function() { + this._x = xps-(xps-this._x)/(1+1/spd); + this._y = yps-(yps-this._y)/(1+1/spd); + if (this._x>xps-1 && this._xyps-1 && this._y 1) { config['width'] = Stage.width; config['height'] = Stage.height; config["clip"]._parent.activity._x = Stage.width/2; config["clip"]._parent.activity._y = Stage.height/2; config["clip"]._parent.activity._alpha = 100; } _root['config'] == undefined ? loadCookies(): loadFile(); }; /** Load configuration data from external XML file **/ private function loadFile() { var ref = this; parser = new XMLParser(); parser.onComplete = function(obj) { var ret = new Object(); for(var i=0; i 1 ? overStretch = String(ost): null; + if(arguments.length > 2) { + targetWidth = wid; + targetHeight = hei; + } + mcLoader = new MovieClipLoader(); + mcLoader.addListener(this); + }; + + + /** Switch image with bitmaparray if possible. **/ + public function onLoadInit(inTarget:MovieClip):Void { + if(useSmoothing == 'true') { + var bmp = new flash.display.BitmapData(targetClip.mc._width, + targetClip.mc._height, true, 0x000000); + bmp.draw(targetClip.mc); + var bmc:MovieClip = targetClip.createEmptyMovieClip("smc", + targetClip.getNextHighestDepth()); + bmc.attachBitmap(bmp, bmc.getNextHighestDepth(),"auto",true); + targetClip.mc.unloadMovie(); + targetClip.mc.removeMovieClip(); + delete targetClip.mc; + scaleImage(targetClip.smc); + onLoadFinished(); + } else { + targetClip.mc.forceSmoothing = true; + if(sourceURL.toLowerCase().indexOf(".swf") == -1) { + scaleImage(targetClip.mc); + } + onLoadFinished(); + } + }; + + + /** Scale the image while maintaining aspectratio **/ + private function scaleImage(tgt:MovieClip):Void { + targetClip._xscale = targetClip._yscale = 100; + var tcf = tgt._currentframe; + tgt.gotoAndStop(1); + sourceWidth = tgt._width; + sourceHeight = tgt._height; + sourceLength = tgt._totalframes/20; + var xsr = targetWidth/sourceWidth; + var ysr = targetHeight/sourceHeight; + if (overStretch == "fit" || Math.abs(xsr-ysr) < 0.1) { + tgt._width = targetWidth; + tgt._height = targetHeight; + } else if ((overStretch == "true" && xsr > ysr) || + (overStretch == "false" && xsr < ysr)) { + tgt._xscale = tgt._yscale = xsr*100; + } else if(overStretch == "none") { + tgt._xscale = tgt._yscale = 100; + } else { + tgt._xscale = tgt._yscale = ysr*100; + } + if(targetWidth != undefined) { + tgt._x = targetWidth/2 - tgt._width/2; + tgt._y = targetHeight/2 - tgt._height/2; + } + tgt.gotoAndPlay(tcf); + onMetaData(); + }; + + + /** Start loading an image. **/ + public function loadImage(img:String):Void { + sourceURL = img; + targetClip.mc.clear(); + targetClip.smc.unloadMovie(); + targetClip.smc.removeMovieClip(); + delete targetClip.smc; + checkSmoothing(img); + var raw:MovieClip = targetClip.createEmptyMovieClip("mc",1); + mcLoader.loadClip(img,raw); + if(img.toLowerCase().indexOf(".swf") > -1) { + metaInt = setInterval(this,"setSWFMeta",200); + } + }; + + + /** Check whether smoothing can be enabled. **/ + private function checkSmoothing(img:String):Void { + var idx:Number = _root._url.indexOf("/",8); + var rot:String = _root._url.substring(0,idx); + if(System.capabilities.version.indexOf("7,0,") > -1 || + img.toLowerCase().indexOf(".swf") > -1 || + _root._url.indexOf("file://") > -1 || + (img.indexOf(rot) == -1 && img.indexOf('http://') == 0)) { + useSmoothing = false; + } else { + useSmoothing = true; + } + }; + + + /** Check when to set the SWF metadata **/ + private function setSWFMeta() { + if(targetClip.mc._currentframe > 0) { + clearInterval(metaInt); + scaleImage(targetClip.mc); + } + }; + + + /** Event handler; invoked when loading. **/ + public function onLoadProgress(tgt:MovieClip,btl:Number,btt:Number) {}; + + + /** Event handler; invoked when image is completely loaded. **/ + public function onLoadFinished() {}; + + + /** Event handler; invoked when metadata is received. **/ + public function onMetaData() {}; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/utils/Randomizer.as b/media/source/com/jeroenwijering/utils/Randomizer.as new file mode 100644 index 0000000..442acc1 --- /dev/null +++ b/media/source/com/jeroenwijering/utils/Randomizer.as @@ -0,0 +1,43 @@ +/** +* Pick random array indexes without having the same picked twice times. +* +* @author Jeroen Wijering +* @version 1.2 +**/ + + +class com.jeroenwijering.utils.Randomizer { + + + /** a reference of the original array **/ + private var originalArray:Array; + /** a copy of the original array **/ + private var bufferArray:Array; + + + /** + * Constructor. + * + * @param arr Array to randomize. + **/ + public function Randomizer(arr:Array) { + originalArray = arr; + bufferArray = new Array(); + }; + + + /** Randomly pick an index from the array given. **/ + public function pick():Number { + if(bufferArray.length == 0) { + for(var k=0; k 2 ? autoScroll = asc: null; + arguments.length > 3 ? frontColor = fcl: null; + arguments.length > 4 ? lightColor = hcl: null; + sizeRatio = maskClip._height/targetClip._height; + if(autoScroll == false) { + drawScrollbar(); + } else { + scrollInterval = setInterval(this,"doAutoscroll",50); + } + if(System.capabilities.os.toLowerCase().indexOf("mac") == -1) { + Mouse.addListener(this); + } + }; + + + /** Draw the scrollbar. **/ + private function drawScrollbar() { + targetClip._parent.createEmptyMovieClip("scrollbar", + targetClip._parent.getNextHighestDepth()); + SCROLLER_CLIP = targetClip._parent.scrollbar; + SCROLLER_CLIP._x = maskClip._x+maskClip._width - 1; + SCROLLER_CLIP._y = maskClip._y+3; + SCROLLER_CLIP.createEmptyMovieClip("back",0); + SCROLLER_CLIP.back._alpha = 0; + SCROLLER_CLIP.back._y = -3; + drawSquare(SCROLLER_CLIP.back,12,maskClip._height,frontColor); + SCROLLER_CLIP.createEmptyMovieClip("bar",1); + SCROLLER_CLIP.bar._x = 4; + SCROLLER_CLIP.bar._alpha = 50; + drawSquare(SCROLLER_CLIP.bar,4,maskClip._height-5,frontColor); + SCROLLER_CLIP.createEmptyMovieClip("front",2); + SCROLLER_CLIP.front._x = 3; + drawSquare(SCROLLER_CLIP.front,6, + SCROLLER_CLIP.bar._height*sizeRatio,frontColor); + SCROLLER_CLIP.front.createEmptyMovieClip("bg",1); + SCROLLER_CLIP.front.bg._x = -3; + SCROLLER_CLIP.front.bg._alpha = 0; + drawSquare(SCROLLER_CLIP.front.bg,12, + SCROLLER_CLIP.front._height,frontColor); + SCROLLER_FRONT_COLOR = new Color(SCROLLER_CLIP.front); + setScrollbarEvents(); + }; + + + /** Set use of mousewheel to scroll playlist. **/ + public function onMouseWheel(dta:Number) { + scrollTo(currentScroll-dta*20); + }; + + + /** Set autoscroll events. **/ + private function doAutoscroll() { + if (maskClip._xmouse>0 && maskClip._xmouse0 && + maskClip._ymouse this._parent.front._y + + this._parent.front._height) { + instance.scrollTo(instance.currentScroll + + instance.maskClip._height/2); + } else if (this._ymouse < this._parent.front._y) { + instance.scrollTo(instance.currentScroll - + instance.maskClip._height/2); + } + }; + SCROLLER_CLIP.front.onPress = function() { + this.startDrag(false,3,0,3,instance.SCROLLER_CLIP.bar._height - + this._height); + instance.scrollInterval = setInterval(instance,"scrollTo",100); + }; + SCROLLER_CLIP.front.onRelease = + SCROLLER_CLIP.front.onReleaseOutside = function() { + this.stopDrag(); + clearInterval(instance.scrollInterval); + }; + scrollTo(maskClip._y - targetClip._y); + }; + + + /** Scroll the MovieClip to a given Y position. **/ + public function scrollTo(yps:Number):Void { + if(arguments.length == 0 && autoScroll == false) { + yps = SCROLLER_CLIP.front._y*maskClip._height / + SCROLLER_CLIP.front._height; + } + if(yps<5) { + yps=0; + } else if (yps>targetClip._height-maskClip._height-5) { + yps = targetClip._height - maskClip._height; + } + Animations.easeTo(targetClip,targetClip._x,maskClip._y - yps); + SCROLLER_CLIP.front._y = yps*SCROLLER_CLIP.front._height / + maskClip._height; + currentScroll = yps; + }; + + + /** Remove the scrollbar from stage **/ + public function purgeScrollbar() { + clearInterval(scrollInterval); + Mouse.removeListener(this); + scrollTo(0); + SCROLLER_CLIP.removeMovieClip(); + }; + + + /** Draw a square in a given movieclip. **/ + private function drawSquare(tgt:MovieClip,wth:Number,hei:Number, + clr:Number) { + tgt.clear(); + tgt.beginFill(clr,100); + tgt.moveTo(0,0); + tgt.lineTo(wth,0); + tgt.lineTo(wth,hei); + tgt.lineTo(0,hei); + tgt.lineTo(0,0); + tgt.endFill(); + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/utils/StringMagic.as b/media/source/com/jeroenwijering/utils/StringMagic.as new file mode 100644 index 0000000..29221ee --- /dev/null +++ b/media/source/com/jeroenwijering/utils/StringMagic.as @@ -0,0 +1,92 @@ +/** +* A couple of commonly used string operations. +* +* @author Jeroen Wijering +* @version 1.3 +**/ + + +class com.jeroenwijering.utils.StringMagic { + + + /** Strip tags and breaks from a string. **/ + static function stripTagsBreaks(str:String):String { + if(str.length == 0 || str == undefined) { return ""; } + var tmp:Array = str.split("\n"); + str = tmp.join(""); + tmp = str.split("\r"); + str = tmp.join(""); + var i:Number = str.indexOf("<"); + while(i != -1) { + var j = str.indexOf(">",i+1); + j == -1 ? j = str.length-1: null; + str = str.substr(0,i) + str.substr(j+1,str.length); + i = str.indexOf("<",i); + } + return str; + }; + + + /** + * Chop string into a number of lines. + * + * @param str The string to chop. + * @param cap The maximum number of characters per line. + * @param nbr The maximum number of lines. + **/ + static function chopString(str:String,cap:Number,nbr:Number):String { + for(var i=cap; i 0) { + str = str.substr(0,str.indexOf(" ",i-3)) + "\n" + + str.substr(str.indexOf(" ",i-3)+1); + } + } + return str; + }; + + + /** Add a leading zero and convert number to string. **/ + static function addLeading(nbr:Number):String { + if(nbr < 10) { + return "0"+Math.floor(nbr); + } else { + return Math.floor(nbr).toString(); + } + }; + + + /** + * Convert a string to seconds, with these formats supported: + * 00:03:00.1 / 03:00.1 / 180.1s / 3.2m / 3.2h + **/ + static function toSeconds(str:String):Number { + var arr = str.split(':'); + var sec; + if (str.substr(-1) == 's') { + sec = Number(str.substr(0,str.length-2)); + } else if (str.substr(-1) == 'm') { + sec = Number(str.substr(0,str.length-2))*60; + } else if(str.substr(-1) == 'h') { + sec = Number(str.substr(0,str.length-2))*3600; + } else if(arr.length > 1) { + sec = Number(arr[arr.length-1]); + sec += Number(arr[arr.length-2])*60; + sec += Number(arr[arr.length-3])*3600; + } else { + sec = Number(str); + } + if(isNaN(sec)) { + return 0; + } else { + return sec; + } + }; + + +} \ No newline at end of file diff --git a/media/source/com/jeroenwijering/utils/XMLParser.as b/media/source/com/jeroenwijering/utils/XMLParser.as new file mode 100644 index 0000000..e87fba1 --- /dev/null +++ b/media/source/com/jeroenwijering/utils/XMLParser.as @@ -0,0 +1,84 @@ +/** +* Parse XML file and return a simple, associative array. +* +* @author Jeroen Wijering +* @version 1.2 +**/ + + +class com.jeroenwijering.utils.XMLParser { + + + /** Flash XML object the file is loaded into. **/ + private var input:XML; + /** The object the XML is parsed into **/ + private var output:Object; + + + /** Constructor, sets up XML object **/ + function XMLParser() {}; + + + /** Start parsing **/ + public function parse(lnk:String) { + var ref = this; + input = new XML(); + output = new Object(); + input.ignoreWhite = true; + input.onLoad = function(scs:Boolean) { + if(scs) { + ref.processRoot(); + } else { + ref.onError(); + } + }; + if(_root._url.indexOf("file://") > -1) { + input.load(lnk); + } else if(lnk.indexOf('?') > -1) { + input.load(lnk+'&'+random(999)); + } else { + input.load(lnk+'?'+random(999)); + } + }; + + + /** Process the root XML node **/ + private function processRoot() { + processNode(input.firstChild,output); + delete input; + onComplete(output); + }; + + + /** Process a specific node **/ + private function processNode(nod:XMLNode,obj:Object) { + obj['name'] = nod.nodeName; + for(var att in nod.attributes) { + obj[att] = nod.attributes[att]; + } + if(nod.childNodes.length < 2 && nod.firstChild.nodeName == null) { + obj['value'] = nod.firstChild.nodeValue; + } else { + obj['childs'] = new Array(); + var chn = nod.firstChild; + var i = 0; + while(chn != undefined) { + var cob = new Object(); + processNode(chn,cob); + obj['childs'].push(cob); + chn = chn.nextSibling; + i++; + } + } + }; + + + /** Invoked when parsing is completed. **/ + public function onComplete(obj:Object) {}; + + + /** Invoked when parsing is completed. **/ + public function onError() {}; + + +} \ No newline at end of file diff --git a/media/source/mediaplayer.fla b/media/source/mediaplayer.fla new file mode 100644 index 0000000..4df2182 Binary files /dev/null and b/media/source/mediaplayer.fla differ diff --git a/media/source/mediaplayer.pdf b/media/source/mediaplayer.pdf new file mode 100644 index 0000000..34a1f44 --- /dev/null +++ b/media/source/mediaplayer.pdf @@ -0,0 +1,1679 @@ +%PDF-1.4 %âãÏÓ +1 0 obj <> endobj 1129 0 obj <>stream + + + + + 2006-07-12T13:07:58Z + 2008-01-11T16:36:17+01:00 + Adobe Illustrator CS3 + 2008-01-11T16:36:17+01:00 + + + + 256 + 172 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgArAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A6db/AJIa9babY6VbebJI rGzube+dhA6zyzxwLBMC8c8XGOQeodviq1SxIqVUun/5x41qajDzfNExiSFlSBlVUS2aLigSZNvW EclD/LTbFU28t/klqWiazYaj/iaW7FrJaSTJJE4Mn1WJ4mO0xWsqsoLFSwFRXiaYq9WxV2KuxV2K uxV2KuxV2KuxVjP5heVNU8z6Cmm6bqp0e4S5iuhdrGZDyt6yRDirxbCYIx36CnfFWDW35CXun2bx 6b5mnFxNA9rJLcRlkCTWMlrI6pE8PxrJMZI6tRBsBXkzKqw/JHVX1C2nuPMXrWcAWOWye3d0lX60 LmSR/Unf97KYo+TDavM0+LFUNon5C61p3mXRdak82zzrpSRwz24ikUXSRqatIfXPxs3BeXZEQb0x Vbc/8493NxBao3mR4preOGN5o7YhpDFc3EpYsZuVTFcCKpJPwCpI2xVGRfkdeNY3sN7ri3Fzdzi7 S5MEvKKU288ZC1uD8CzTiRaUPw0JPUKr/J35K6l5d8zaZrUnmA36WYuPrFs9uVEjzpwDoTJJxKqA K7tSorTbFXquKuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KoW81XS7KET3l5BbQEsBLNIkaExqzuOTED 4VRifAA+GKSKQFt508oXVxLbW+t2MtxAxSWJLiIsCFDEUDb0DCtMUL5fN3lSJuMus2KNULRrmIHk SQBu3iMVRdlq+k30skVlewXUsIVpUhlSRkWSvAsFJpyoaYqi8VaZ0WnJgtSAKmlSegxSAkmu+evJ mgFRrWt2Wns0voBJ540b1eIfhxJrXi6n5EeOKFSPzj5SllMUWtWMkgDEhLiJqcGVXrRtuLSKD88V XzebfKsEgjm1iyjct6YVriIHnUjju3WoxVF2eraVfSzQ2V5Bcy2/AzpDIsjIJASnIKTTlQ0riqKx V2KrZJI415SMEX+ZiAPxxVLdZ1t7OxaewgXUbgMoFqk0cRIJ3PJzTbFWPS+e/Mqxrw8qytMzuDEb 20AEa8eMhcMV+Kp+EVO3yqq3N558yR20ko8ruzo5VIhe2zFwHVQfhJpXmT8lNe2KqSef/M1J1l8q SRzoK28YvbdxJWQKtXWqIeJLH4tqeG+KovTfOuuXV5Zwz+XXtLaen1m6e7tyIPtg1QHk26D7PY19 iqyj69Zf8tEf/Br/AFxV316y/wCWiP8A4Nf64q769Zf8tEf/AAa/1xV316y/5aI/+DX+uKu+vWX/ AC0R/wDBr/XFXfXrL/loj/4Nf64q769Zf8tEf/Br/XFXfXrL/loj/wCDX+uKu+vWX/LRH/wa/wBc Vd9esv8Aloj/AODX+uKu+vWX/LRH/wAGv9cVd9esv+WiP/g1/rirvr1l/wAtEf8Awa/1xVfHPDLX 0pFenXiQafdirC/OXl3X7vyzDBo+jaFPfQXdxK+n3/P6mYZlnUsjpGGWZzKrv8PUsOX7WCIoNuaX FK7Mthz58vjy5DyYVp/kLzRc69d6hd+VPLMK231mGJbdnhln9QNH6sqwu8cgnUBeU3xpxYBfiOFq TibyP5pbgsXljykoUmkUkcrKorUhfg+0wpvxGKsp8k6JqumTXf13TNM02N1jEUemBwpYci5PIjrU H7C/NuyrK8VQmpWzzrbhba3ufTuIpSLnpGEaplj+B/3idU6b9xgIbcUqvcjY8vuPl3vMPPvkbzjr Gr302neV/KV/bvIRb3eqLNJdESQRgyOBHwDLJGNq7gL0IrhakDpXkLzpZG8tD5P8n2Nm8TfVpLb1 COcssZKv+7gJT92PUXiPsr9o1xVkmh+T7u61P1PMPlry6bUhzJPaR+q4epLKRKtK8+vvyxVmek+X 9B0hpn0uygsvXCrP6ChARGWKggbbc2+/FUxxV2KoTUv7uL/jJ/xq2KsV846p5j03RTc+XtK/TOpe rGq2fqJEPS5cpWLOyCojUhRX7RGKsBs/zG/O5o4frX5aEMkUrXTLqMCh5EQMgjU8iodqrQ8ute1C qraj51/POO9upbLyLbSabbAPHC96v1i4X6wI+CMCFR/SBfdWHep+yVU48o+dvzA1zUo01DyXJoWm VImurq6Vn+H1lYpH6aOfjjTjyUVDculKqs8xV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KorTf72b /VT9bYqw7z7o1+/laxgsvKo1WWHVGnbSLK/+p8UZp2NwJS1qshYuHeJvhJYjenLIxFBtzT4pXZls Nz7h7+XIeTEdH8h6bpsvGy8g39iLxLcX08eqMGRpESYxsQ/xCKR2RnT/ACt9zkmpGweS7Qx2s83k O8ivhLwCDV6ekFVKSiQSry5VYfzfD74q9D8l6Bp+jaLEtpaNYvdrHc3NrI5kaOV41DIzt8TFaU5N viqfYqgtUtvrC2w+pxXnp3MUtJm4CLg1fWT4Xq6dVG1fEZGQbcU+G9zHY8uvl7i8i83+UIr3z/qd 3deVLq8tLm7tJLjWxrK2sPC2slZCtskocGN0oyMlG+1vsRJqQ155EtH06zgH5fXj29nxECXGrrG7 nkXT1ZC7N8Lyvxq9eR6V6Kp95b8i6TdXMem33lW50i1gtaBn1J5SP3sjqCsLUZWaeT7be3EjfFU+ j/J78v0gMdvazRI4qWju7mhJUoGoZCtaHY0xVmyIqIqL9lQAN67D3OKt4qoXcSS+ijglS+9CQdkb uKHFVn6Ms/5X/wCRkn/NWKu/Rln/ACv/AMjJP+asVd+jLP8Alf8A5GSf81Yq79GWf8r/APIyT/mr FXfoyz/lf/kZJ/zViqQaj5r8iabqB0691EQ3yukRty05bnIAUHw161xVBN+Yf5ZKiSHV14SRmZWr cU4CT0qnbb49sVZVFY6fLEksfJo5FDIwkkoVIqD9rFV36Ms/5X/5GSf81Yq79GWf8r/8jJP+asVd +jLP+V/+Rkn/ADVirv0ZZ/yv/wAjJP8AmrFXfoyz/lf/AJGSf81YqpvZ6ejcSshalaK0zbH/AFSc Va+raf8AyTf8l8Vd9W0/+Sb/AJL4q76tp/8AJN/yXxV31bT/AOSb/kvircJtbZ5XVZFjKKWZllI+ HkT9oGnXFWF+e/Lv6T8q2Fi3lJtUA1V5G02O+NusAdrgfXHkRkMgb1OXp9i/bjURiNm7PK5XfFsN +XQbfDkwvTvyzh0fUby807ybqtw0yvCnr6wCoD1DmP1CXPIgMTMTTfjTvJpTrS/JFleX1rb6n5Jv LGOeq3F22qNOI2o78uUcnNt1pUkfa6Yq9C8veTtC8vy3EmmROjXKospkkeUkR14jk5ZjTltU7dqY qnWKoHVoTKtqBZi94XMLkFxH6XFq+tv9r0+vHvkZBuwyq9+H0n4+XxeVeevJ8115u1DVF8gTa+JJ ORvP0sYY5lTTliBW0eX01LsTAfgFaciOjZJpY3YeTLXU9SFld/ldq1oIfSaa/k1qcFOREtBKHUyU Zm3hdjUfFSuKpjp/kOHT9QtNT0vyNqLahZllhafWlDuCxmCTAu4Kq0hIDGoNPFsVZJ5W8k2R1yz+ teWL/SfqQSeG8l1L6wpe2ZWjR0jdkILSsR9PTFXqIZSSAQSOo/z+WKt9cVU5f7yH/XP/ABBsVSnz lpGuav5dubDQtVOiarI0L22pCP1fTMU6SsDHyTksiIUYV6HFXm6/lb+ecTWrQ/mq5NoqrGJNNidX ooVvVDSH1CxFauTTFW4vyy/PPToY7fS/zOeeGQIt1JqFjDPIhoPUkhZxKxqQQqFhQH7VRuq9E8s6 P5k0/wAvrZ6zrjavrJkkeXVDbxwrRpCURIU+EBY6L1NTU+2AsokA7i0b9S1f/q5f8kE/rgo97b4k P5v2sZ1fzHLY38tjPFqNyQ6xyTw6YJICHUHkZa8eI/aJ6fdjR718SH837Uvbzqohif6hqxjeL1Ao 0gVUCVowhFftV5MAO2+NHvXxIfzftZnDbarJCki6gVV1DBWt0VgCK0IPQ40e9fEh/N+1f9S1f/q5 f8kE/rjR718SH837V1nFdyQ8pLyQuHkQlViAPBytaFG8PHEImYg7D7/1q31Wf/lrl+6L/qnhphxj uH2/rd9Vn/5a5fui/wCqeNLxjuH2/rQ19a3bLHCl/PH67NGXURchWJyCP3fUEA4CPNsxzjuTEbe/ vHmutI2sI44Z557xkiRPrUic5JCpapf0UVa7/wAowgUGvJISkSAI30HL7Xnd3+Tum3D3KnzP5igs btma40+2doIJC7hnMqxwr6rSL8EjPVmBNTWlCwQ9l+TAiuhf3Pm/X21BHpHLbetAiwrP6qJxYTNy EQ9EsHoU2CgbYq3J+UeqS6kJf+Vh+ahpw5stqs04lBaioPWC0KKgOzRli3xcq1qq9Rju7ZEWNRMQ gC/FHMzUA7kqSfmcVUry7iktZ4kWUyNGwC+lL1YED9nFUm8yeZfM2maTa3mneXX1K+lupYJNLFzB FL6Mcc7pLG7ExsX9FDxrUBt9wcALPJEA0DxDb7v0ckin8+/mWkcjr5Al4svKJ31C0UR/Ap/fgnbi xNeJ/CrYWCPbzn53+pQSnybLHdSCZpLV723JRIlXixcfD8bNsOtBU0O2Kph5d8xeZby+a31nRU0u H0TIs63Uc49VX4mL4QP2Pjr4YqyXriqB1aIyLa0tHu+NzC5CSCL06N/fGrLyCdSu9fA5GTdhNXvw +k9Lvy+Pe8q8+eUxe+ZdX1f/AAFf61OzQWyXFtq/1ZbhBAA0v1dpUjQIH4dCxIO3EmsmlIbX8vLe 8uQt1+Vl9Zm4DzvcHzA7R80XmsMvCUv+8kUBl4cf2t2xVEad+XukT2tpey/l5qUNxL6kUUf6Yb14 4VKTq7yGZQrySzOPtl/gqTWlFUztPKctnAsEHkLUVtLd5JLaNdZqed0sSTAM83qrGFgj4KNh4Lir KNF/LDyrd6LA17pE+m3LljPareXBKkSs3EujoHWtWXbbkaUrirMtH0mz0jS7bTLIMtraII4Q7Fm4 jxY9cVREv95D/rn/AIg2KqmKuxV2KuxV2KuxV2KuxV2KpdoOpWd/aTSWjmRIrq5hkJVlpJHO6utG C9DtkYkFv1GKUJAS6xifsTHJNDsVQepXNvbfVp7iVYYY5GZ5HIVQBDIaknIyNNuKBlYAs1+kNw30 F9CsllJFdWsqB0uI5aoysSKoyBgfs9QcIN8mE4SiakKIeWCT8j5bScnWrW3EqNDIk2qTwztHBNyY RwzlXK+rbkAom9CFwsV9jcfkZf2jS2+uWJto0WN/U1meAcZQbz4g8kZJKzEsT/lA7hhiqaabov5Z 32o21vperW11qlrcGdYbfVvWlL8jI6NCC44kxFuIQUoSKb4qyXQvJWjaHfPfadaendywrbzSvczS F0UKF5B+QqPTG/z8TiqcTSXEKzXBjQhY6lQ5/Y5H+X3xVgH5laek3kiDl5Rvteig1CaWXS0uX+tx qfrC/WUdZJJHVmcUjXkQj7J8NBGPJuzm5cwdhyFdB7uXI+bBo/JNzJpN1635WXaXBQD6q+uM63Ie 3kkl9Q+q/wATufSJYkmoLE8VyTSyC58sTx2cEdt5Cu3b6ikLwNqioUBlNInlVjybggL0YjovTFUq j8iSpIrf8q1aNFP2/wBLuwVQeQPFS7n4t6AEj36Yq9v060js9PtbSJSsdvDHEisQzBUUKASOp2xV ddWcF0IhLzpDIkycHeP40NVrwK8h4qdj3GAi2cJmN11FMR1/8ofJevateapqMVy09/xNykVzNCjO kJtxIPTZWVvSopoaGg2rhYK11+Vfk66kuZLmG5la7kllmBu7gLWYkuAquFUfEeg6bdK4qqwfll5R t9Sg1KG3mW6tpElgJuJ3RGRuQ4o7sqjtQClNhirKcVdirsVU5f7yH/XP/EGxVUxV2KuxV2KuxV2K uxV2KuxVpVVRRQAKk0G25NSfpOKkt4q7FWnRJEZHUOjgqysKgg7EEHFINbhDtHJbittEnpKgUR1K BQlaBQqt49MVJJNl5Zqa/l1eu99qH5eGaa2hj4zS6aeXFZBEkcbGLdgZq7dt+wxQtSw/LSNJGj/L XgZoX5omkkFkeSSIqQsOxehNDQ8Tv3oqreXH8heW79L7RfJctle380lul/8AV5C3KSQVjS4lU8In LckVG4U2XpTFWTWX5jSXWowWA0DUYZJpjA0ssLrFGwqOTycSOFRTktcVZLOb6aOa39KJS8dC3qMa cwR09P2xVL9Z8rS3+nw2VrrOo6b6Us0zT285Mr+ukqlHeUSNwRpuSAEceK02GACmc58Rvb4bMaj/ ACm1JZzI3nzzLJH6qSrC11CFHGQuUqsKsVYMVpXp8hQsERa/lfdwwXcUnm/Xbn60iLyluEHplJEc snpRxkllThVyxoTWu1FVS7/LJ7qWaV/NGto0zBv3V0EChQQAoCbbGnuPvxVOvK/ldtAS4T9KXupr OUIN/L6zIV5V4GgoDUbe2Kp5irsVdirsVdirsVdiqnL/AHkP+uf+INiq+SRI0aSRgkaAs7saAAbk kntireKtBlJIBBKmjAdjStD9BxVvFXYq7FVG9mkht2kjpzBUDkCR8TAdAR44qxPXvzL8t+X9Qt9P 1vWbLTru6T1IY7jkgKcuHNmLcVXltViBiqH/AOVu+RuHqHzRo6px5hmuI1BQOY+akyjkvIU5DbFU HF+ef5dyiQr5n00CJS78yybBnT4ebLyPKJtlqeniKqp75e886V5jt5LnQtRtNRt4WCSSwBmQMyhg OXOnQ/w64qmkmo3yozViPEE04N2/2eKpoWUEKSAW6DuaYrTeKuxV2KuxV2Kqa/70yf6ifrbFWAfm AnPyrZrDd+YrGZdQmkgutMiaa9V1FwD6qMCTb/ESg7jh+zUZGPJtzfV05Dly5D7e/wA7YTY+Z5rX R7O2luPPlzPZC4lurwac8ck/wqHI+s+pxRGgYxxsS/xdzkmpEWWryXFs14bjz3ZpLBby0ngRGFZT G8QUoxDp9o/FVga/FQ4q9G8ka9bzwRaOkWqNJbwvN9c1SF42dfVIALuas1GHvx64qyvFXYq7FXYq 7FXYq7FXYqpy/wB5D/rn/iDYqxf8zrDR77ys0Or6dBqdmk0c31W5uxYLzhrIjLcFo+DBlFN/ntXF Xln+A/yvtNN0ddO8u2aWNvqE17HEnmFfTYD0RNN6skrmQJ9Whd0FOJUfFQsGVZb+XP5cfllHa6bd 6fokemX+nztd2ljFqs1+IZELx+rVJ5I2r6pBrXsD0FFXqWKuxVAWNjYvY27vbxM7RIWYopJJUVJN MiAKbsmSQkdzzX3lvHHZNHbxKtXQ8ECrU81+QyVNRkTzYzrf5feWNdulu9a8uWmo3SqqLPcwwSuE QkheTEnj8R26b4oQg/KfyKIYIB5UsfRtlZYI/Qg4qJCGegr+0VBOKqcf5Qfl/EGEfk/Tl50LEW1t UkdDXr/biqc6H5T0vQbd7bRNGh023kcyPDapDEhY9yEIGKo+e3u/QkrAwHE1NU8P9bFUZcaJpFzq EWo3FnFLfQxtDFcOgLiNyCyVPaorTImIJttjnnGJgCRE70qacqpbFUAVVlmCqBQAeq2EIymz8B9z wS0/JX8vG1m7la28tyCaVY+cOqaks/r+okirIn1hoyzSJ9kAdj7YWtHt+T35c6jcahDrz6PZmX6/ NHd6ZqV2t3EtyqWzSlLmWWGvCJ0fkhUECg+1ir1fyf5M8qeVdOFp5btFtLKRUICSSShgoPFuUjPW vImvetcVT7FVNf8AemT/AFE/W2KpFr/lB9Y0qLTjrWpWSJNLLLPazLHNIsyyr6TOF+whlBSnTiuA Cmc5cRugPcko/K6+Fv6bedPMMknxUla6QNVlda/u44+7hvCqilMLBGW/5f3sL3R/xVrMouboXSer OrGH4WUxRURVEbcvssCB2oaEKp15d0S50i1mguNTutVaWZplnvGVpFVlVfTBUKOIK1G3fFU1xV2K uxV2KuxV2KuxV2Kqcv8AeQ/65/4g2Ksf/MGcQ+Wpn9OylcuqxpqMElzbl22FY4kkcnf+XFXnn6b0 RGjvdPg8tW9vbmfgRY3Z4cqS+oY1iTiWslHxcfib4Q1PhxVkGiXfmGaBLvyjZaCdNT0Y3W1hmglr NKkz/C6wKE+rSByOvLx6YqzrSBrIslGsNbve1PJrRXWKnagkLN+OKo3FUPp3/HPtf+MUf/ERgHJn l+o+9UuP7sf66f8AExhYPMPzUtPKVx5lsjrl3q8HpWExaCxMYspo2LKIp+Y+KV6t6a+2KsE0ab8o rXW729sPN/mGxMRW4ECtJ9XjTiLlREqRM9ZYIGqWJLVYN8TcSq9H/J1PLdzBq1/oeu6lrcdvdfo2 6bUmY+jPbwQtLHGHAb7b8mNaF2YgmtSq9HxVTuf95pf9Rv1YqqYqh7H+4f8A4yzf8nWwBnk5/Afc 8otrbykJpvQ12xeRpC8anREkdSyRvC5YoZHeEzK3Nm/aatP2SwQ1hN5Zs7yzur/zTZX1txdr2NfL 0SC4gdUnKvLHE3prS4Vj7tvvXFXrmlahp19bc9PYNbxH0hRCgBVQeIDBdgCMVRmKqa/70yf6ifrb FWB+e73y7H5Wt31fzLqml2b3d2guLcPDdXDLHc87fisKvwiVWaMhRURqat1MY8m3MCJbgDYcuXIe Z59fPu5PPtQ8wfl0LS1Dfm95gMcky0aGXkzkvVWb07X1OBagqpCmo8RkmpPtDv8AypqllAtp+Y+r Xr2srwzXILLLJ9bM08XIGEUVorZzE4FCACpoVxVn3kPUdIvdOu20zW5tdjS5b1LqepZGZFb01JVP hUdKDFWS4q7FXYq7FXYq7FXYq7FVOX+8h/1z/wAQbFUr813N7a6Q1xafWjLE6sUsoVuZmXpQRsyV FTU0OKsMl1nzmiEi+1RI0cgTTaPCopGASJGVzxRvq0n7z06D1VPYYqgdO8wefRYSPcXeuXMt2Ykt D+gbeNoDI0g5ECWjIPSHIt0DL47Ksul0DzncJ60PmiSAvHWKN7GCquwkI5gHehdNv8jvyaqqe6FB q8Gj2kOsXCXeqJGBd3MahUeTuVAVBT/YjFVbTv8Ajn2v/GKP/iIwDkzy/UfeqXH92P8AXT/iYwsG MebIfzGbUYn8tLpE2nLblp7fVBNza5jflGI2i+wrL1duXE0op3xVhkyf85IajaGNdP8ALGlzRM8f KQzShw0bxepGB644fvFdeQVjxKsvE7qp5pKfnd+mbNb+Py/baKZEbUBZ/WPW4gln9LnVeTk7lu3S hJOKvQsVU7n/AHml/wBRv1YqqYqh7D+4b/jLN/ydbAGeTn8B9yTaZ5RmsNdk1T9OaldxSLIDp91O JLdWkK0KIFWnELt8+uFgn6RRRlyiKhduTlQByagFTTqaDFV2KuxVTX/emT/UT9bYqwPz9rkWn+UY L6XzemhW3124hl1i8s0lcki4VYY4+CBWjcDi3H4lTvyqYx5NuaNS5cOw8+g9/Pm80vvP1pe2rQXv 5l6ZJYwXDRagraGoiljciGTkrmQG3Zrj0nqux6tQEtJqR0nnFZ9WtdK0n809LtdRUfVLewbRBQSO QsXFCUpwSTj4D4jsAy4qnujatrNw2m2Om/mBbSXtxBWQnTFU3U0k0jLNw4xhKR+nFQGg4nwxV6fo FrrNrpkcOs3qahqCs5kuo4xCrKXJQcBsKLQYqmOKuxV2KuxV2KuxV2Kqcv8AeQ/65/4g2KqmKuxV 2KuxVzcuJ405U2r0riqB0IaiNGshqUKW98IUFxBE/qIjAU4h6DlTxpkY3W7dqOHxJcBuN7I10V14 t027kdDXtkmlIvMCebY5ID5fhs54qN9aF7cXEbV24en6YceNa4qlNvJ+aRulSfT9MWBWQSyrdXNG UsOZj/a2WuzKPmcVaT/la/qQepa6OIw4+s8Lm8JZDseFQvFh13rX27qtofzUNxOrWmlLCIwbd/rF 01ZP5T0NPHbbtXFWUWEF09hbnUVVL1o1+tRxSSNEJCPjCljUrXxxVGYqo2aMkTBhQmSVqezSMR+B wBnM2fgPuVsLB2KuxV2Kqa/70yf6ifrbFUh8w+ePJOj6XBf67qNvbaddXD2cEs4JR54+avHTidx6 TjADbOcDE0ef690jg/Ob8oJ5jCut2qzqrSSRSQyxuipV3Lh4xx4FTyr9lhQ74WC+0/N/8sJozdR6 gkUavLEkzwSJy9EIzcPgqdnWg6nwxVkuhan5b1q0F5o7Q3VtBIYkmjSiq6DcLUDoH7eOKptirsVd irsVdirsVdirsVU5f7yH/XP/ABBsVUtRdltGKsVPJBUGhoXAPTFXnPnX85fJXkzWbbSdfv7iC5uI hcFo1klSOIsUDPwJbcqdlUmgJxVLx/zkP+Vn1eO5OtXAhmRpLd/q96RIiSekxXjGdwaEqfioRtiq FtP+clfyvlJ+taleaeKEq1zBcEMAKkj0fV2oQd+tdsVek6fqUOoWFtqFnPJJaXkST28hMi8o5VDo 3FuLCqnoRXFURzk/35J/wbf1xVFWE5WGZpC7hJKDZnahVegFT1OKQLVvr8H8sv8AyJl/5pwWy8M+ XzDvr8H8sv8AyJl/5pxtfDPl8w76/B/LL/yJl/5pxtfDPl8w76/B/LL/AMiZf+acbXwz5fMIW+8w 6XYfV/rTTR/WpltoP3E5rK4JVdkNK8TvgMwGzHppzvhrYXzHL5qWoyN9fcNK6RpDG1FdkAq0lSeJ H8uSaHllj/zkd+Vd5c/VU1W8S4aUQwRtHcn1SW48kKc1Cj7XxEGm+Kr7r/nIn8r4Qix6pe3Fw54C 3SK6jYOGKMrtP6MaFWFDycAdelTiq5f+ch/yraYQDV7z1w/pvCYL4Org0KlePIkd+NaYqreV/wA+ vy48zavDo+lajeyalczm3t7dorsFyql2cEVVUCoSSxFAN8Vel6e0i3wT1HZGidiHdn3VkA+0T/Mc VTBf96ZP9RP1tirzr8zPMEdn5Nt7w+brfSlN9Kn6W+oi8U+m8qmL0gSB6PHjI1RXiRtWmRidm3NH hlVcOw2+A3+PN5uPzAuri7WJvzZ0iOS4tFMrppHEhG5KLiGRukvE14lae5pkmpfofn69vfNkOn3H 5kWN7aRqblGttEaAuFMo9Ac43jEPpw8uXqEsSAv8zKsrv/Or20MUY/MC0tZZVDSNFpZkBL0YPUDr 6cqdadK/JVPPJvmDUdV1+1t4vOVvrcESS3F1aQ2Bty8IX0xWQhgCsksbUqD8wcVej4q7FXYq7FXY q7FXYqpy/wB5D/rn/iDYqp6gjPbFFpyLx0rsPtr88VS6XTkknWKZbd5yPUSNzVysZHxAFa0VmG/a uKoY2elL6KFrFeXJLdeaCtHVGVBx/nZVIHcgYqiv0Gu3+j2+woNuw2/kxVWGn3QFAIwB0HI/804q 76hdf8V/8Ef+acVRGnxSRCdXpy9Svwmo+wviBirEPzVuPN8en2y+WdR1DTLgF5J5tO0uDVmdFaNe BSd0VG+MkeIB8MVSDRPLn5x6n6t0PP8Ad2EMUphNnf6BZI7mOUM0iN6nIxyJVV8K7dBiqcz+SvzU kklKfmK6RST3DpH+iLIlIJDH6EXIFW5RBXq/7XLoOOKsw0Gz1Wy0a0tdX1D9LalDGFutS9FLb13H V/RjJRK+AxVH4qlN7b30mpyNbpEy+jED6kjIa85PBHxVLNOht9Si+uacumXsQPD6xb3AlXkn7PNI mFRy6YqujsY55rm3jg02Wa2NLuFZgzRmVS37xRDVeamu/UYqqG0kE8CmKwE83KS2BnPN6UZ2T91U 9QSRiqrHpF9GwaOzs0ZfslZGBHbakOKomygvY9RQ3CRqDDJx9N2f9qPrVExVMV/3pk/1E/W2KpB5 j0LzTqWl2sGn65Hp1/BeC5luxZpKkkCs5SAxSO1NmRWcMCaHpyoALZzMSfSKHzYvL+Xv5km7tJov NtjFbwFWltBo1vwDEoZXhbnVGcqxDGpFcLBqXyX+cRn5jzhpjxlgsqNpCr6kTIquDSYgb8iBudh8 VCaKplH5T/MWOJB/ii3lmjlJjkewjAENZAsdFNSeDoC3LcrXviqeeWdH1+waZ9b1GDU5mSNLeeK1 S1ZAAfUB4lq8moev0DFU+xV2KuxV2KuxV2KuxVTl/vIf9c/8QbFXXH92P9dP+JjFXmn5qJ5ah1m2 1LVvLmpatLa28YjutNN60nF5nX0xDbAo8aseU/MjYpRXNAqrzk2v5Wy6Un1v8svNH1+eNAbNYbti ggjVIwZBInCqONgo+IVp3KrLPJvmjy95MlmsdL8geY7SLUppCJ4Ypb5XFqAPUcyyAxIWkKpQfEan 3Kr2nFXYqpxf3k3+uP8AiC4qxrz1pWs6hDDFpkl/E8ivHJNY3aWgi3SRZGDbvvHw+HsT2OKsb0m0 86Wt/Y3p0nUuImDSWk2uQSwKJI3hkVk4fvRGI1kArUu1a7HFXpuKuxV2KoVpRFeyMyuVaOMAqjuK hnruoPjirzm+/IT8pLyRGbSbiKMQ/V3t4mu1ieP0TCAymu4qH5Cjc1ViT3VR0v5O/ljNHbQzaXcy 21nGkFtbSSXzxJGgUBeDMQa8AWJ3Y9a4qho/yM/KSKb149Gnjl5o/MPej4klilG1abvbR/dTFWf2 slja20NtbRPHbwIscMawy8VRBxVR8PQAYqvWQS3kbIrhUjcMWRkFWZKfaA8Diqqv+9Mn+on62xVi nmjzB5osNFtLnR7jRLm7lvJYZpb+4eztjEplokTKZi0ycFVxXqG2HYRLZliBKgCNhz58vhz6eTGd E8/eeHiuG1TVPKLtMyfopbO+ZuTSspSNy7JUMnLiwFaUPEn4cLWiIvPHnSaeN4dT8pNbMkcvH67L V0kUlWSQV+FuoPH7+uKs08rXfmG7sZJtbWzEhf8A0drB2kiaPgtTVvB+QxVOcVdirsVdirsVdirs VdiqnL/eQ/65/wCINirrj+7H+un/ABMYq8v/ADc8zXmi6kktr5oHl2SO2idZri2e6tUJlkNGhSRT J9YWNlZvSk9IJWqc6lVitn+aSxXVbj8x5ZZFHCeBdEkWslvFE8oHqxKv7ErMq8W4tsv2cVSqXz1r enFRb/mzLciOS4/St5+gfrMEb2lTcqwJX01i68YxX4kpUFQVXu/k3zZoPmjRI9R0TUDqdovGF7wx PCXkEauSUdIiCQ4JotN6YqnmKqcX95N/rj/iC4qwL82IdOUafeXUtqsqLMkEF99aED8GjuHLG0Vp BwS3LdaHpQ1xVL9Ou/yq9Rbe9dTqt0IbiZYhfxwSvDVoJUEhKBqJ8NW5dsVT3yheflvpt++i+XX9 C7/dwvbEXR+0stygDTVXoZGqD7eGKs0xV2KuxVh7flP5MaIRvFdMAqryN7dV+E1/35Qb9ht7dMVc n5T+TEWVVhugJmDyUvbqpIr39Sv7RxVVg/LHyjDLbyxwXAe1l9aE/WrigJpVac6cPhHw9PvOKqmj flx5V0e/gvrCGeOW2LGBGuZ5I15p6Z+B3ZTtXrX9VFWT4qpr/vTJ/qJ+tsVYF+YGlpf+XNPWe18u Xwg1C4meLVqJZcEiuTWNjz4zp8LSH2c7dhHk25vq68hz58vxXk80n8vR3WolZ9K/Lm5KXMbX5llR plMzH146LFGKxvKONd2O7GrUwtSeaZpHm3WdVu0udC8nu8VvJ6Nun76aQEKLe6jIeX9w3EUDcDud v5lXrvlizu7Py/YWt5BDbXUMSrNBbV9FW7hKkmn04qmeKuxV2KuxV2KuxV2KuxVTl/vIf9c/8QbF XXH92P8AXT/iYxVhfnKXzudfS38tXelGc2XqW2nak6gvIrSF2MaI05UusA5K4UAPUcuBCrHGm/P2 a5Fr9W8sx3FzH67RkyGW3UKSEdOTVikeERM68yC9RstMVTvymfzLfXZrLW5fL19oUXwzz2nq/pCR vTZZGljH7kcrlONOI2UjrirP1VVFFAA8BtireKqcX95N/rj/AIguKsS/MLUNYs3006U1ys7i6DGz sor+aghqPgkKcV5UPwnc0XviqnY6R+Yd7am6PmdrMXES+hbTabAZImFPjccl+JgDyTtXrtirNFBC gE1IG56VOKt4q7FXYq7FXYq7FXYq7FVNf96ZP9RP1tirAvzC0+5fyvZJHbeXXb9IyJLaaqwjsZYb n142WNyu07iTkwp8TchvgjybcxuXU7Dnz5fivJ53p3l1R6elQ6X+XtxAvM2chkWaR4+bycGi4t1t Buwr38di1Mx0z9MW2oOPKUHlaKWSlvaFJn9VreONCiGKEsAqwJGRxPQA8RXZVldtN+Zf6Vt0urbS v0X6tLiWF52m9Gj7gOEUN9jbfvirKcVdirsVdirsVdirsVdiqnL/AHkP+uf+INirrj+7H+un/Exi ry381ofKH+Ire413y3rGpTJDZLBqWmJzjXleOqRPyeNKrIwqBUkP0qqkKvP9R0P8sZpLs3X5cea7 +wZxK12xvJZ5ZHiC8Vieaq8OdBVgBQ/Iqs70T8oPyl826Naam3ljUNLhWZ5YLS9ku7GcScVjeVol m5AvwrybcmrftVKr1aztLeztILO2T07a2jWGGMEkKiKFVamp2AxVVxVTi/vJv9cf8QXFWN+frXWp 7C2OlW99dSRylpotPvk09yhFDzdwSw32C71xVB6N5OvrrR9Imv8AWdctLuBjdS2z3yu5aXixguHS JBKiFdhTapHtiqa6H5SGkXkdz+mtUvwlubb0L669aI8pPU9Vl4rWX9kNX7O2Kp/irsVdirsVdirs VdirsVU1/wB6ZP8AUT9bYqw7zl5b1C/8px2Ntoeg3UiXrXElhqQc2IjMkr+spWNSszcwzNx2LP8A a7iIoNuaXFK7Mthz58vjy5DyecaL5C8/WV/xsvKvkGx1O3BEktu1z9YSJ41jPLgiyKzqzHnyq3Ta vLC1Mr03yl55svSvoPL/AJYsddtpZjDcWgnKGH0Y40oxEbq7l5AV3ULTeu+KvToDMYIzOFE5VfVC V4h6fFxr2riq/FXYq7FXYq7FXYq7FXYqpy/3kP8Arn/iDYq64/ux/rp/xMYq8x/Nbzre6Lrlrbxe Y/8ADdlbWoutQvmsReRCOZ5EUOWk+0zQ/uwsRbZt/ipirCLr8xdUEa2p/NG4Zp1cRsugxrO0KSTs 91/dxBUWEKpNFrw5qPiFVVTyb5g8w+cb76tov5sO3maS3R57D9En0oOEaCZgkiRQmjFhyI48j4jF Xt/lfT/MFho8dv5g1Zda1QMxlvkt0tEIJ+FViQsAAPc1P3Yqm2KqcX95N/rj/iC4qgdZ8t6LrRhO pW/rm3NYvjkShDpJ+wy1+OJTv4YqlX/KtfJwWERWJi9HhThLL8SxvG6q/Jjy3t49zvt1xVdpv5be StNuYrmy00RTQtE8TerOwVoFKxsFZ2WoB8N++KslxV2KuxV2KuxV2KuxV2Kqa/70yf6ifrbFWD+d fLcV75XgtZPKw1P0bu6mGkWd6baP96lzWV5AIufrCQ8k4mjv3pyyMRQbc0+KV2ZbDc+4e/lyHkwO w/L+GLzD9aP5Z3sU13NJbXmsDWUYrFeJ9Vmk9FpmrAsD1VRv8I+GoqJNTM/y70SdNam1G98sT6DO luUgke+a6R1mZS6+mfsv+7DMe5qTvir0XFXYq7FXYq7FXYq7FXYq7FVOX+8h/wBc/wDEGxV1x/dj /XT/AImMVYj5wsvzNk1yxuPLE2lvo6mCPUdP1JXPqIzyfWJFaNC4dE9P0/jp9qoO2KpPZWf54x2i xLa+U7RYeEcECQ3nAQ1CsAFkAUrH0UbE7VA3xVrj/wA5AxpdFIPK3qiYC1MIvU9SEVNZC5biWNAQ Om9D0xVaG/5yOS1kKp5WkuSgdFl+u8BJVQ0YKFTxpyIJ36Dv8KqrCn/OQcF3DEX8t3dmZWE9xP8A W1lEQCKhVYgiljR3YU6mmwxV6JF/eTf64/4guKqmKuxV2KuxV2KuxV2KuxV2KuxV2Kqa/wC9Mn+o n62xVUxV2KuxV2KuxV2KuxV2KuxV2KuxV2Kqcv8AeQ/65/4g2KrpI1kXi1aVB2JBqDUbjFWNeYl8 9x3sZ8vwWM1iIyZRdz3CymSjUC8DxpXj1/tCqEH/ACtA2hBtNLF3Rlr9YuvS5B04sDu3Exl+wNRi rpYvzS9FljTSPV4vxkMt4RyCqY6rVercgd9tjv0xVF6GPPj36DXLawisDES72lxctKJew4v8PH6f 6YqyP6vH4v8A8G/9cVXRxJHXjX4jUkksa0A7k+GKrsVdirsVdirsVdirsVdirsVdirsVU1/3pk/1 E/W2KqmKuxV2KuxV2KuxV2KuxV2KuxV2KuxVTn9Ki+py+18PHlWtD/Lv0riql+4/4u/5LYq79x/x d/yWxV37j/i7/ktirv3H/F3/ACWxV37j/i7/AJLYq79x/wAXf8lsVd+4/wCLv+S2Ku/cf8Xf8lsV d+4/4u/5LYq79x/xd/yWxV37j/i7/ktirv3H/F3/ACWxV37j/i7/AJLYq79x/wAXf8lsVd+4/wCL v+S2Ku/cf8Xf8lsVd+4/4u/5LYq79x/xd/yWxVfB6PN+HPnReXPn03p9v6cVf//Z + + + + + + application/pdf + + + uuid:424ad26a-0391-3e47-a0ac-110085b31b58 + uuid:a1ec6a83-7e4f-654b-b671-93a86e5b3c39 + + + 1 + False + False + + 841.889771 + 595.275574 + Points + + + + + HelveticaNeue + Helvetica Neue + Regular + TrueType + 5.0d1 + False + HelveticaNeue.dfont + + + HelveticaNeue-CondensedBold + Helvetica Neue + Condensed Bold + TrueType + 5.01d + False + HelveticaNeue.dfont + + + HelveticaNeue-Bold + Helvetica Neue + Bold + TrueType + 5.0d1 + False + HelveticaNeue.dfont + + + MyriadPro-Bold + Myriad Pro + Bold + Open Type + Version 2.007;PS 002.000;Core 1.0.38;makeotf.lib1.7.9032 + False + MyriadPro-Bold.otf + + + MyriadPro-Regular + Myriad Pro + Regular + Open Type + Version 2.007;PS 002.000;Core 1.0.38;makeotf.lib1.7.9032 + False + MyriadPro-Regular.otf + + + + + + Cyan + Magenta + Yellow + Black + grey + New Color Swatch 2 + New Color Swatch 1 + + + + + + Default Swatch Group + 0 + + + + New Color Swatch 1 + SPOT + 100.000000 + RGB + 203 + 0 + 0 + + + New Color Swatch 2 + SPOT + 100.000000 + RGB + 0 + 0 + 0 + + + grey + SPOT + 100.000000 + RGB + 102 + 102 + 102 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 2 0 obj <> endobj 5 0 obj <>/ArtBox[52.1289 57.0234 796.124 551.148]/MediaBox[0.0 0.0 841.89 595.276]/TrimBox[0.0 0.0 841.89 595.276]/Resources<>/Font<>/ProcSet[/PDF/Text]/Properties<>>>/ExtGState<>>>/Type/Page/LastModified(D:20080111163617+01'00')>> endobj 1122 0 obj <>stream +H‰´W[“Ú8~çWè¶G÷˾%d7SéL’f2ÉÔ”팱 6ÝÉþú=’Á– mèšMRÕ€¤£ïÜtÎwž½MØ-zv}…Ñ‹—Whôì꣫Dº¹z7 èqt [ÿ†­U5R’&„rD±L´ÑhJ(F £­Ý4òó +án¨æÅHiš(ƒ¸’ á +M9㉔r/°}a$ŒH¨’Hs7¢éá7ø£3w-q×î/}6›aø>[ŽGþÃãÒ«%˜IˆÀÍÖ£ñó÷ïß¾¹z>{óë;t3ûøÛÕì·¯&³¯£)% 6[ŒÆ¯ß>¿ùϧçoÜ2ö·mW4Æ/$'š鯞—Å2[!'u0z¯öž0Æb©/ã]±°Ë¬°‹/“1ÀáZww6[ÝÕçp8ѱØe@•Vâ![Ôwƒ8$áÂI]‚“)â’wbË,·PJ'Rï"ôeìDÆÿ7OäYU'ß×ÍO÷W€‚f×sÚÚÝœ­Ó•=ãb™ íµ¹Ø|¤A¶BS*ÁD‘Xäb8ÄIŸÛtþ×¼ÌËíc€S @ÍæèM°¤&ºä³Ÿ=Æþïwÿwéÿù¯ Ê³_:]°»1Tóà®åv2eQïÕr¢=Ž X'ŠExè‚¿cÿ¯ßÌÊÝêõA‡%¡01Ë^¦q`^5÷öY[\`8YDò—Š Ày¹*‡ìÒ21P¥#™‹ŠãaÔÊ{»­êÆ´z~wÆ,¨ñŠšè +]¦yeYàøê®|È ÆVƒ1#^<—±è—q½Ý á`ãTu¹N `DÄ’gí¡FÆ8‹l•Õg Ò®õE²ç ¢š•E^¦ƒE†0œH!i,}Þ(Eƒ¬ØUv¹Ëó.ç‡ó‚RèTXG—œ·Žë1ÝÕå®Ìó³ÕCJhá XÈ×/²jæ|v¶iÎÄ/zh”òÀ³ÍHWEBÄÒ—á‘ÀûõÝn}[eÅfßO‡S „áñ gJB^’îÙ`½–¡,òd6CB^âó¤N·ÃaÝÎÅ”æœA2¬Œ·»¥ËÆ¥Ýæ¶XÕçJ£‚Ò¨ã;‘  …¤cžnêì\U„n‰±Ò±ä0FÈ4ÀúÜцa ¨(áOe·„†ï¹)›Öƒ~ƒ‰ 1‰>å)“ˆÄ4E£Nk[gk{&L0Àh"£ te±œênŸù0ÔyŒ¥Ž¤Ï–Ä¡Ü—ùn=L^™6±Ô—±î3…u:·Ì/È ÊXB6Ox€Ñüb‹ô6·_½<š|†²¢ _ü×ô>…¦’ÁÛì™” (Nü©P‹¾ù<+†½IX"°câòI¾a£vh{â½Þ·˜Á´¤'L`y<òœs­ ,uh(ĪÇ+;üÊ]ï4ŒL>—Ø, NþHm™—ëµ-é¾nÁ-QT蚧¸<*7¶œ®›Ô†æDÕÑðöËÃúQÿØ Vêº1êx°:B{5} LByÃk UN8F[;ZŽ  tsõ¾PÁЂãп¥Aš$B8MM¢¸€v.jˆ»ñ§Ýa¥áÃW¡Âáj΄^ƒ€i~ÍÊ0•Á²;ÊŒpL±†Æš=òó[ȯt^Ê샻ª…÷æw8q].lÞè$`Úꦢý÷ “Ýz-¼‡§eÎJF¼ó©K5ÀÍwÜ|´+ Þvþæ«w^R -j†!¯aCåzSVY}x¼ €»Ä;Æ 0TÇšy—Ã~òÕnK[&JPçfÿ“úh¶?q÷µ B#8=òû)$dàO…ÊBý Ú†Šºï°];´î¼€ª ñ DHž§ÝùC[…‚Ù$1麠ò¸g¶mèÅßLH)\30®óCfr)|Bn¶%t’ +:‘ý}¥H]ƒ2x¶·4×õô–G*ÃÑã…G¡!Á=GpíêTSöÜàž ¥{6¤ìÙ¼gƒƒݧÖaçT¯vçD±vçD³vçDµv§W7Îz5 ½à>ßœˆˆ3Ìôx“éžP2уÁxé >5}W±­¨îÁ ªOBô³pJû0HéËnҗݤ/»I_v““ :lœ$Ða£/H_2œä‚åW7h_ÖH\S!©}ûØõæÕGê©?ËÕÑYàÖ^ê[?Øð,QÝÙÿ«’0ž`Ž›iÃ÷W÷ Þ-³ÕµçôQÉÛûcRÄÌRäEÁј²$fúXÒO0¦¾>ñˆ¡QR%02NÃUÀ΀¸ÀÜÅ; ©÷6B‚<"d!œh}éš…ꙃÐaVL»¤èå“\9)„B¹8ñ0©j‡ÜÿÂí·=ÝhÆRÇ4GH0á&X¨{¦ŸwèpÄ$K´{ƒhã\Üï^çÍ•i™ã”* Ž>}ŸJwÁth Ó?¢c[ãíOÜ~;°¸˜ý=j ãŽ¸#u<Ü0©(=­ìO_w÷úb!ûbM HùÇ”L€«¥„„¹‡A›6÷Ô+áHúçqYLä¸ÞNôØ3*dáçý„ˆ±õÕ¿&Ì~i‹Mû ÉŸû=õ—‹Ý¤¬?Ý-²²ãGºñ“ë~ü<^îò¼šo'-9¿¾²užM¦„ÿ \qa'lüÝïÆ0­Ì‘?YL¦ll¿ÃIÐÒÀíŒ`Ϭ ìúèžðÄ&ÝU6XmºïV@ÇÝí„:e*;‡ëÊænÛ!”›náÞ]P滵 +Bë¯t’`&o5=tG ï‚Ba‚Îò?£&7äǔԅäŒÄ¼ôT‚qv>_ƒ½ rXLC@_À|†ì±Ä"M]#ƒ‘ýNªO1B ›×A}Gs`X{™B¼^– J‡å +¥à EV‰&0MR9ù‰) wâwpf•¦¹Ôcå™)%: +©™éèþJ ˜i` TL«K2aa•š“XPœ +u$Íå&fæe楓¾À +ÒÕ×YX^°  endstream endobj 1108 0 obj <> endobj 1111 0 obj <> endobj 1113 0 obj <> endobj 1115 0 obj <> endobj 1118 0 obj <> endobj 1120 0 obj <> endobj 1121 0 obj <> endobj 1128 0 obj <>stream +H‰|U{TWÎf­›Ý2'.f<3±´‚+âP+úªˆ "+° 1OJËÕ…­¾q#„¨–CÉÊü/`ö×[8AN‚Ç6Ëm&K@¾ì˜^)JHOKLªM;ÙT{üD!sRy<žƽ‰BŽ™sÑÛŽÃf¢ÓžHÿF 8h†m†Ï'ÐûÈS“­ÌÍž«çøŽÖ(§C­’­z§C9 0ÊÁyÞá ƒ¤1H# Ïãr9l8|°‚î¨líø†êo”ú0èìôªŒU%&úìá–3j¨QêÊ ú1h€—K»xBnF2©ÎRªå¶zâ²2¤ÕòŒØ.gtÜ*¸‡ûÆè⨷þYi­ö‘ß[ýë°r–”Ó݇;Û®pþmæüûzÚ°Ú±u“§4"<Ödgþîýç?Ú>ö>î`A×|8É!ñ|´-~¹þvð·{à ΋_"†Ñû‰^µ9~„°¯•[G~ý±ó§W\%@Ÿ(aÛËÔ¾-SÃ(T4U"¬áRÊM +ˆ_Ç`8¹½@v^A{cNƃ‚G¸a7„l‡‹LÆ +c9MNõ™/½FMž·Và]v¶÷ª}¹ÞT!¢ \ÖKi3¿²2‘½ÃsÆ¡œKºÒ`¢ûL]×)SY¡¾Œ!+ È^ÃÏ‹ó¨ÀQ›3˜‚lÐ=·ÚQ$JþHûÂ(ˆó[Ž‰2»5(ôõ>¾³§}å×LÀñäêVñé†=MªÜ#tíM¼J¾§2”r ð÷ÞÞ6žÈÜJ«KG'%øï¯6§ÒB´F; ÐMŽñ'ÙAÑò5›î"³ ¨lO]*VsJÓF5Öÿ«ÔÌôá9}šÝxJ°ïGö`+žÓGY7§ýà¨'ÈNVÁf‰Ü…PéTù9tŒ***òL¹þ#5nè´ù<ünìüù–cÖJ•uÐ ¿Gèš ;,baFÑĦ7Pl)¯àp `& % ˆÍ—‹QNžQíÖDk£muD^kN«ªÃê„ )…«ÏŠ2í«§¥ä™jkè.t„¸Ù89`î9l1]±5¦ Ê0sð·Hå0BÀRÕ؈ +TA9»„ÎÕÚnÍù}WÖ@¾ÃZ ªvª>ÕZç÷[r,š‹þPè Œ®áî¬L8”‰}ÉnãWY™V¡<´áSÄ `Õ æºªs „9¹J~$ea¿ÜÝi„ w!|?P¥ªªdUU¯®ÛW¿ñ•ƒ0Ù+§ßÍÄÊØ0~™Òw&/Ñ>ÏçÿªUÿˆ„‡‚w^misxqun}u„¡A¡¶š¶±Ç¿¶àÊ÷:÷÷í.+û°hf‚n‰ƒ¨±€«!÷²@‹Ñ÷ëÒøøÑû¯¡¤Ÿ£Ÿ¦÷l÷­ŒÂüD÷š‰uqwtvqûoû±€–ù6—û^–÷/–¼–üt’øJ‘ûL‘üo–¸‘¨‘ `E‚™ÿ Î +ã ã ÷cø‹ 3߆Œ endstream endobj 1119 0 obj <> endobj 1127 0 obj <>stream +H‰|TmPGÞ…ÙåãÉ8†ì˜™1A\8Œ&UZ‚ˆ +"(Š¨,îòq‚‹»dÁC`ˆ²Ë®\ðƒ?‘•‘"~DsRÑ+õRWwÉq9ʲb*ïpMÕ]/¤îþ]uÍÌÛýôûôÓO¿ÓR‰ÌC"•JçoÙ•½aYÜ!}ŽZ“ ×Eêr5nDY©8O&.ðfÐJtllâ_ ¨ž­sX ðõ•xH¥­WÖéòqfVv¢r¿Ã…™ÏŠ.C+$2hó ¦ûtú|^] Õ ¹¹B¢;Í $j Z½Ñ=¸-8)XHÀQá+ÂU+V…Ï +r ‚ZÐk³r0›^« +ôj6O­ß/è2…ÿ³Òòè¤äCùZa• ÑfJ$RÜ$¬D)‘DyH6zHâd’$BÂ`/$q’-’SR_iºô™Ç2Ûž¤g¤çÏ2•Ì û;F؉q2€Ì$ŸeT»ÄI—¿»<«ebÕTÂt ÇÐ=­LH@_0àîL×ʧÉ=31¬A¸OŠCŒ;BîE¡Ë&ˆEáÄVúº`½ƒÎ2_^í⇭ÙÝÛÙí™Úm)Ýš;¼ =›§þ‡ðt¶ä¨RÓ/kPüã7o}; >€ú°˜} ÈSþ‰V†m4åpíK‰æÎ/N»Øo3ò•raCäD+éÁÕ£ë^ßsµôtrÇHú°#•€÷É#m–þŒ¡€Q¦ÜZn©àr͹z-›s°©gô«¦Vž +57´Öß!ô1æ2æÝgYlDüÀ÷ü ëÐÈ öz[eÅžî>A?©ß¿·v‹b–"/´¿ „°{wœõm<5lŸ0£8é& † Âû §RtÆ"¿üiZK<‹¼«ÐÇ6 SM€âþhûð%ÎŽe×®#€%-]–{ÃJqLŽTÓ&†,µšªL\zyvÞ^vsÆПŸ_ýêÉ@WN†ƒ·UØLJìo_¿‘vÁ‡°zŠfØÁØÇÎ^P‚O<ÌAahÍô>ú- +ú1>y4ælvq…?¥êÊ6*îâý#¼õ-Ó{ü½ì7§â2yê&Ò‚bdÐ +rÓì^âð^&Řdè—ãö^û%ù‘ôä‘­„…¼®Wwld‘÷‡þ(-üËRð¹;p¶·ƒ·ï'C“5ë·5ÉåòœÐß|\ú€}:ÞxëK~x°éù?”HþL©µø¨‰Ë2çÔ°±Y#ãëw ÷uäïsòö +›Ù¬¤ú[Œ¢ÌÁ¾JÇ@á ò›ƒ¤ÓÖp¼Žksž¿piÝË£V<Þ#ÿˆ¯uºÊ²ÎPVø‡b£ÂHER¨s•¾€ ßXBñ¶& g¾X‚ý.‘ÓožM%2©û¿×Æ«­9·YWçå>þ¦5·+•¥PS‹q*uFÃÌúS©nFxŠ) °QcþøÁÄ3Hú%\“×Û?¯çÎ;Î^ìao·ªãxÔ†§ôÈëí§Ž;¹Žºóç®°ƒg3¢xÔ„Ç{å°$êQPV^Eq>WPòiÑ¡ƒ + yǸ³s3»2}w®‘¯"éÁÊGšGÚÓí)[þëÏŒ‚{è~·>’~WäöFlQ{ÝùŽ>v¸5}3ι­“Ãâµü?Ù™–[ÈaÂþʯ |¢¿^5@‚‹*vB¨' ˆU +]´WSxÁ"xèW¸àüýBüQ$až´GDþN›“}ÿ»§mߎóÔZo„xð‚vxçWq;!˜~9†Rµ“û[oFY¼ñDóô$„,›¢’òS²8 6¯òñŒ¨=öäX%¨v1ô¤ÃVg?Áõ×]ë¼Á>¾’°z•&yÓÞìK½fÞâ°:ë•ZÑb g×£»g +ÅFâ?×Vo¯ãê®]¼Ëžn°T9yX,·›*«KØ´ø¼(=”¤»|O wI»Úž¶NI ¦_`ègi?,yþž]ç˜Âî‘ÃXž>þ·>Õ w*â[ŠêÏ*Ï·œìîë+.kåÚ~&NæíhHd‘Ïêèå¼5±'ùMŽb¤èd™^©/*Þ½gkóiG¡HÌ…S “¾‚¥ž¯Ä?1Ëcì!J“G7¦µàôçz ²7®Öœhæ­ 'ª—[ìî’¶QÖc»dH +ïÉ©>Ì q׳ï(ᣠ+¢‹Ä&±„AAøDÑMy™µìh9g0¨Ùí7þÈÃ)„®ËѲñ¸×½åºØä¾1‹j£ ð#-½–¡JÊT ‹5Cêt¾óAôt +bÄ…“›Q!Aw›Ô™ûUdeOyOYï&(ô£à@5ÌÃ7ßÇøRuNén§;ýwú"rÖâ+´¡¿î–cLa'ë¶8ÒëwÝBký0nꥤm7%™“+“޳滥åé⇼É´»lWI’Ù TÜ5›]Hó£LΩêÑN':]uÓ†RŒùÅÖLëœý5͵òYx–;Óµ +1¦mž…;j¼¸–ôõåÿööÙo@õ¨Þ>6o +ö̯1ÿ`ÁSé7 endstream endobj 1116 0 obj <> endobj 1126 0 obj <>stream +H‰lU XSWþï{!†°â{/ +B"D´Å€€kÁ%QAP@°€Ô­¨ÕÒZµ¤­kk­ŽZ7¬ŽÕØ:©ÓÒÍ…Újm]j1¢m¿~cÇG«SÈœ—Pëç7/ߟwî¹÷ý÷Üÿž{.?4€‡etaŠ±Èp.’<' ã§Õ”Öíõ›0` YQ7½Æ?úÀFÀ«–|…Ó«çWW}± P~øß©,/-»ÐÐ ôú>½’ÊÝ +oj_§vïÊš9õQ~þy@˜šø Õ3§•îÞÑt ˆÌ¸¨šÒú:^ä€vjKkÊSÖS{!à“Q7söœ›—~«b¼X7«¼®Húœ¥ñ +0æäŽÀË?­€Å<|qf€Š§>o(€éxä9lÔhPÝçøS®8dð§ðý#ÝAÖ)Ôý> +ÞHF1,4[ 1ú£'c/\,•e³BVÊêÙb¶†;Æ]⼊÷ãûðýùÝ|«*D 1‚$Ä a€#ì%1Nâ$¥(KQRŒ”( •J¤òضß]÷9—‹æQ¹ù¼}ÄŸÅ +X ñ/"þ£Ü¼’÷áã»ùC„!ZÜüñ«á/só3—ËõÀu×uͳP××.×Tz!DÈžÎÎÎ÷;¯tžypïÁåŽížqÏu„]®½ÜêÜàlr®œ+õιÎiN“³ßÅ[=Î{}æÑ‹+ ezÉŽÿÖ†Çö9yO>îu÷ù^ÊZ©ï_Êa AP#!´Wa‡ˆD´ˆ&cЋT!A‡ÞˆEâÑ}‘@9¤G’‘R)[Lè‡4¤ÃŒþÈÀ Äx™D;ž…l Fr‘‡!ŠaŽ‰§0 +£‘1(@!ÆbÆc¬°a"&a2Š(_¦ ¥˜Ši(C9Ø…—ш_°ÿ†k°[ñ.ÞÁ +8±oâ6î`56âUÃUüÛ°¿â?¸†&ÀWøï¡‚rùuTâª(Û¿Æ·8op70çñÎâ}<ëxqòº¿ã_x µ¨ÁL<ƒ:ÌÂÌÆ\ÌÁ<<‹ù¨Ç,Ä-)9Åj4õKK7÷Ï0ð‰'3Y²²çäæ :løˆ‘O?¦ pì¸ñ¬¶‰“&O)):­¬Ó+«f<]]S;³î™Y³çÌ÷lýü Ÿ[´øù†^\òÒÒeË_n´¿òêk+V®Z½fíëo¬{sý[6þeÓæ-ooݶ}ÇΦ]ïìÞó×½ïîÛÏxïýƒ;ôAóá–üý£[?ùô³Ï¿8zìø‰¶/O~õõ©Óßœùö»³çÎ_øþâí—œ—;®\ýñ§Ÿ©ZéG4Ã;ßz€±¶fæZÚœƒž‡)‡ù)ÅIÍ`zAÈ­Êq°jpzr$ˆdñz!ÏÁÇæXu6Á.؇•Ù…<¡²´Ì¡ˆu¿©£ÜnK(´VÑÿX«è°Ø´Ír›mñ(d…›Çn#†Ý 3Ü DÐIƒ¼ô#—ocu4äh–›V…\Gk¾ÕÑš£m6¥|)½UEtÇ܃bV&¡ò°QØìöî':Zív­VâöèÄf†n­TÃÇæ63K¾»Ë¢µ²C'êDŠÃ–CÜÞú…Ö\ŠD´%Qq¦RÌ>åä„êA…€B+¥‰Õb¬ZT³–®ÖÐUÏV©Ø U—»œs.ùB8ȵÑW¾T +ä/<_z§´Pð‰´[A-4Ú +up†!UËL!<3™E‰Ý-â&î麹¹?óMßÞu›Ê›²ë;Ô™¾e q2Ĺnps?P©i¡ºã릋 ÊÒBIÍpB!GO¨ Ì#,#¬#4Žü‹²¼èÇÐI‹WDG,’ +LI*©œÉ¶2ÈÊLɼN +àÂBc8“1“3ë˜NJæÒúeR;†ãÄõ=Ò «,™h0L\R][`T®÷34mxrXXòð´ži ÑìbfI–4 ¢1Ôò²Œ>#«µöÍ—”bÍí"%ÉG5W¾i¥˜Ðâ>Ér<ãKY¶}É ¿/IG겇꒪_RY!yên¡Oþ°º-CjˆÚ¤6…ÐÞ:^Çç®åöÏßÏ­½´™Ûŵu¦³Û]¾ì^—’kë +c¿Ê—4ÅÄ-¦˜ü¨Ü76£'MÁèV£aAÔ&„·SŠ¶“˜i‡¯¸"Úo2" } „a¡Š0ŸÐHx‹°›p˜p‚à_äa ”¡2u]"ËךŠ¿æ1r‰–\Ü[íN$²Q8µ"Âf‹SÅh´8Ýß»»‰;ÌRΈæJ1s k™?"mݳ’™­3}…ù™ö4ÅPT1àp`ø"£Q*E¹„_ƒM + ˜ÐÒ ‘ìóÓ%P³z 4É ÝCZïú »¥`'‰{„9µ,dºÂ Ü,ÈwTî°?a-Z{šÉsèܧל‰ô’z< é·Ð :V®¿n¬ÑíAÐøZt±0?Ëú”¯:ÿm‡˜Àüð_â]6Ä5ì¡aívæÑ~Wùz¶Š+¬Yúc]°3Ùë6‹ãY@Ã7ÿ˜¹x=ì<µ—ð.c øÏÖ§KrlP­Ñ÷ï¡÷‰yVËÆÒì=M<­ÚŠìãg–ö +³ÈxþUp70o Ø$yz\pÛ¾g+û°—ýe(€½™_…ô½ïx> }N¯éOÛüãÉÊ-à=àÒA"ÈzWý¥ ¹¢ÁZå-à2ðØŽìç<ø}} Í![@‡F±œ•;Ú2k׎C§ÓlÎ0BÜ&¾`] ÏÂÒNŠs˜%翉U®1V¾ÊX¥ýuŸPeTå+¡ÿ=z”^å_ÙÏ+:à†|B%ȹmE»ôÙƲ]Ó¾þ°&öˆ÷µ'´“ª}I¶sho;ËöWÉõ¢lo¢Ýýû_4º[¸õ©ß+BÝ@:•½bÏèæ©g¬Þ@¢w&W·”W$•,˜C䕬±Œ(*7©ø‚’Ì)–•ìÂmò%»Ù_ÄY%ë¼W;ªäu,dä7mÿÂŽ[JæÌ㸯dÁjœ5JÖ˜ßY¯d 8[•ì´ P Ö@¹6熱™Å¥Å…¹™CæÜÒÓ‡—ž]8º°´8¶txùÈÂÁ§Žš©¹³§¸±§‹þ¯—ÿ{åÿ¢9³8¡ˆŸÛ—Ž.>aöÈÌ‘esâéÙ-æÒó_¬Wg”Gþ#t:Y8Ð0ÖóÞjO²lDR°t2² wgrêéÙmÝÌô2ݳ«%lÀ&GgrÎɦ‘5SÚFårHkN´=ª•4Deý5Ѷ—É¡æ&Ñ©®…qf<bv¥,lªÊ’Óv]S¹Lé‘ +¥vŽd¶ûÌÔš@IÞ­R¸Ã§m®®ÛÄîÄíâÐ:•[qÜ–=Sú,²±0ÊÆÂ8ÌX_9Ö‰¸XUjÉ’£%‹v‰íÍV2»K,Uª!.¬2±õü†ØÚj{ å º"÷â/>óhÕ3}¦¤§(Q¹,—׎½!]wCRBB:šAznu!b&Zò~¸*µMtì‹ITG⬲ºÏ2[-”niªN÷ØÅͼÓñuiµmÕ¶:Ñ~}í쬆xö!éÇWîâ@[»Yt¥ É7íÊðú*TXZ+“oÝE?Šq@ei­‰Gýå‹gdG±é”²×ú°½ÁÙ¹í-[7íy-6ÝAç¹eé§t è‚D²vÄ’`ùõL‘°õÔNV8‹µ¹*\ÃOTn¼fƒdZ'âLIß~,˜Âž]Ðn¦M±6ýZ§Yf†L5¯­hÅòÁà›×²#fXz:%°ÂÙf×¹ÞÎ-[ƒAsœ:M>G¶¬±ì+zïŽÅ~%]E¨^>ÒÑ`¯És¢_™_ÊÇ#Ÿ<"QOHì…AC”Ðè  ³c3ﳘÃ^+ã­ãmã9Ž$ +$µd®^=D«=8zèAÕ’6½Iþ†œ@Îù^=¦åˆ¯L˜Èò8O“Ø|t6ä}¬åí kl¾²Ú+\Mràcò»ª:¾FK­,XKó°G‡Ê8Z3¦¦Yágij uæ‘¥!²^ôÝÀž›`LáHÀåY·A¯ š®f@ru„»*ãÿÁè¨òÖîÂ+Ý6‡ÖÄx[°“pÿˆÛEZ±qb8X×mIû&ðáêŠ_í…£zcáV½1:Jâ?׳ÿñÇC§?ÁÝÇEÇñƒa]t;×ñh¸SØÀè6òØ· 'ð#ï$œÌCè)8•Ç·Óqî€;âNü¹3î‚»ânô~w^ï{â^8 ÷&›ùYxã!Ž-Œ|–¶á\lÇy8?Ží'à‰xžŒ§à©x.ÃÓq9®À3ðL\‰«ð,<ÏÁÕ¸ÏÅóð|¼/Ä‹ðb¼/ÅËðr¼×â:\pc´7ã•x^×àµx^7àxÞŒ·à­xÞŽwàxÞ÷à½xÞ[ð~ª}Š¦ð|ÃÇñ |ŸÂ§ñ|ŸÃçñ|_—ñ|_Ã×ñ |ß·ñ|ßÃ÷ñü?Âùiðü?ÃMø0~Ž_àßøþ†¿âOø-~‡â/ø=þŒ¿ãø%þ€_ãWø#~mˆ¦£ÑñѦè„èÄè¤èäèöÑ)Ñ©ÑiÑéÑë«B_ÐjÍnˆùò+M¶gq<š_ö.ŒG§/ÉUGvJ¥–o{AUš)¾èú*ÎÛS2®œlŠuWyšñ¨C1_¾‰oàiº4…µK¶Ÿ'c‚'Í£“åi5o¯/ùfæýÄnUtdY噬¼ÎT,G&¦yêåK]z»¦ÃWû2G§Y“餲™):¦¯J…)]·ÿÐÖ¹í·çüW€\?®‚ endstream endobj 1114 0 obj <> endobj 1125 0 obj <>stream +H‰lV TTÕþÎ9 êŒ3 +ˆŒŽç̃>€3 "ã™&((Œ†Š€Š‰¨á›LK©Ô¼i7½uí¥YæëÞîHÞ4Í·•™]ÍQkµn÷‘·òöPœûº\®ÎZÿì½ÿ³çû¿ýýÿÞû@ÐMYX<$aò7göÓsŠ6±²¶¢~WÒq€à cfÔϬ5õÛ³˜K_ñÌ9‹fl;X°üPÂfUWT/ɘ¸ÎñÿîYtî6˜Ç‘³jç7V†mâxñ†Î©«¬xqÍæz ®#j+ë%E²ƒ—p¾<·¢¶úMÿ¡Õ3f·øúº†ùß_üqr ”úyÕõ>`0âœo€ ì6 @çÏö;m~eHÐC0w=c +Ʋ1Þ%Ÿ ’%>¿ëµHÓt +Ñ!å %°3™ŒfG0LèåØ¿0L% B£°\Ø(/JÉ(õb¤i§t@‘#d»ì£å¡rªœ-ïRJ´Ct:z:z9"vÇ G¾cš£:êÄÏþ¢ß¯ñÒñelÃnâg ã…iÄ_Fü#ây R ÔMruá÷–Ãå~²¬ã¿ßz~•Ž/øýþ›þ_üßv.Ôÿ†‡:ÛeBˆ×ÙŽÝ6áîy„é=u¯W³ï·¼¬Z#Õï†î¬aÌè ¬è…ÞÌU(ÂÐáè‹ØÐ +Ú1€**pÀ‰HD!.Ä` bYCqˆÇ` ÁP cµ$" *ÜHF +†##†‘HG3ž…Q¸ÙÈA.òÜу0…(Â8ŒG1J0QŠ2x0 “ñ ÊY/S1 ˜ŽJT¡^ìÀ¬Å7Ø‚ïÐŒX—ð^Ç:´á <‡ëøžÁV<…£¸‚áeìÆ?ñ_|‹í؃pockùYÌÂ'¨aµŒÏpŸâ ®a6Îá,+ù<„c.à<¾ÀüŒÿàiÌE-êð0ê1¯¢ 0 ñ¡‹±?`)kzVàQžûðÃJ¬ÂjÜÀx?qOý‚›hÅ׸„v\ÆW¸ˆ«hÁ{8öâ8Ç<‰ð=3ˆ;å HzîîI1]ÆànÝ{˜Ì=-Ö^½CBÃú„÷°õëo +gdT´+f`ì ¸øÁC†KHLRÝÉ)ÃSG¤LÏÈÌu_vNn^~Áý£Ç<0¶°hÜøâ’ KË<“&?X>eê´Šé•UÕ˜1sVÍì‡æÔέ«x^Ãü i\´xÉÒeËW4=ºò±U«bÍÚæ'ŸzzÝú Ïlüݳ›6?÷ûç·lýà /þqÛK/¿òêkÛw¼¾ó7w½µûOÒž·ßùó_Þý«ooË{ûþöþþ<ôáá#G?qòÔGrúÓ3ŸýüïçÎqáËÖ‹m—Ú/_¹úÕ×<­âFû\T¶GÖy|‚µ/ý÷²†¥©Sâ}âd9§&Û+Lã@Œ£#VaOŠ“s½RTîø2§Gn–› ªšå\yVE•×¥·|QÝì"{Q\VÃß’2Å›é±ÝéV{<©Ä1h8§ÙC„Ù]³utpR@ÜhÙ+E•+ó6eÛ¼™Ù›¢È9ÞEeÞÙ6Åãá¬À;LÙ.« ïâDαì;QŠ‰AOss×HŒV¼š›mÍ\‰îq*>]®T›#Eåø„Ì"ýU¦S±i§âTÈÓMìà¸ÑÅe9d¢xây8ó(ŠZAñ à†p¥,‘(ŪDY«Ðr«IhºÕ(l0 +׌·ºŽó!þ‚Iü’GFy ïŽîño–,9 £EÓÜ4÷ÜÊn47ˆEmÀfÚvÚ»´#4SyVwš{Á±œ,‚ÈdÑ'q° &¹“f!4Ä.žÊÊ™–Ÿ5¶0l`êá†+Ë—´Ï›ymá¨Æyuü?ùGà¬Î©BãdºÍ)€[×ÄMo"4 j÷M_Ú@ÚpZÍC«¡-¢­¥=OÛIÛK;N3•û`m%¯à.^A‰éä5Xt9í$œ©SŠÌÌ™vxæwdÔP—ÒpuùâËÔª˜Šäa!ÁÖ¢ï[—dáV×”Ô° –š¨×ñ¡Â±þë¢(月“>$1¨JK²ø~š¦‘hÕµµµæ-—Æj j j j j j jÛZƒZónæºT¢F·ú`nÕ•2S)óm¥ÌTÊL¥ÌTÊL¥ÌTÊL¥ÌTÊL¥ÌTÊL¥ÌTÊL¥Ì„çºzóªÐÖÕÛÒ‹!HïÇs½“Ò ‰ vChˆYt:¨^ˆãt•48fÃ*!ت$DF&(Ö`!À–æv§Ù&,ö¤Âä”±Iv‹¡³Ÿ\¨õ…òˆŒŒ´ø~ýâÓ22"b*KÓÒJ+]®’¢<÷€î¼¢­Ÿï–ew~Q •.¥ÜUTµœ-ë´ ³*èYm!S£î "WÅê4KÎÐD«P•œéÇ$Ù»gßâ§úû¯ ·ˆ•.K»5µ¿Y¸\[„­³îŒTÓx[M#Õ4RM#Õ4RM#Õ4RM#Õ4RM#Õ4RM#Õ4RM~¹pw ºÍÚkx o^£ÞóÁA²qô[ôQéÇ3ƒfRí̢ʸêí¸*㪌«2®Ê¸*㪌«2®Ê¸*㪌«2®Ê¸ª^ï6¢e¶2›&Ö˜¶$—çây¡õ]짲˜µ~ªE+gªxZSR#,!U'¦½yšyu3íbgÚ£]Î@»È´‹jR´KíÜG̾”˜”N˜6màp:.„Ûää˜ð¾±) +ƈ%½ÌàåÊÈŠMQR «ãÜÿg½ÜbÛÈÊ8~Îاqšøî\ÆãØc{â8OêxœØ“{“&Í6›„¦¹µ„mÓÒB«"uêji‘€jíË"-ˆ‹„»À®Äò@jÄR+„"^*V•VÀñP ±P\þßœqš®xDΉOìÌ9ç;ç÷ýÿßIHkT)mÍDzC½!Yöò×ç=A­”ê+iáõÙÊùšÚk­”'?Ú!…ϘCóF4‘o&òÍD¾™È7ùf"ßL䛉|3‘o&òÍl曉|3q D †p¶' òøÿÄIï9±íEgÌ*Ƭ6ǬbÌ*Ƭ:ÙÚ&Úu´;hwÑÞ@{ íÚ}4b ï%sŒõ9üinoÍ€¶fÛT@)Oýú~D}€.3¿û°è9* Ž¹Eh@,.Ľ‰ÄS"Ä‚óÍHÊèž™.õéÈ–užÌNm”†ÎOdÇ'O”»J«Õüb5=ÏW’j¹?ÕË}ÓkÒñÞ~¥£)éP +}æJ(zqÂZ±z”Êòðôv$ò¼™ž*%»Œ™ÆW’'3áhf(¡žÌF ŽOèÚóšôŒøQð’/ +ó£Æ__XæÆÒõÆ_¸ÎÏ6~Ê¿w`=|ˆ19+ßØ㟦ùi˜ã*ÅØO~ãÏTMÇϯîÐ3ø7<{ò˜ßC7ÊR»[Cµèü¤£~I«®"Ô‚"›Ç¸Äh£ù½¡)íDwÛÎ]{e(ú®ÔuðAm.šNªƒW¯§*sÍÈuÌ'³d¿Ä +ešƒ¶Â랤·¹Á%þ»Æ½“^¢ç±}ž¸CÚúÑI„ZEè8>'éqãL3àkwæhTëaï¸Û;T +ã…wÍÏ—·¤÷WÞ—¶^ùÃï][=xÇiÞ”6šì_ŠZ¡}w÷X´x´ÖÀßh°/ öe°/7Ù—Á¾ öe°/ƒ}ìË`_û2Ø—Á¾ öe°/ƒ}ÙÑhÕOœ´ OîâIÌ´V—ˆÂQ\S81ËIƒúÊââJãO;ïݸuë‘·ÌLNžâü<ÊðOíîÞjÆCØÿêŒ"I¹ØgÍ!”J»=–€Sôàó„㺈NGtz3:ÑéˆNGt:¢ÓŽètD§#:ÑéˆNGt:¢Ó)³Å‰u8 f@ÚÍ€t€&õ°ôÓ“B¶ÇU® ?Nu‘Óy`aw3¨=Ú¤ JƒiöË:³YÆ Ìô¸:à8‚ G°á6Á†#ØpŽ`Ãl8‚ G°á6Á†#ØMG°á¶Sy1ºíœO‹ ÂœÜྨ ©?„þPQhÏ)òø +ŒQ D]9 ¿‘fû¢Oåñ^D¥—-CråàaI7Λîëð4‡ÂÓ¼Àw¼R[§?âKÌj…%;#­yæ­H>UÌ -51¤öÇ5뢦§ôC9{ œí &Œêè°4-+*o;1’l å§ÍÑ|K(­tu÷ùZBÚÈ ^Ñ:³Y[åµxk(íËFŽùüÄõ vþN® …ôÛ{ÌÀ‘u¹\w¹\w9/lßÐ I|Ãg1ÂýnAz7Hïn’Þí¼òh£h h›h×Ñî ÝE{í-´{h÷é6zQŒ¬ad­èªŸêʆêÔQýîÊá¼\R=ÂÍhŸ[|Z?ÝM§dšY“:òF1\Û¬%ÔÚ¦mmE%^ djƒƒãz(˜³ ù1=L¨ÏÆ“¡Ö…K•Ê¥ÅA½Àý±ÜüH:eÎeæ*é>kœƒSþopª±;u¦¸µŠ"ÈT@¦2©€Ld* S™ +ÈT@¦2©€L¥I¦2Q«Ä]ÿŽ;þ–r…ÊcºVí±È£ÿU·ð#u ÿΆœ«»0¦&Ƕk—ow¬·žžè¯f‚ì¸aMòcf0ZX¼R­¾0—»ö1{º¯|*£/Œ¤-æfbxƒ‡Ÿ ‹º÷ÒlÒ¸ô>ݼœšâføš!n†¸âfˆ›!n†¸âfˆ›!n†¸âf͸âÆ”8óFmÛ'éý.¢Îí ?P¤Ò:Í¢NÖÆœleÂT:!¢Ê©³„[o$¨ây¦f6$Þ¬=»q¥èç»pþšÃ‹9ÊKãC¼'=h|O†[ -gs…9wç<`£ {±]gºb1‘í ;ÜÁˆ{Edxo'é(2Š"sEU6"²ÌÈ¡efè:ÿÔ¸ÇÑw÷à+)K¨L”’Áâ?h<”b¹rª¯¬ÇÏóÏZãý!ο E+Û³åÍ©Œ”ßßx‘Óí ®[ï”N*űtñÚÆhÿü¥Zmw¾3·C¸óÒoÁäóGk +é®þø#ñiVg;ýSðC!3W%©/ï»UÖÊÀ— „Ñ’@Åtymw7ô÷øÛ“ís ë¼Þ˜ãõÅùt—Ç{Æëš8»(-òö¹rþ‹:3\{7ž©Î \h#˜tÄ1!‰8ìЧÑ}‡[Ü8+ÜàÖ·¸5À­n pk€[ÜàÖhrk€[Ãq’æ6œqSEº%Š™ÇÉ1z12-¥×±°’kauÈ¡¨ó¢^ŒjýQ&]^uÍ9Mýˆ–ÑW¼±Ìã«C3FºÕóÕ“›1)˜«D ½—¯Iýc‹™—ÿË~ÕÇ6u]ñ{ï{ŽãÇIìØ`;NâÄqpžƒIL @B- «@ 4„@"‘…t+êÚ©Ó˜ªmðצFꦢ­ë¶jí¶¶jUM[KU•US„¶i­¶Š±ÚÁ†bïwî»Ṅª©ê¤ý1_wï{÷ÞsïùÝs~wöj¼³ÎåmXÓÜÔõxêø¥---í÷Œ¬Lïßom«kh¯uT…«ãkê=|=šÙmظ*N÷ÕÕd¢”Ï{ñ¸‚(Pö[|¦H—ŠyókT~¨Y<±Ìb“”ÿIŠÖ¥*ZÓ-¬˜IZ±ÊGšÊQoo¶²13;³ÞøÆöj³µÞ’‚ÔG»ζÜÓ|g¼?µÜðEÜ ‡öì'%«·³ä¾§ŒÅ%Z±‘³’¥XÀ†t´–(à“Ù¬80>¾wᦰÑM÷Z¾KYÉ:7.E¼±° V-æå˜guË“†çŽ±}•ò&ÐÂëc>$ùÕ˜'[µ¼B·Ûªª3hÊ…½bÓÂëM!ÎõôÇj„½p»*SÕ-Ý}–Õ;7Š÷4ÝQÙ®uDíÞþì½/™kæ +íÞ϶äþÎú{þŒ5‡ZÝG݃ú³K÷ à@óá¬ÅÙË·Æ€òùõÛO÷ƒ½P¾ZPÂåy©X?0/? Xoö”pAy^*8*óÄ©Ë™Gž(š¥ {Þ”¤Cƒ™eë2Šýó‹|įÎ=t×Þ.XzøBÚp~H]½¨Y”ŽŸ=šîͺ#é†Ú¿#;ЊùK³ÁÖžX{¶Æ³­mr¶…›¼ÏÝ_á Ń¹çx_÷@E(€öpS¦Þ»2ÞÖ´këÒÉñ"jößz+>9šŒBVó«µ±»›»žinÑY9–¶ì»í ìÛQÈç5°§yòMÚM„H“ŒÛO4ý]4À4 xÒ4À °AlÐ4À °AlÐ4À º÷Pf .fƨÊïQÚ—Ûò{LÐt‰G‚‰®H¤+,Ô{½ûÖ®ÛÛ÷î]·v_o˜‹ä`ª¦&5˜Lš55æ`2s`°¹yð@&3:ŽJvÜvìýeÇϲֶd ÷`€º•ÁèKÉ["•Q ¥’7ŸÛýeqzÿ«m;ÿÁÂMô÷â|¿‹þn–.êïHZ³¬h²¼ ¹n[ŽßŽ"z¿µkVÙ;.Ú÷íÆÅæÚÛÉs.7¦|¾€9Ëàܤu%¥¹õ¤uPír‡Òà?­½£Z¯XôÍן:5ùú=vb’{r½x‘W_}ñE|[šOIOù‘ÖÖR!×P±hy…²Ü¦vÅN<0ÈœN/ûÝžÓâk¾u†kʃN× +wm®¯ÒàÌ$¨•zi?É·°åú ¼Ý£¯’ãý¸“ÀRRó[r‡®±-ü»lR$X„æ}@|šEð~ï’\Ë_ç²€ØÅÚÐö)þ–’õpþ/ø>¹€ÔB–C¢FH +ÒDãÊï!c%#ë[­…Ñÿdþ_â9¶M¼ÃZÄ«¨G!ë ×ñÿ¶ 6ìvæshK°mÚav/ڇŠÌqmTSÿ“¬ýÞmÁL{Š9©F»@íÇ8d3j†ùSØÆ.çßAV/¶³ ª®§š7¡ï:è pÌËàò—ó×`C?éZ–õR»|OýЇ³µ/Ä»˜ƒÞAw‰0¯(aè¥ü·˜˜µ¡ÿ0jÚË~¹nZ÷¨\¿µ&²?hÙñŸÆLÑ: ¿¼»hÛí’Pv/IœŸb¨ÉIòlþÁ{ø& å²Üƒ”»9µ§`ó ìõ¬F~‹6) ì+­ý* áOëÀšÿÉJx0öYä[ŒEµCÒÂÚFž£ÿ5Ônq•ó·áŸ·Ùz`qšpº¸WØ=ŵ‡±çðÞØí“âg;ÆÚ`C5í;ùž=“¿c—c®$Ÿ8¥ÐúýlHbýõX;ÍA¾(Ô£ý8†Ü€,HA4?k_ô—…òÃ/ ?‡ü +m}¨ó¨m¨‰z+}C{Açö mLëc&„°Ô‡5l&l^p&J©]qAt‰ù¾  +kŒåË!YÆr ÷!‚\GÛ<_‡ÿXïóMiègTÐXö‘eŒ½°Xγóü!>ÇçÄc…¢}åŽrN;§ÇõÓúkúU*6»­e¿í;TJ<(ßPå•»û‰ÒÊÒÇÇ—¿6¾h\*ÓÊzËfÊžpÖ;”—•?íêp¹î=îgÝox4OÒ3îyâÿå“/”[¸“}Y-‚see ƒ.#b +ï(óT±*‰ +\H¸ÊR.Sº`v‘Vº†ìЭtßTºÙÄŒÒKX¹øŒÒíìâq¥¼]Wz ésJwB?kéxôKJçÌ©ÿMé‚9ô¥kÌc+QºÎ|6¿Òm°ž,ä:xŠ^okÙ025=51:r$<:=ytúØÄìÄôÔ†é£Çg&φ›F›Ãf¦ÓLÐ3%Ÿíò™ L„’Áß­SӳǎA9032s<¼yòÀ@xz&<1{,¬ëQTÈ!­yÑ +¨FTÖ• ×L»^.‡aX˜L·u-Lsð³·²tme-§í»¦ò˜:Þ#N ¬öžd¶ûŒÔš@IÞÍB¸;„mn&^wŠ}™!Ú…¡óªpâ¹µ=cC”FјEc~ F¬¯<óD\ª*5¯:U.íjá^±«9“Íî‹•jˆKª\츠!vÌÌœ·Z/j-‹‚dËj…-!:2Ð’©BÚ¥µýmˆAW§Ýˆ„Ž¦cO]Š”Á•¼«¬v™NC‚3pêxšWN÷™Z+Éѵ¦êto9¡kcB.šAImWµœÎtX_;"+.žsTz ٺ0ÐÎm]ébÀMë˜"¼¾ŠYծʈ•w~ÈDù8 ²tΤ£š + 3²#ˆØt¬ìu‡Áí`pvn׌« õüÚ¸eº·yP„ KUÊú ¦S®gÊŒå¦ö0«™ ¥,Téa¢ +4$Óy‘æJ†’c’”>šDè’v‹8mŠ·y®óš©eX¼T øZŠV¡`«`ÓM· ¬ô®Ùõ¾·gûöÁ`ФŽG“ÏŽík,‡,^3¢SqPI_jÇGug4ØoŠ‚è—ç—ó‘ȧÈÔÄSûaÐÃtá!pRlå}sØÍkcy¼cb¼sb<Ç‘D‰¬–ÌÕ«Giµä ž'ôØŽŒ$-ž&ùr~ç‡92”®z¸h­Íœ?I™Š¶ÿ×Þ6îTÔɹ–R^PphJš”î«WWüsqÖïýh¹É—ã4¹0fQ7 ‘qwÀ©h5œÕH +®¥ÔxTmQrEÒ›<"2¦£9±†ÕN¼ +à5`ZŠÞ¹ ÿÊÈ䡉(ã4‰-xçbÜÇZÁΰÆ2«µÌÕ$Á§°«ªýkÔ1pÔÊ£…±´ˆ{ť՚)5Í2?#ž}̳€¬=´õê ¢ïFöüc +Ç#®Àº‹z6júšÉÕî~ÌŒÿ££Ì[» +w/WÛ?NWÆ;£ŒûGÜ.Њ‹ˆ‹èÑ:o-í›È‡¯3~¥6æWÕÆüÍjcÔ>â?W±þoáfèÚn…’’“uÉ­Ù"®c;¸SØ@ï6²ÕÛ„“±§àT6ž§át¶lgâ,Ü·Ãíù¡rÜwÂyú]°g㮸îŽ{ƒ­¸'Î¥¿Ûˆc;=Ÿ%†8»p>.ˆOž=Ø‹{áÞ¸î‹ éÓEdó?î’ÝÃôÿÜÏûóí2<¤ ¬²Ëñ <ÁCñ0<À#ñ(<‰õ›ÆX¶#'ǽœl–1F9îÕ>óã8Ù~'à‰xÛí'ã)x*®ÀÓðt<Ïijðl<ÏÅóð|¼/Ä‹p%^Œ—à¥x^ŽWà•x^×à*¼W'ëq-®Ãõx^7àxÞŒ·à­xÞŽwàxÞ͹÷à½xÞàƒ¸‡ñ|4™ÂÇñ |ŸÂ¸ ŸÆgðY|ŸÇøá÷E| _ÆWðU| _Ç7ðM| ßÆwð]|ßÇðCü?æçÀOðSü ×àcø9~ã_øþŠ?á·øþ‰¿à÷ø3þŽà—ø~_áøM²!™N6&'%›’““ÍÉ)É©Ém’Ó’Ó“3’3“³ÖW¥¾xffvCÊ—Ÿ5ùE ãÑáÅñhÿüxthaú²BudÇ*µtâÅ•5S|ÑõUZ´¦dZù0Ø”j›VE;g«C1_¾Yo`mMD-«â–“2ãeÊ&Ès²1Ól;Ô¢µÞòÍÌûænUv¤­Š\VAg*•#ÓìtùR—Á®éðÕ¾ÄÑÎä:«\nÊŽé+Ëö—Òuî˜ÛupîyŠc endstream endobj 1112 0 obj <> endobj 1124 0 obj <>stream +H‰tV xLWÿ{ç•„d&ïâÎ\y1DÞ¤‰&B=òÒt¦IHT"g£h™¶LmK±VµJjÕŽÚGQ»H´ª–ˆ>¾ý¾îƒÝÒIfÿ÷Î ©rçûßsÎÿœó;ÿ×ùÝÐuà‘“[8èâÒ\%)(©,®ªÎ~W°X@QPRS-°VåR@¹–æCgVͪl-:°Æ»iMþ¬9‹gÚ;à6 º xþX^V\Ú>mL »IëËI¡jáϾa4T^Y]{úBŸ4Oø#æÌ+)¶,)¸®¸ÐÊâÚ*^`·€à´^˜[\Y¶¥€Ï¦ñQÂ/ªš·°z{`+M‰„ÁÏ®ZPVu|HÐN |)«À˜;DžŸÑ,ôA‹{t4*H¢˜…^ÏÄñ“³!@sŸã9$ñÇp­×4G"ÅÍ_~ƒÏ‚EiHƒ¡ð ˆÀ +샃Ű1,Ÿ³Z¶‚mäNr×x¯áûð‘üþ#¾Eðú ¡‚Q† )B†°Ï`4„9£Êècô5ö3†‡Ç‹Œeam?;îs£‘ñ¼‡ý„ŸÎòXá/'ü\;^Å{ò.|?!Xè/2~ò#øº^ø¥2>s8]Ž»ŽïŽ:ö:ö8fP›EìÔuwußï¾Ñ}®ë^×õÎœºÎu[I–u\Ÿ{½¥ckÇîŽ @džŽÚŽE%qñW~P_RwÆ‹3óg&g—r kcð‹‡açØWxÌÃN#oø@ |áG¹ +@ ‚Œôƒý)‚¡HQ4Àƒ†pD QŒ!0a(†!ÃCÕ‡x$ Id¤`$Fa4Rñe<cð42‰±ÈÂ8ŒÇ3˜€‰˜„ÉÈFr‘‡|LÁ³(Às0Âçñ¦¢êe:ŠPŒ(A)Ê`ǬÅ:ü[qVlÄìÄǨÇztà5lÆüˆ·° oà$¾Æ¿ð>öãŸø/¾ÇnÀç8O0“jùm”ã TPµŸÁyœÅ—8‡[˜K¸€‹8ˆño¼ƒ+hÇeÌÁÏøÞÄ\TbæÓ½Y€]XˆE¨F ^ÂbÔb –â,£š^Ž—±’øâ>Ä*¼‚ÕXƒû¸#ø‰îÔ]tw|‡ëèÄ |K·æt£‡Òî` MhÆ)4 ­x'ð:¶ã”Kãå,>’lRi<<½úôõöÑê|ýüƒ‚Cúéû(Œâ °ðˆÈ¨ÁCLC‡E‰‹OHL‘œ2rÔèÔ§ÒÒÇ<‘96kÜøg&Lœ49;'7/ʳÏ™-Ï¿0µpÚô¢â%¥e˜9«¼bö‹s*çΫš¿`aõ¢š—j/YºlùŠ—ëV¾²jõšW_[»Îúúo®ß`{kãoÞ~gÓæw·lÝöÛí¿ÛñÞÎ÷?Øõáî=õíýý¾÷ÿ?ðÉÁ?þéÏihlj>tøÈÑ–cŸÿëßNœ<ÕÚvú³ÏÏ|qöËsç/\üêRûå+¿z­ãz篿ùö;ªÏi”ŸØ(þVòÙË‘ ×>\¼Ó„xä˜0¶ÞÒÀk1 ‘jŸ>mh˜I2+2쬈œ‰ƒ ÔãMÂX;66Ï,Z«`_jÆ +åÅ¥vE˜ÜÒD™Õ-Ø‘o® ÷³ÁžfÑ?è–Y,)„£p2ŽÕB³]³eè¦EJÓÁ·ç˜sÍöº ½=-â7„L{KŽÙÞ’¡7X,´JõÀRj—W»lV“ͪÁÔÑ8Qò ƒ ,V«kÄ…ì-V«ÞJžÈÑÐÀàR§Ò>,³¥åÈSi¢A/)Dƒh ;,„íašoÎ$K –¡DâôQcŸrR¹©‰ÈËhŠs¦™<‰Ö7‘«S-Ãc :C˜Î cM=u¬®§–Ù4ì–¦Gúpò·ÀD â‰p&õ[!íV oªåWp.…‡¤ðÍp¯BSÚ&‚TB¡óMÃx‘×31BT‹~lj&—¹ãð&Ãæì/Kï9Êw'nÞLGÓåwØ]®Èk_3ñ™ŠŒ‘ðm3MJ_8iÄ´ 8K"9Iý@êF§{ЈQ òˆà( +‹£®ºRIæªz9¤•ZÉþFbR®‹PÔÔ "‰ I"‘PTÚF"UyZC`’H’$ŠBò0.~'½¹ÿP..6•Ký½I1ŒKˆO%E +±y™,“r‹â||âŠr³,1¾6ï`ƒ_ä¥ñõ1aìr„eRlÒŒu¹ykgÄ›&—§¤dEôõK*ÍX‘> ‹Ü—r|šÂ䊵'yÁähx’ƒÊ³n!ùƒ^r’‚s&ˆ®m“6yÐ&N+å–ï’‚ØH€|9B%’`HˆKâرžùlKÏO·n9¸¶îÄš–šz—ÇÈ +d7S¨Ü9QK€O¶‚I +æ´BEVðò&•Öy¶Z>ÛO§“Nˆµ¹Îd‰5õ5pzOö}e3½Ü§zi%8•Ti.8/ÙŠµš'Û£–êÇø«J—‚í¶ØS벓~"ýbmõ6[½lêèžã\[ÏM¦“ìM¦ÓZåÛ”ôhå*£+5p™)_Ö‘L'&Ûl„ñ0æJ$7Ë)t¢ñÚ'ÞÓ‡HÒ Rtã dp,Ûà°ÕpS\1åúÈ1]ÕL›Ýõ¤ ˜jhä!4Zg±xQL½¢ŸO¤Ð<¶ì~yóHÑD~ß54ÑÙÎEU”‚ê'‡µ}÷jξk5'ÇõvÙ$q±çAìàGw³©‘tÿ£H"É>²Ï?Z2JÕ%3ýó& 'I”ÂEw8Š–…^•*]^"ý· +!‰"I&Qº½%SÅ^΄HŠ§3¢V* b‰h•ÄJb%øÂt雌N +¡5>n2ñ!2ñ!ƒ}ˆL|d¶ð“9!”ÈÂÉnÒp‘ÈAu@„ D¨Ý­Mâ 7wH-+÷ö«.6Ž« +ß™ý±ã¿8öz;þ¹î:Žì®×^¯â´upÒ84â©´…2Þ{'ÝYffínxDÚ'¿ $oHÏ©*µåµ}H*H‘›J©@ˆ†4„–HÅËwîÜ]'iBxà¼>sÏœûsÎ=ç;çÎÕçz{çô…ãÉÞÞ'*Ë?\<õòòŠ“gÂ-Ä·œ¥MøHÜQCb¨í*mG:$„M†ààŒRJVÏÆZÉb¿í˜4T«¿Š/U¿úf(TJë ê_G$ P€«Ûüõ¡ÉdæÀ•“é¥ÙHòé)嘞œšPæòÞî‰Å©á“‡öú»P£ØEbù®&°Ú™!D‰·H+Iüܯk¥ZFaÛ‰·£~ï.ð»F/"ju,Èù5+v…âí¾úÔ/)h$Aã]‚f4û®èÇP¿^ô·’Âë +©Öqß)ÂÑ~¥OéLg†ö íK*ôŸyBÉL Åð¹l|ãdbH *áÆs‘pPUT5<>ùøAýÙ³Gôé ‘ÉÔþ—;öí 7öõÍôŽFMÖ«ÍÏ-|ó¹­w.eŽQžGq¦~‚¬É°·(J~žÿ +7‡0ÂMFvÃÈÝ%„‘‰Q$€…°(½ÃŸ_D^("cÉmp!!7S‘˜ª9“'øC>8\­¤Qø&ŠŠ"…¢o!t§ª™“pSÈœ2'%2g*Ö‰¦Ç' B£JR‘§kGÚO0¿}r½?ß0Ï–¾×Ô?1b'{êp}Ü?øÊÀì©ÔêZtô©ÔàX_sf go´þƒÞì™Õï L&öµ¥Ú­}-SéHúøhGîL|>=ÔÚÔÙÛ8ÐÚ3ÒEmÂã}`²ŽõÏŸêqöPüÈíåկɘ@`½B§n‡8R”÷·¦ÏmœSß[ûç/ý="b÷baÞ»fp-BgóÃuJD~áЇB¤ê䜓#prÄ/O±]Õ‚„b\ýŽ¹°14·/Î mÀ“Ý]íu(Ë- ÏŽµ¶M|ãˆrk«eOb ½} ±G Ÿ©A᡹GùäAgÆELÜ>ÚÓí±@ìg?yõ«?: ÔƒnŠ{–Âêßüöo>|aç¡¿+]?ÑWìùù;jßl +=½u¾ î¼q;üo\q{»c=Á£è}>¸¿v_«ÞÐZ”ÛTuðwSÒ}¼2̾ª\f Ê5ð×ØŽ@Û£žbMJAß8ˆ)ßguê3ls&‡ÅE{BŒoÈ6j‘<µQÙ¶Ðx"Z£Jʇl0ÀY¯§ou6®v±˜ú›|öŒCv€äŠ +Ùe\·º¤ü:Ë`õÇÔ:ÙÒü:¡¯ W³ðõ¸¦u±°ZÏXs·2˾&lž…ì6 ¿0¶YùúzÀ'±v7Úѳ0øÉawšÝ¬ü|šxõSŒ¥ñ×ä<šsí¬SÎ#>¤¾Åê”K ðÊkBÛdch»¡ÿ¨Ü7£}Óžk{‚ý¦ÙvIû>}ú¸fÛýt[Ú}»f@Ù`h@!¬ß 9á@A;B±´‰`~ج\W÷¿ŸU¶„ïŠ}Œ +H¹V¹%Ö½ÁúÑטB<îÀmðùAÖjB"ÅÔß±=ÂÄŸ°'׆*×ÐîTuØñì! ÂiÍWÐ |ÆÔX/År>8"(È戶n Äžý¢òôu³O°Àˆöµ;Å<Ìî¡CÄB¶‚jåèSðªÄ[,d¼ª$b±Y¹ +ú=èÐá»)󩲉v7!_ˆœ»L¹XùLý-öR¦Ùnìa–°Mx.„\ì«Çi1Žækø~Ãß3a± +>1+§Ûºƒ +*PWi=ª ‚TÙ·ô8Æž¿·v°ÿòOÇﻥd”"~ï(¥ŸÚŸh…`Kðõà_B ¡ïþÿ÷¿ý£sFiA<ëRüӨ剞6 WžFjë¼ÂZÔ}’WÁg$»¥:/ù N…³’!‹,ù02ôœäëØuõuÉ7(éÀO%ßÈúƒ—$ßþcŸÇcw¨Iòøæ J^e;B)ÉXkhFòAÖ:.ù¬' •à7úÖ¼fÙ–™Õòœác3ÓcqzŽ‹gZ¡y0‡~zÝtÝžÓ\{ùŒóÖ Ä•’%lF\¯ŒÜð÷¸ŽÁšëÚY? cþ:Û«ŽVÌ•iÛ´àØL&åÊÜžL!7{ä[dÈ + L Ž²4©ÈJ‹"8Yã ˜¶´‚ayqz1 +6ŒÃ™®Ç³yC£,®,O,AŽ0-¬[¯I¾½mèu=h´‘ïFö-XÅEý w‘8 Ô\a–ç&sžW<8:º¾¾ž¬DÆ#‰r3úˆnþRÕ#f–?eh^ ¦’¼ÊŸ0W}fÞ.`}íý먢(P\7î*¤lžÙ¬ÈÊÌa&[e9æ1ΆYßWœ±6g¼ÆßŧïâgÀiÌÂåKfdï ¬jAƒ E|_ú’ehÓ@e¼c¼/€³!áèõ˜+V[ÁÏdy™!Ö¾½f“G_ò¤d‡ ©‹oPÎËÞíý¹âú ´kbå$ÎÓ¶„~²QcÉMÌ&; ¬JtiI}YŒ'{þÅzy·UDQø[â"Ûq ‰SpŠ!” ¦8„d}‚0 Ø ½Éj~Ò3’žJ(BïBo ½…„ІÞ{`è½å/œ]=9òÀ ЧÝ}wïÞsöÞ·g¡Ç¨ÞD…Æs#yY'åcÐaµ#Éc¿žÖS v~Jc…píE!²¤óŸpÑZb.‚|¸zAó[eéˇåÌZY^Ë<Wéw( ƒ§Õ1–v8<Åjߦܳ•=mL}]¾ _Ö19¯l—KûT›E—wû^²²~Ž +c³™Õ;ÄU9“„ø¶÷ /+Ïy(fÜœ´ËŒá–1YúCüy.¸<³‘%Â\øvÐE¿Ä±W(c,ÁR—e=ïìrβ2ÕÛbÜ.3þF‹™÷ßU¸ÝPµÍ¦½¬=Ãù‰k~‘ÛyÉ»ˆ3ÃîaÞæäßw|ÂŒ_WÝÃj£ûµQTœüµRõÿ/?iSi;)º¦ÂTš*S-UY)YM„¡«“:¬§Fš%­:š1Ryc§ÛÓÝ[˜È$&3E«oÈFLec6aS6ÓØœ6áÝRql-äÓà ¶e&³t׳_žíÙىم]…iŽØì “¹bw¾ðïÁžúnìŲ7û_ªl1û²ûsrs‡r‡»ú¹½L:NÒ¡ÝóÄfÖíÑ‘.Ç ÚÕåÇR±}4Çp,Ë8Žã9YÎIœÌ)¬àTNãtÎàLÎâlÎá\Îã|.àB.âb.áR.ãr®àJVrW›×r×s7r7s‹©áVnãvSËÜÉ]ÜÍ=ÜË}ÜÏ<ÈC¬âaV³†GLòóOš‘<ÅÓ<ó<Çó¼À‹¼Ä˼«¼Æë¼Á›¼Åۼû¼Çû¦žøtƒø˜Oø”kXËg|ΟüÁ¯üÂ|÷üÎÏ|ÇOºSþÆ|ÏW|É|mL£i2£Ìúf´cšÍX3ÎŒ7̦ÅL4“ÌäÊ­²çUÙtg{ûôš˜ŽÁœïÍé)µæ/*µvë.µæõÔ.Ì$RÑT.‘è«ìHx…h“n½’Ô}:Vóý’ÌAÎoбŸöã’:NUI{çülDçã@"–éDcAÁ5tJÇm£>–ÎÅ‚LÒ“TÊôÖí­|»)#ã~!³Õ©‹§å6/-œé­Îieý7. ²©h.ÈxÑÀÚDbÑ¢‹Ziji¨õ매úÔjÎû^:ä=?›ò9 mVÎíÚföL÷œUb^°¸«k.AU¶ÜÚÚÖª‚¦©šU9õªT-C=£J˜ª~±·Þ0Ëê­³¬(›·[™Õm« µsYz|Çß …n,› endstream endobj 1109 0 obj [/Separation/grey/DeviceRGB<>] endobj 1110 0 obj [/Separation/New#20Color#20Swatch#202/DeviceRGB<>] endobj 1117 0 obj [/Separation/New#20Color#20Swatch#201/DeviceRGB<>] endobj 1100 0 obj <> endobj 1101 0 obj <> endobj 1102 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 13.0 %%AI8_CreatorVersion: 13.0.0 %%For: (Jeroen Wijering) () %%Title: (mediaplayer.pdf) %%CreationDate: 1/11/08 4:36 PM %%BoundingBox: 52 -539 797 -44 %%HiResBoundingBox: 52.1289 -538.252 796.124 -44.127 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 9.0 %AI12_BuildNumber: 406 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%DocumentCustomColors: (New Color Swatch 1) %%+ (New Color Swatch 2) %%+ (grey) %%RGBCustomColor: 0.79999 0 0 (New Color Swatch 1) %%+ 0 0 0 (New Color Swatch 2) %%+ 0.4 0.4 0.4 (grey) %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_TemplateBox: 421.5 -298.5 421.5 -298.5 %AI3_TileBox: 17.9448 -577.1377 800.9453 -18.1377 %AI3_DocumentPreview: None %AI5_ArtSize: 841.8898 595.2756 %AI5_RulerUnits: 2 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI9_OpenToView: -125.3335 -6.6665 1.5 1402 965 26 1 0 50 75 0 0 1 1 1 0 1 %AI5_OpenViewLayers: 7 %%PageOrigin:-141 -492.2754 %AI7_GridSettings: 10 1 10 1 1 0 0.8 0.8 0.8 0.9 0.9 0.9 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 1103 0 obj <>stream +%%BoundingBox: 52 -539 797 -44 %%HiResBoundingBox: 52.1289 -538.252 796.124 -44.127 %AI7_Thumbnail: 128 88 8 %%BeginData: 10184 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C4552A8A8527D5252527D527DA8FD26FFA8525252FD057D527D52527D %7D5252527D527DFD0452FD35FF277D275252522727275227A8FD26FF7D27 %277DFD0452277D5252F87D527D275227527D52275227FD36FFA8FD31FFA8 %FFFFFFA8FFA8FFFFFFA8FD07FFA8FDFCFFFDF5FFFD047D527D7DA8527D52 %A8527D7D7DA8FD38FFA8FFA8A87D7DFD04A87DFD04A8FD07FFCAFFA8A87D %527DA87DA8A8A87DA8A8A8FD0EFFA8FFA8A87DA87DA87DA8A87D7DA87DA8 %7DA87DA8A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFFFFF %CAFFA87D7D5252FD047D527D7DFD08FFCACAFF7D522752527D7DA87D7D52 %7D52FD0DFFA8FFA8FD11FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8 %FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8 %FFA8FFA8FFA8FFFFCAFFFD04A87DFD07A8FD0AFFFD04A87DFD06A8FD0FFF %A8FFA8FFA8A8A8FFA8FFA8A87DA8FFFFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFFFFFCAFFA8275227FD057D527D7D %FD08FFCACAFF52522752277D7D7D52A8FD10FFA8FFFFA8F82727275252F8 %27F8522752A8FFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFF %FFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8 %FFFFFFA8FD06FFA8FD2DFFA8FFA8FFA87D527D52FD057D277D7DFFA8FFA8 %FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8 %FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFCAFFA8 %7D5227A8FD067DA8FD08FFCBCAFF527D527D7D7D527D7DA87D7D52A87DFD %0BFFA8FFA8FD0DFFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FD04FFA8A87DFD05A87DA87DA8FD08FFCAFFA8FD047DA8 %7DA8A8A87DA87DA87DA8A8FD09FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FF %FFFFCAFFA827A8FD047DA8A87DFD0AFFCACAFF527D7DA87D7D7DA87DA8FD %5DFFA8FFA87D7DA87DA87DA87DA8A8FD09FFCBFFA87D7DA87DA87DA87DA8 %A8FD0FFFA8FD15FFA8A87D7D7DA87D7DA8FD19FFA8FFA8FFFFFFA8FFFFFF %A8FFFFFFA8FFA8FFA8FFA8FFCAFFA8527D527DA8A87D7D7DA87DFD09FFCA %FF7D7D527D7D7D52A8527DA8A87D7DA8FD21FFA8A87DA8A8A87DFD1CFFA8 %A8FF7DA87DA8A8FFA8FFA8FD07FFA8A8FFA87D52527DA87DA87DA87DA8A8 %FD07FFCAFFAF7D7DA87DA87D7D527D52A87DA87DA8FD0AFFA8FFFFFFA827 %2752F827F827525227A8FFCAA8CAA8CAA8FFCACACAFFCAFFA8CAA8CAA8CA %A8CAA8FFCACAA8FFCACAA8FFCACAA8FFCAC3A1FFFFFF5252275227272752 %2727F827A8FD07FFCAFFA87DA87DA87D7D7DA8FD0CFFCAFFFD067DA8A87D %A87DA87DFD11FF527D527D527D7D52527DFFFFCAFFCAFFCAFFA8FFCACAA8 %CACAFFCAFFCAFFCAFFCACACBFD0DFFCAFD05FFA8FFFFA8A8FFFFFFA8A8FD %08FFA8A2FFFD097DFD0BFFA8FFAFA87D7D525252A87DA87DA87D7D7DFD0B %FFA8FD15FFA87D527D527D527DA8FD07FFCAFD0DFFA8FFFFFFA8FFFFFFA8 %FFFFFFA8FD05FFA8FFA8FFA8FFA8FD18FFCAFF7DA87D7D52FD077DA8A8A8 %7D7DA87DA8FD1CFFA8FD05FFA8FD1CFFA87DA87DA87DA87DA87DA8A8A87D %FD1CFFA8FFFF7D527D7D7D527D7D7D527D52A87DA87DA87D7D7DFD05FFA8 %FD25FFCAFD0DFFA8FD05FFFD057DA87DA87DA87DA87D7DA8FD05FFCAFFA8 %7DA87D7D527DA8A87D7D7DA8FD08FFCAFF7DA87D7D7DFD07A87DA87DA8FD %4EFFA8FFFFFFA8FFFFFFA8A1FF7D52527D527D52A8FD047DA8FD07FFA8CA %A87D7D52527D52A827A8FD077DFD09FFA8FD25FFA1CAFD0CFFA8CAA8FFA8 %FF7DA8FFA87DA8FFFFA8FFA8FFA8FFFFFFA8FFFFA8FFFFA87DA87DA87DFD %05A87D7D7DFD09FF7DA87DFFA8A87DFD04A8FD34FFCACAFD0DFFA1C3FFFF %FF7DFD0527522752277DFD08FFA8A1FFA85252FD047DA8FD067DA8FD05FF %CACAAF5252FD077D52A8FD0EFFA8FD3BFFFD06A87D7DA8FD0BFFA87D7D7D %A87DA8A8A87DA87DA8A8FD67FFA8FD05FFA8FFA8A1FF7D2727527D5252A8 %527D527D527DA8FD21FFA8FD37FFA8FF52527D7D527D527D527D527D5252 %52A8FFFFA8FD6EFFA8FFA8FFA8FFFD05A8FFA8FD06FFA1FF7D7D52522752 %52527D7D7DFD04527DFD20FFA8FD4FFFA87DA87DA8A8A87DFFA8FFFD05A8 %FD3DFFA8FFA8FFA8FFA8FFA8FFFFFFA8FFFFFFA8FFA8FFA8FD4EFFA8FD1C %FFA8A8FF7DFFA8FFA8FFFD07A8FFA8FFA8A8A8FD52FF7D7DFD07A8FD0BFF %CAFFFFFFA8FF7DF8272752272727F827272752FD05FFA8FFCACACAFD04FF %A87DA87DFD06A8FD0AFFA1FF7D5252A8FD077DFD25FFA8FFFFFFA852F87D %2752277D52A8FD08FFA8FFCAFFA8FFFFFFA8A87D7D52A87D7D527D7D7D52 %A8A8FFA8FFA8FFFFCAA1FFA8FFFFFF272727525252272727A8FD0DFF527D %FD04A87DA8A8A8FD2AFFA8FFFFA8A8FD13FFA8FD11FFA8FFA8FD08FFA852 %A8A87D7DFFA8FD0CFFA1FF7D7DFD0552FD057DA8FD23FFA8FFFFFFA87D7D %A87D7D7DFD0BFFCAFD05FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8 %FFA8FD05FFCAFFFFFFA8A8A8FFA8A8A8FD0BFFA8FD04FF7DA87DA87D7DA8 %A87DA87DFD29FF7D7D7DA87D7D7DFD15FFA8FD18FFA87DA8A8FF7DA87DFD %0CFFA8FF7D7D52FD057DA8A87DA8FD24FFA8FFFFFFA8A87DA87DFFA8FD0B %FFCAFD08FFA827522727277D5252A8FD0DFFCAFFFFFF7D7D7DA87D7D7DA8 %A8FD0BFFCAFFA87D7D7DA87DA87DA87D7DA8FD29FF7DA8A8A87DFF7D7D7D %A8A8FD0FFFA87DA8FF7DA87DA87DFD12FFA87DA8FD047DA8FD3EFFA8FFFF %FFA87D7DA87DA87DA87D7DA8FD07FFA8FD08FFA87DA87DA8A8FF7DA8FD0A %FFA8FFFFFFA8FFFFFFA8FFA8A8A8FFA8FFA8FD42FF7D7D7DA87D7DA8FD13 %FFA87D52522752522752FD12FFA87DA8FD057DA8A8FD0AFFA8FFA852527D %7D527DA87DA87DA8FD24FFA8FFFFFFA8FFA8FFA8FFA8FFFFFFA8FFA8FFA8 %FFFFFFA8FD0FFFA8FD0FFFA8FFFFFF7D7D7DFFA8FD0FFFCAFFA8FD047D52 %FD057DA8FD29FFA8A87DA87D7D52A87DA87D7D527DFD0CFF7D527D7D7D52 %277D5252A8FD10FFA87DA8FD11FFCAFD05FFA8FD2BFFA8FFFFFFA8FD047D %A8FD047DA8FFFFFFA8FFFFFFCAFD05FFA8FFFFA8527D27FD04527D52A8FD %0CFFCAFD15FFA8FFCAFFA8FD0452275252A87D7D7DFD29FF7DA8A8A87D7D %A8A87DFD40FFCAFFFD05A87D7DA8A8FF7DA8A8FD23FFA8FD15FFCAFD07FF %A8FD11FFA8FD05FFCAFD17FFCAFFA8FD05527D525227527D52527DA8FD35 %FFCACAFD09FFA87DFD06A87DA8A87DA87DA8FD1FFFCAFFA8A87DA87DA8A8 %FFA8A8A8FFA8A8A8FD21FFA8FD14FFCA9AFD09FF7D7D527D527D7D7D52FD %067DFD07FFA8FD17FFCAFFA82752527D527D52527DA87D7DA8FD70FFCAFF %FD08A87DA8A8FFA8FD23FFA8FD05FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8 %FFA8FFA8FFA8FD19FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD3E %FFA8FFA8FFFFFFA8FD05FFA8FFA8FFA8FFA8FFA8FFA8FD17FFA8FFA8FFA8 %FFA8FFA8FFFFFFA8FFA8FFA8FFA8FFA8FD0EFFA8FD29FFA8FFFFFFA8FFA8 %FF7DFFA8A8A8FF7DA8A8FFA8FFA8FFA8FFA8FFA8FD05FFCAFFFFFFCAFFFF %FFCAFFFFFFCAFFFFFFCAFFFFA8A8FFFD05A8FFA8A8A8FFA8FFA8A8A8FFA8 %FFA8FFFFFFA8FFCAFFA85252527D7D52527D7D7DA8FD29FFA8FF52F82727 %27522727F827277DFD04FFA8FFFFFFA8FFCAFFCAFFCAFFCAFFCAFFCAFFCA %FFCAFFCAFFCAC3A1FFA8FFA827F8275252FD0827F827A8FFA8FD06FFCAFF %A87DA87D7DA8A8A87DA8A8FD25FFA8FD05FFA8A87D7D7DA87D7D7DA87D7D %A8FFA8FFA8FFA8FFA8FD15FFCAFFFFFFA8FF7D7D7DA87D7D7DA87D7D7DA8 %7D7D7DFFA8FFA8FD05FFCAFFA8277DFD06527DA87D7D527D7D7DFD24FFA8 %FD0DFFA8FFA8FFFFFFA8FD06FF7D27522727FD0452277DFD08FFA8FFA8FD %0FFFA8FFA8FD06FFA8FFA87D7DA87DA87D5252A87DFD05A87DFD1FFFA8FD %04FFA8A8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FFA8FD05FF7D7D52 %7D7DA87DA87DA87DFD08FFFD08A8FFFD0DA8FD05FFCAFFA8527D527D7D7D %52527DA87D7D7DA87DA8FD3FFFA87DA8A8FD17FFA8FD12FFA8FFA87D7DA8 %7D7D7DA87DA8A8A87DFD04A8FD1FFFA8FD06FFA8277D52522727277DFD10 %FFA8A8A8FFA8FD11FF27FD04527D525227522752A8FD05FFA8FD05FFCAFF %A852A852527D7D52FD047DA87D7D7DFD27FFA8A87DA87DA87D7DA8FD0FFF %A8A852A87D7DFD0EFFA8FFFFFF7D7D7DA87DA8A8A87DA87DFD0CFFA8FFA8 %7D7D7D527D7D7D527D7DA87D7D7DA8A8FD1FFFA8FFFFFFA8FFFFA87DA87D %FFA8A87D7DA8FD0FFFFD047D52A8FD0EFFA8FD13FFA8FFFFFFA8FD3AFFA8 %7D272752522752522752FD0FFFFD05A8FD0DFFA8FD13FFA8FD38FFA8FD07 %FFA8FD17FF7DA8A87DA8A87DA8A8FD0DFFFD097DA87DA87D7D7DFD0EFFA8 %FFA8FFA8FD2FFFA87D527D527D7DFD0452FD0DFFA87D7DFD06A8FD0EFFA8 %7DA87DA87DA87DA8A87D7DA87DA8FD09FFA1FF592727277D7D527D527D52 %7DFD24FFA8FD06FFA87D527DA87DA87DA8527DFD08FFA8FD05FFA87D7DFD %2EFFA8A8A8FFA8FFA8FFA8FD2DFFA87D7D7D527D7DA87D52527DA8FD0CFF %A87DA87D7DA8A87DA8FD25FFA1FF7D52525227277D7D52527DFD25FFA8FD %07FFFD057DA8527D7D7D52A8FD0CFF7DA8A8A87DFD04A8FD27FFFD06A8FF %FD04A8FD2CFFA8A8FFFFA8FFA8A8FD11FFA8A8A87DA8A8FD28FFA1FF7D52 %525227525227527D52FD25FFA8FFFFFFA8FFFF7D5252FD0427522752FD0F %FFFD067DA8FD25FFA8FD04FFA8FFA8FFA8A87DFFA8FD2DFFA8FFFFA8A8A8 %FFFFA8FD10FF7D7DA8FD2BFFA1FF7D7D275252527D7D7DFD27FFA8FD06FF %A852A8A87D527D527D527DA8FD0DFFA8FFFFFFA8FD2BFFA87D7DA8A87DA8 %A8FD2FFFA852527D527D277D7D5227A8FD0DFFA87D7DA87D7D7D52A8FD25 %FFA1FF7DFD04527D7D7D52527D7D52A8FD22FFA8FD0BFFA8FD0DFFA8FD05 %FF7D7D7DFFA8FFA8FFA8FD28FFA8FFA8A8A8FFA8FFA8FFA8A8A8FD29FF7D %52277D27522752A8FD10FFA852A8FD2BFFA1FF7DFD0452A8527D7DFD27FF %A8FD07FFA8A8A8FFA8A8A8FD11FFFD06A87DA8A8FD28FF7DA87DFD05A8FD %2FFFFD05A87DA8527D7DA8A8FD0CFFA87DA8FD057DA8A8FD24FFA1FF7D52 %27522752527D7D52A8FD25FFA8FFFFFFA8FFFFA827FD0552277D522727A8 %FD38FFA8FD04FFA8A87DFD04A8FFA8FD2DFF7D7DA8A87DA87D7DA8FD3EFF %CAFFA8A8A87D7DFFA8FFA8FD27FFA8FD06FFA87D272752277D272752FD3D %FFCAFF7D527D27527D7D527DFD75FFA8FFA8A87DA8A8A87DA8A8FD27FFA8 %FD06FF7DF8FD055227525252275252272752277DA8FD33FFCAFFA8527D52 %527D7D52FD2FFFA8A87DA87DA87DA87DA87DA87DA87DA87D7D7DFD38FFA8 %FFFFFFA8FD29FFA8FD9AFFA8FD64FFA8FD06FFA8527D7DA8FD097DFFFFFF %A8FD33FFA8FD3AFFA8A87D7D7DA87DA87DA87DA87DA8FD6BFFA8FD80FFA8 %FD80FFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFF %FFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8 %FFFFFFA8FFFFFFA8FFFFFFA8FFFFFFA8FFFFFF %%EndData endstream endobj 1104 0 obj <>stream +H‰ÜWïrÛ8¿Ð;ð>ô&™«MÉ–lËýä¤i7½&öÄÉn÷2#Á6·”èRR÷é$eYùk·×\Ò‹G‘ü ’¯þ>7±¼€F»éçÕ«},—ªO • +Qd¹Ò¤“]â! +AƒÃÞ¤þ*ã2í–a¾ÓÒ;@IHÉü/P<í’]äò\rˆ9[¶Õ\ÄÓÝÕĨé-ËáQÏ£nøýv‡ŒŽ¿'‹4FM{òºO‚iítÃ.iø>²ã'ÝÂ4½V/ÔÀ^³…Ý°ƒ_ ໋BoeT$æ#%#Ȳ})¤ÊúdÉRrÄfÈaäOB^‘=Á¢/5‘w2ÍúˆKÈyÄŽ¡€Æ¾LcH3ˆ÷¤ˆüÏ›ü»”F¦œLV«±s WVŒ¯X͉·kìºËh•Œ™‚¥þæßpz¾×ìõ  ÙêË?)¨³”ëœjiRhïHÆ Ð„JË;ÁÌš™Ÿ·þo§LÍ Çr”¢ÈMo@S- “ã£.ú¬ÔN† HOåïÆÒ†× +šívCÐiv:€èpx¾Û"!Z3Kà’nPÎéJi˜V¥­fÐ>Â$*>ãi¿áùÖ~ØÒû6 ß+¯sÐ3*ÝR«ÛìÕžpõX³1yiéæþþQ-—ÝæÑ'?Hã}™èÅÈtÃTH1K„œY^õm8(^,œs§Ò¯…Ì!C]Ðq:Sìö2:ˆ9(ädt ›*Ý° +Áè‹ŠèqŽt¸‚9ô¬’`ÂŒ6Ê"®0O¦®)[c¬<3Ê£•r0’+ +5Q¨D¹UÏ-†×0¼Â¤F½C¥ÅJ‹•5¬¬°ÒšRXha¡ÅêТÂÆl6Ec4€Fošå „ö"ƒHç ½(„€œ.˜ÒXÌ)J$,/FI™*Em1äÛÝlžS¬Ú¦¾PkC5]3•y S:8 ÃL°lîT¤î– O‹5hõ^BJ“â6Ù¹ƒ[½¥Š§€ªx +ú;aYT=XÒËÙ¿i'cy•R¸ŽKÌ'f¶x•ÔË™§w͘aëHÜž§ùzd#; _hϳ‹€ìb Êd+_TêIc4‰Bb^&‘1ÄFéj`ušÑšn‰1¿ä:Aª U1ÿ³úš*fWô PÒXj*¥²ÛŒŒ:‡N9:\¦ÎL8Œu‚˜µ^×ÙË 2Ð šÏe‘aŠ8tPKуÚ÷ÀfÆAeÜ Í¡¥ÖÓõ°ZÐЂ†5}+¿‡qfgu5g–ë•Ê2NkÒv!)ïØþ ŒE¦ lIÛŠvè¼HgL‰`EŽuˆ;Ä1”sNL3 >LN3ÜGÖÛG{bÚÔAI}Äê“É#ÇŽG$çô1Qú˜Z»ãœþÛ„=te9Uœ.ðcÖß:"ݶð¦MOfEãžÐݱ{õ³ãsç,xÏéíºC¤wí^}ŸUæ’ñƒf‡oMod‚t ¿)ç^Ç+7ÃËœ¥Ù!±HýÈ/ÁÒšŒ/vÕwŠ}\Ç}xñnÑ(\Ö_ã‚ç°YÑ8ÒG1EöT‘ÍÉ©”¢²ï&«2³$ªÆ¿Œ9FF ¦6Òwg*·gÂóø‹›Ñπ̗¬}ŸáqÇœytß÷ð«™,ï;&Ã{„‚µ¼®Þ[¤¥©lž%ël¬QFºóDÆK<Û&›µ½…©TI-l†z^‚‹š‘ÏF䦩ÖË„3Æ +1õ¼£L²@ ã9[€17Ÿ¿3Èñi,XŠ;‹¡W*uûÁüÅ~v³%YZ¥4Ð}¿ÞúÇ{âw „–ƒy7Η¸=8ô_)ÈÍ€ôчT¢mô˜%@^;tÌìØ+®K†ŽÛìú!^{Ýf§ÛóÛžùpƒž¹‰º^ØñÊvþ‰9.©K|À¿tE|rDÎ?»$F꧃û„‡Y 7Á]dŠï{l#­¶çÿwŽá +ׯd|ÅòhNZ»fþtýT–¿¬å –aÇíjC¯ÝmúC[®Ñ_=;xY>µµ½—žÂá}2{š©;í_h!;›*ýnªí—aë9RY!òÏ[[N陟)ƒ“ï(›ÁÖV{Xmv*ÜÚÖûÔÆÍnÄàùÐØ>ºøŽfzŽ—AžåÊ\>—ÿæܦ;£•z­Ð÷Z&,nÓE½í.~tCü{ 5{ÕRÞdzZBß¿g•ÿ`âÙ‘V1Ú«¢¿ +ò÷œ1>bë‰wgq àG3žkYöh©Õü­$£¢[Ä”—¶¾•Q‘@š¿e9s^MèjŒõ G<Ò9ÀÔÒŽ?}<–1ÜË|Cv®‘"»×Å/ŠðZúZCJ±ÿŠŸ ¿†Šæ\Ä +R‹izˆ‘©¸ú_¾ÔeÍÝùGšM.™ÊÞ`‡£ötvzÉD±Âjzö.Åîda¥%ÙÑ/Ü*`‹À}x›È¬¯Ÿ×¯ ¼¡¡Þ¾arŒ!?6Nlö¯ŽþIËÿ_FÂ{4[ùÏÙ…€mãª>s¡GE–Ëä©JýWêbýË­ÓÐíSùéò°Ÿ1}ðÓ{–ضéøäu¶¼ Sþª4›^½àÝø™Ë <úÕ{q#h÷š­A'ÀÄþ.·YÝås7ß …׬^¸É™ë­Rõ¹éúífÞ&o®xœÏ·ñ¨>¯W~è㛜šŸÍ·éæòyÝÂUÚ“ò‡.dŽç0͇Šã½q×îʼ€Íßt½±,T{²øñUÓÜ6ŽDÿ +ªöâKd’¥xr’ídV»v¢µ©ÚJMMA$$q\´£üúmðC)~™Ã\å_7ݯ_7Xü·O˜QwG¢p ËÐ_ãæ/Æñ˜GÙ‘0«Æ1À¹ñ·G'Ù=Éi¦;çwpó•%XVÞ–+ßG÷¥!‚-'KÑ' +<$Œ´D…ê7×",ovÚžKEÄ»W).ÐSÌ¢ÒÄ«:©eÂ3EF"ßU«çzR ~¨Z£Ñ$E×ëìw$È®=b‘êKŠW‚ø+)¤EÉËÈ}Te%N¬‚­0é–ÞC¯ÒÀåê=¹GOyV’X'ߨàûãÑ®Œv‚giz ç_2•`Ø}p®Ô R3™bàet¿IŒdò£¬Ï< §¡ÅBm81$‘B-}¸gf Ý RQ? fÁû~l`¸…º¸$¶ öŒô…XWtxb¢q¢ð&¡‰:«V5ÀEã<`¶ËðŽ 5O«Òµ§Öt^U2ÅqÜbÂËC+&™rÕBaš” ]TEŽÓdRÎãò¸¥8KÃr…–™âgÆ\¶æ Jq +Í “cFqÍ"¿7ƒÝ×kN¬gBÿ‰déG˜j‘ù÷®évÆÂyD¬îM¤ùùE?Çô©å}½ª"ëà߈fìÅõô—ç‹ö™6n—]þ*‰žËk¨ÞçìØ xÒ‹IΪbjHþý‹ódo~¾Í§¿ù½+‚ǼD˜Ï´/wNÊ]& óóÈ;Ìîy¦ß·I\™.3ê"¦¾;½ðÔüøA‹¸_—ì@ן¹j|öMþ1^ 0JX®á\&ªš…Ýã̘cšÌ.³Ísìwå¨x2G…9ærhÑ8׿óíÖœo á/п +'Z0‹ËIÐ熠FzÄ-Ea”kOÛh<>ú¸~¶ÍAîOÂ%öK9ˆŸÍAl檀Y'«€»f«°O—)]-¡N1#´CÀ.)zõížlqFUÇît]¯ë× ‰”äŽÉÎ0qRÑI\xÌ™^uòÕ°{mVâëÏÆ&ð™2ë ÒØÚy”I´³…ÿ¾*’ LS1IŽ = +Jb‡T¼žáb1Œõ[ñ½çc‹××Y/¡‘ƾñízf ¶oÁ.rú'ß@Ne²c¸±òuóÒnÊ××(o:PÍ3ËM¢Ž8vY`ÄårzŽ¨˜DœH+½• „ª‘g1ßP•[M°èD‹xÂ…–€‘TiàôjÏÅ’ =¨|r˜¾:ʨO…)N'{KÜkléîx˜stPnP´P]O(šJTeáM‚°“÷ +M­û-‰Õ~$ôZ$a[>Ó/*x‡ª¸›Þ7XÈäæ@H‰¡Ÿ …°ŸEÂkÈ„ÚŠy/üˆÅA6£¶Ÿ£¶ÀQ[ [òvQ“-S“˜¦bËÙPë¦r¢7/Ð9\b9ýÛªÁjÌé ä÷t"Úò’ï]È®Ô!ƒ¿_ kœÌ7–~"²Ù½Ã$‡mËõJ àq’–ïÈèx:X¹Ú{ýeÂaêrÑa)y©7Ýâ ]- +ïgAjOj^Á*j£Í Ý‹äUG;¨V`_/#·!O}—4zø2‘ÂTm¯#Jª'_#¶Î¦w§±Ñ vãã0åèð½^”Œ0%•¯QÜÀ(*@)NýL)0“C)ŒJ`€ìF”^ªÈd­®cCTêØr!`’tbd¶©n5í.J^«õ¿³‹$,EúA0¨~Œì` +¾Ý[K)¬klC}-¸¸ +¹»ðZn÷8&‚È}J uªµiѵ ,n²é„MkÀÓh`]Èràê9 Î߶ æà \ˆuÆ"+AÈј1^Ö]÷¢ÆT4½fé•|úõ­>ÞÍý›ùü]0ñG’z ~Ð}€~ +Â_ù ¹¾ÖzÝFË +ˆôÓ¶™ŸvÏ£ìÛ6z&*KÑf» ¶?´æiVïT·œÓÒàãú}É”^%ÑËÅÄ­!ÕÆ_’ïZŸ…„NB¿ ëÏ\=‘ˆ‹&Hž˜õÐbyNØJ·<Ì*cõ»¤.¹7™uu‰i€©¡Ÿø%zæè=pÄû~Ö9ºp¶Ò]šÈz™Þs+Èÿ2ÂŒ2Œ ƒ÷Ö€^š`Ò©:¥eà ´æL¹ÍÆàgÇca”›a›àœÖv$ Û$N^Ø$ÕÕgò†îôË =¿aíQ0Ú"áØ–uyšáÃíоMþÃ#ǨšT£oèHßÐ…¾a‹¾s ´5}Ckú†.ô ]è;kÑ÷’þ(!gö„œµ4ÕmGßY[ßÛá­²{.Mæ¹°Ûk÷Øtµ\ù>Z±zH¤B÷IT{ºåœæ€-WèK¦hÂVó‘çàºQ/ä{éèZûL8Ãâ„~Aן¹z"‘Ž!h²?ùf’ÂÀ‰k˜<$é†bvèO—† Y’ĸ|2â”bEšZë +¢ñÍŠmy3M¹“óýᔼÚÒêöW _Yñ˜t¥A{]ßú²šf +‚©"³M(ù|®ÄT†wœrq—3DBkNãØkÖsžiî9D': + ¶J¶Éȃ©‡¶P8ÄKFÉïºì|}äZIÄ+YF‚o°zÀ'¸üøË{®XD3Ë\™aF4IQÄ)%ß‘ ;ðPºÇ­–Šˆw¯$R\  >GUyÃN-\®nÐ#‘{ô”›&?°Î/z"’ÓLÿ[îŒ^¯uQšû«o$N²£ñá÷ÑÊçeøT\¾çQ5a;—ŒÊìËv àO‚—B½qq¸åJñãÏX¾ðôgÌž’Ý^ýŒáÙªþwfeöÈö`Eî€-M¶',®Æä·µñ=ck¢­Ž ‘Ë %F±-®=´ËÝCô^û:î!Ë_>—ýO†éy¼AO{œ-ûïëæªiërÓR5»ž;Öé¾fn÷³÷l_t°P¯±ÚjkŽzý!%±“ç'CïS-?7;œlï‰T Ë5ÍnR¶SòYacQeãe2‡° Hí ŠÉgT!ð»“èmO’øU‡ŒZQ3°Ži½×sa©^Æ|CxG1²#LËÉÿ¹¯ÎíTÕ4|Þƒnņ•ˆJ”nMŒ%jìŠ {Á5kæêç+(è11gοÙ?ö"~o}Þnÿ<"‘àÿkaÿÞžì;0)ì oO F@2TÅ͆ÚÆ®]) +ز ëf–} E¨»¦Câ¡}5oº„€€gÂñ¤Î¡y¹ I›mL1XÛtÈ°n;5ÕkGûi³Ü€¬ ý:`êAÛ™þ& ï­úeÙü©NŒRxsÜ0mÄ7ùó>gþØa«§œÛ^È=–.¬@ Ššewý~Ü Zë0|L}€I-Cï1±9è"!êîYw&¯´õÛð°lL®äÿ°»¨'ÈR»\W?2€N WòÇ”¨M +ÃÃ/6Hб ¿ <û÷ó~ièÇc?ßjÔóO£N¯¨¤ªöŸÉãÁs3a?–ïòŸ C8^[öp–ÞÓòc©‚´1յɿg`/[m~Z6Cmeƒz P¢#Íœë^´IX[?u+xNÔ_M•«[ÄÒ„eÜ©ÅánˆÔk盜š€/ËâEqC€ÖK¢öj–G»‡° M¾*ær/ÏÒzcé’»M²WOrØ äŸÜ¥ À2ëyb¶qäGÀëµaèè¢?³2íLpŸ™Ô[,(vqrò§êyç<©ªÃ%üÒHyZœcÍÑHxÈ4× ¡õòcaE¤=ÁÙ9ñŪ{í÷qÉüüÉFÐÉz—ã7ªgM¾¾%Ã̺óª1Lø8}âj.J.Ñ‹DUÊë6Æ3aO‘qBÃCßèÛ«7˜+æc=ø*œ˜c 9>‚¸äÚ´3ňÛÉÖÇí5rv‘ØÔ†¥h¿†¤M7èòâ”–>:h¤f}\ŒD»Î7)ôDb#"£?¹ú&S +k|± ¹€ÉÅ€<À¨/*R·!S)9@IéÙ«!§ñÊ&Ç‹÷ŠäÅ` ¯$<Z‘¿Ð5 ˆz_+hC¿=;© L:F·è…Êפ)MÃ…½<z\=ú,Ç?ˆæ'_…iZÕöܾ›˜Ù!öUëc0[ô¸%÷£d]ˆ5ù’"-}Q[ÐáätMÌqêS†©Œ·¦¾—ê~]àKEA‘|ãÆæ 4Î}}}*“éúpJÑ/C.YÓÿðµ¼~ú«k7ÈZp8¢ypœEUAå”DÝFÈ­11£lZŠ(î ¥;` d‡?@ +Ûxï6V×ÈZ{ÎœôüxB(_¬8勾Ÿ¢ŒaÉ]r x§…ak-Êçê±Øk8èS%>Q¼îOœÓ•¥LCŠ™© ¾¶°ÃTå1ÌPÕÚ \súÄÙ\>2Iõ³Â3-ªqƒìª~%ûOFFñ{!‰/'ÑFðµ·1 :L*. ůå=k¥….óÅLAÑÈT„ÊÄÞþš9ú‡âûX%é¹E‘©Ì h#L¿€WêT‘¥gF ƒ¥:j8¡h¶@J¥ð`çúx-¬6õ-_›7]@DÁ°“ÛwJq™6_™Î=€íÝ .¯ê‹¾Ÿ£né‘€{Ÿ!2Ý8«VœÍ_jnÏ[¦ã•BWt£%l£ezi¶…—9S@í_Þ¥ñªÝæΦnölÐK7CK\jùS\V—{·VÅ[æ·¯ '¯”íôîû¡œ}çkT%nøãnáK,%¿êýUé¯B:_=¹4yT“½ÀÉFW*äÕËkøœÉzQ)¢-OÖ.È7âdºAö˜ÊÔ£R¾œƒ£ä'A¦¤uD Ž ¾¢¯§¤ì:Eõ×ÎòÛÀÿ$ a¥û1#%;h3žâ"¥áÄcpŸ`9X›U(¥–¬P²Ÿ•‰A,ËP‹i$²ÊAZ@S¸a1þÂUÛ´„­Á&AãhЂÝD¡QJ—¯¢ß°0äÖ¹o 2M¾Q:šµYŒ ”+ÓnØH¢jpŽªœ‹·@‰ÔKmg Xx.›`¤ñ`î¶`M?zéÔgì‡W/–¡s%î {;²zr¾ÀEÔ ¦X>eŒyï7…r'â’r9z{¦XÖ‰gýãûsƺv t΀[ìú ÃÑ/Í‚¾4^ý>r¾Ž4§Ù_¯­ˆ“«{¼qq~ª´¤Ba/X´ÐŸð•T⪽Ãl°+n½ÖB¢Û!ñYõÙà@ÙÞ¾†ÿøÚà­ïd5P t€ +Zîƨ0šI¿œ5g8Ü”®æ°±…u^J–ÓM®7³\è¶ývÍ` nÃo•í›ØÖŸ[ÿä8ùÅi/‹xœüâ41¶¾Ëq„®ðþëbâS¦^nwøšØ[šÊË*tÈ1ø$–]ZtáŽ- 5ÖÄÉõÿuYX¶ÂR1Û‘ +í +|#¡ÌµýB™èêp&­ÕkÅÉz‹ ´OÑ…”[0C6pY@à øùM–©¯Š5¾ôYéª'§iÈwGÆ3EÓþKd„=k£íßF˜ûÿY¯Ò5Ó¹&z}fbŠ)„ ;31KÓh¡‰˜gîÿ­$Z‡vÎw~|Îã<Ù½«vÕªµVýNtVçĵgãÕ¤ ç9…_¡‹xpNH2˜KÖ¯­üaI¡|Ȩ½¡ƒð×óclÚ*Öãô»ƒ¼mª +6ƒb­“q þYHx4úîþ0ÏÀì3ëj‘†ž¼H¶²¢Ê¬ãyÇχ;Øñ¬¶ÝàF›Ê¶= ^!â?#÷[Ò‚:Ä\¿»ÂTÆ=ä6c!+äŽ2éi¥Ì4t#èmk·D¯=jÖÚ hߺ«Ä­Æ{/ž^_' ¼rˆlÄs}vzŠ|›Ê§ØÿïÈož;$útZ¼'³ÕnÑ Ã‹¹±±Ý±¸N²%ÛÝõ#1É+ƒÇBïgÇC*"srøþÁÒ=sããCôÉY–,J]uÇ4Òv N-A 㥠šš¨;ºÛ z³‹`{‰Ï&äÊĹëL6ž&[“Z ¤0W>ac c¿ãà ÖÉûü>\öoÓ'×T@ÀØÿœß§ãåÉ?L¯-czs¥ó^g—ç%³¯‡-¼ÌÙ§ÉõLfmƒ&öÒ:¼™ãq1Y}¾¶éåþžÞDLdåy$Ë8j0û^ã‚ä^L³Ú˜—k§mÉÐ7øüNN÷qŽ•\–…÷ŽB$“:!S¢ŒäRÙÙØ;»ÕIjD™M¤ƒKÛ;Óv¼y¬ˆµ÷Ì©ž$?wÐGFõßkqÇ°.áö@%È’¡9s~í/=ŒÉآ߾nz]™/€“»ÕCûQ»­“i*ÁÏxµ>7É„£øiÿDI?Ùúëf,/p ŸpÑTñÝË•¶Ë“ê|DPkÌQK +0/q˜fÖ1íd·*– ä£\iPÁ ³Ñ2;SíWÉg¯»-Œ\Þ§C½˜MÆâÊþ€ß47÷Ù7ÄܳLÎœîn/^M‚ˆ¶Î|#t»¼½:Œ•î¸/—{ä +´xî«)ÀÆǤ”Høá“Ï—"u»â®¯FÊÙêJ׳K_³ +–.„®'a¯žöp.|óä†ûÁçßu°tmš«?ÉÍÔÀš/øs¶6Ï-ùоfLÅP;% ÅíóçVß ó E®3hIua™"ñ!²5¶S|ÂáÊÒïbáä€m>¶ ÉôÑæoWá(o÷ðæÉÎwŽò‹D`>e‚È G‰â6•Ô ¾5Qu98½=Þ³K§;ÀNÖÑ®PÚ¿ÂÑtõýô@Võh Õ³¼L瘃Ýyß2£üô)L^ N¦Qz¨Õ8Gùþìâ}ª'¹:ùÐ}µ‡ö‹:{ËÝ.™ÿýüI’*–l()ä‰Ä+€kÄ+(M¶LÛ8âôæÙ¢Š[¶’K`Ñp.°ô¼%¹ó±¬@kôúÂVtG)} w±eLìrÒÙîlNg§»HŽr¿£ðñfö +PÔXüëK-›÷x %nªÛ`!-J /Ç~®woTˆ’?£:Ë4+/ÝÑ•«íîQ¾r‘–¦³µÕÚfÎá…p +;UÚÞ£m\(^îoAýÊïÛD3uü]êYÒ^©~å$õ|æB§nÏ;`±wd¶õBê7^šN¯ußpJ¹˜x.髦D9]9Á²‡ë[Û–Îñè2}r©Nߟ"/K‚jÎl‰r}¬^LüŒÏm1ÌA&k`Y,Áý±ZâñÔzÁ‰Ï ,I}BëóÓNÿ|¿6'øKZ×X.? Žøàf”j¬ +×àÏh~óà ÌȤN¹.¸"q…ê1&”NqMS…ƒË>‹ß6~iQ[ñs¨OÞ“æâä¡’hüC¬ì/ +MÕ秽SŠ£¥Ãñûpho:Â’Ø£7h7õmoî"˜/÷˶ê€ê ™Mšv›«Ô© o!êxv5ø¢• ëÔéþR°Iá@ÑÚ«Rg6›ÃØ0yÛW³Éö„Ÿà”Ïîào7Ö´ùš¡–¾pÁ¶èÂQk_¹p¡úQûvJëë=k»¾˜Q¼üÔI a“qœ>Ó’žÍÆQjU`ó¹súÖë¥O×ñã…^8€*061› Qþâ)r]>X÷¡€jøoç +UCÙ ‘…È«Èož{ì.Mm„À_ ÊàÖ¦t?ö›MÄ ?ÑGÐs ;BµK5{Cp>ïõ03—¼œ +Rà¨ñ¥M2¶þÌì"¿aV«Í•´æl_ $nT²èzµ5<Æ_¼%ǵžÿÉõpriLCàæFSncvëS¾^þh}@÷5mªdkêä*ŒÕøÖä¹ìR-kÆrÙGíÉA¸MŒÁãùc³9†Yuðàë’»~N÷\ÄE¾ä…Ñ&}ˆ²TCs¤pg,Uƒmèt3øì$]F ù…’>ßžŒuå(ʈç άÜ@²_;P¬”|­i¯þì̓R½ýW&]d%v1ÐœÆj椩fBåd™yè)£Ÿ¤ŒF¸™¼GûªtLêy_ÊÁp£{Åx³To‹M—DŸ +õÆfŽ)bot ~Y$ï û&¡;qÝ9Ë¢«ïƒ Ž1R„]D™Õzç6¢€»àÊmÚ†ËÑ觕1ï§B×Ï%Q8b9~Ú äm–îvDŠ +ÜñÀç«&H/ 2XèÜ–˜†7˜Guß饱‹á˜0ØgZW¶ +¿Šnc§»9—ÇarºCN.–bÉ]¦j¿v©²ÌfÆdÅËQ›ü|æ·¾Ÿ´æCídeB¥<7O°?\cUÉi|°Öôâ1i·–ÜŠõÁOÔåæ)ÂI®éU…N;·]Àسnü©ò÷æXâ`X ÿÙ ÷Õݯ¸(¿ò<Ò`g¦¨.K€1(뼈ùó2ÝNIÂðxAE@DQqAѸá¾á5FãÀs¾_síÓ  .0sfþ)/ôÓµtuU—Ž÷žW—\ϼïØç×›>¶÷{r 0ù¬ËÚÅŸS;i–ÆÉÕá»âb¶öÕ«hŸ!°‡óöÚ°ßG_­AMB@_ƒÄØ^Wíñ‹Ífý87Þ­æJN9µÅ +´¡Í^‡º’¿H˜V.9-ÎZZå!¹A ïÏïä‹ïWröþìOÚÍE+89yNhŽ+“âG69¶ù”Šb,žgCtÊ&§6ߥOIP +C^jòöìÒܳßéÆàœÿäíºBÑ\ùù¢¿8ÝG¶æ½ˆÒq6ÕO•²¶gn±Öá ‰áA, èÕ– ÛGErй6SV#fÕ±ÊN\K úhë0%-µÛ…úv)PªžqÁmi±ÌÔhÑ?NRk¨™ŽA…`tp²nvQPuxŽ­Žæ6§<ÕGõèi ‰Æ”½Sî'¿GÕ¹ŸùWÆÏRo¨PŽÐo•Óò­q6¾NGýM\ ì-TTX–ë·¦(½5µµ¾6»Óòt} {#Û†öuütX¡¾8¬¯+¤ÞÈúÉh­?NÚj½º>Î-'fÅãJ<}\ë£a͇‘È,¿þü:vÖÿ¿š‡4Ÿß?yK¾‘B»P.s1¬»Z›«â™Ø<Žg{K’"ñZÏn ÆüEGy%Éü Êß/KH1ÙŽ‘mEi/ûZé…9dþTO·Ø-„1r"H¤å!<³ç!¬>J@¡í×Býc +Õ¥0SM1}¾füŠ…fó %Ï+Yssܵ_\kùK¯Uûâ{jÿªâ>Lê1™# Æï j¥7dvÓÌÏkýBŽÔáøû¥r49eôŒÔòå >¬¨U`(Z< +XãºoËH +ósT„j…Mi i§ :èÎRA%ñª®i´¾…ð° íl "¦áŠ7a|=•„r3­þwÓ¬¸Ü÷_L«‹~LÌât<ëTŠšºl¡ãYGR»%D¸~;’'ÓR嶛_[þŸ€>‡›oÂ~I¶Í˜ŠAÕhŠLQËf|ÃÒzͲÊr“çÔÏÈ'æÒ Ôpje«”<[4-“^ +¡s½’{OèNæûaé4ŠöŠÚdDÓ†)-¤?€iñãÅ4®@[€WeÝò÷j!ÿë+ftSz@BeŠŒ¶Q;tÀcæÎtN2zôô´€,ŠïÈ\VS)ks—[ô áäJQî'µKß· ¼‡N4µ6Q̸<`oÐR|K–§aGèÔ_¯ºBÙ¯÷üýËdB5=kÎÐfdõÒÉ TGB5ÂÛ¢ dXô/¬n¶Á :§J¾Q×Z"rHy8ºw²[°ø†m"È“ƒ™”ñ½²  —ÅǨ5•®ÔM(öÓð8š®pZç³®»Tšò’+4öÝðnÐ…6 À=¨ô¨#Â26öª *ã•£§ñðä 彨ŽÄš ¶ØÇù2Àš«NPJJ3.PÖço÷´”34:5%©rËÑV¯¤§à=Ùk;BK¡KÝŠ¬?s  +rìÞVˆÔôsÓoBƒ/V¤`6žƒj??C«ñüðèÄ¥]Ý÷®Øâd/=Ø:ÊQÕq1æõÊ?z|ïSXGh-¨}ÙP³µyÀæö³‚àcT;¹Ö¡ïÄä}µNP—ö,›rµµ>‘+y7hê!ÎZõ]ÚŸËgA¯•ÿÞÖ^5rr…vKdéÛ úNõÎLÎ ê6‹õ¦ý¹àèà~vt…Îf‚ÑwN¢”Úè`Ô̱'[ëÏÝ@䃎PÕ+®ÐC œ ØÄJÔLùáÜÈaÚXÐ ŽÐâŽ`] œ õëTÕ‚zàÈÂk”MV»ôÇQŠ¿š†/…M/‡€¦µgè<Øòß ûDІ‚šlÞ5ªVYXÉ ò#4¬éŸŸ %^ËC5 M|¬ 9ý¥NN jÆ-†ž R¹BU#Yyˆ*ÖÅSrºbBÉ'((„ݯv*]l( XXí+‹]mÍ&•ðã­*wóp€· L¶[­>Xêë±å¸iB©÷ÖïÀPÊàT®ôs¡Ô4ayìâ0Ã;«yv«“Š£zY@Vþ’‰,ô¦Ïék)Áy¿hªemydã’?bªÏIcžJy{þ½Ê^t µxC•c.ªap‘Ì(P®Ñ/*W9èFê½\ñéÎê;¥VqÖM Ä…FÒMâ6îøO÷·aÆǘå¿r¸Ž;Kkoë{h›¸C½Ò”xº÷5¯ ¥¤<Ûp‚ÆøȤf.Ðáô êï±%(æ-•´á”y€r²ï‡½LþÍzµ®%ŽÑ'ÈCpI$:Š+¸@‚ŽˆƒÂxaàÇ•î¼ÿvwnÝMwÒþôºN¬Ë©ªS§§æÞWt%¹n qàû«æô]QbžÓôÅŸîwŽÓ“OØmsÏ)ȅȵÏw­ÿÖ¹NÑåÀq +Õ8¼Fì\ ý¡SS/r"=â9…×+á*’.`Í/Ók…ªiNZ¹z þlâûæû%þÝ]l,ò]ôî2B1 ûËåf0‘1¾€9Ûñ«Y¦ü­¦¦+³Õw«÷ÁOU¨ÿ®!b[wâÍ»@ùV³Ÿ²~ !R‘?ˆ÷}aUÐ:ø4µ@‰çªðSag¥¹úþûøjT²Ÿãš Ÿ˜ë …;pfDó\Ë¥*_uøIÞ2à‘^©dÈ葬ÿ@fôô6°“ß㟷•…󉮲Þ<‘eôÀf  ;®;P'ƒZº4Ê701äÍnü¯"ƒúmRÁ!­¨Üê;Aeÿ‹5ô DÓ%3¢Øv¼¦²@¿ÙXŸøAŽžŽ]}ð7#ÃË˯œkÝ凞a¯Æ­Ÿ$£ +æË?ný+˜.i·3;?t1ó»H±ê'É¢Äëg>LEÁrn1c³`c"ÍrùH,zé¬YnO4X¸;óùløɱ7ò6X£ÕÎ`QÔÓצPõ‘äc¼Tç¸7ÿÕx3rÆëÊD¬Ùϳ[×l)¶(c`g¼Ü­}£AÌ®ÁçÙžJ€r¿ÆÀLªj´8´ÍœÊ$w*ߌ|ü¢uãƳ[j/1nj’7@Š5E<”õñpÐ Ì*© ¬¨fÕ·BîksrQì–Píˆès;¤-†ÑÇí#ÅSët™JæË"¸iX@QÀ|ÕŸ¼-}ﶷ¥Ó|Œ?ªGÏ6íbŒš»ÑñŒžºaôËÖe×¹ÖÝO8•Ô =’»òsS6 ŽòQVAérÂÇæÀv"Aõ†]}ámŽk,û9K_z‘9ßœ¸ŽÇ•ŸçM/IL)×**C…ù•dr6§Jþ¶»¤˜Ó04ùøR ©sÖÒ–b´ª¯rêûnz¼Ê4ŠìêB¶œœmü“¨zÁ"¬µ¼¸`Á ³A-79rCò¢¡B‚³Ï ê3ÆÛ•WÞÌŠÔ +:BóÓÂb°Iøh~¢~þ‚N® ¬ßÒ]VášÁEŒ4öž{w2…Ö ÁcûóíøˆquÝ.ˆ‘\»W’ˆÑvGÌZËN£Wzý y¸6à¨ÄÛU¯K¼É–´ì9øÝ*„'¯ÙJ¡Ùâú0Sù»qüº©´|{Ì ÊN½0ã S–…å> ÊÅŽGüìã¢c¶ÿp2¦)x•^‘'lW6BxÜ@X,à*XaLHm)©.@(AcO]†£|’¹ *½AÁk»ú·á0º6ÝÔæ¸Å_Å ©ƒESVkkªÝÙç9Hý;2Ø]Ã×ô™5‚<à]Ép†ñw|¨†9¿>|]s €˜}ù)áóƒ=ƽ\¼¿Ý/¤Ú)ßýBuí¶–·ºöuõ™óÏO +ÑTf.:0ô$÷âûºÖ?rÑþaÀòÜá±ÝÁ: ±+·Á"Gür½5â¹æFàHCæí]™3^&uF5šôˆãW® ௤‚Z‰]®—dÿxDŠ|¼x¤à½á6.ì Iv»=ßFk">5‚ù Uj}܆q³WPsk-c=Fô¼beŦ«¿M}¼š<…Jh[ÁÂÞ!·jžÝçfK±&«Lç z‹íÌ`É-7E¥ùè|TžƒÐÁ¨ ¸`Ii{#çË?Ö:)¦[ž˜sÀ‘Ò€ÞcAóŒ]‹^ƒt4` J²Ø"œ·ÈEÈéÎ$ç¥x¿÷¼´è ˆ)%qV*ÞGп†ês{#c^îÊùÔŠXyÄF'€y‹Þv¾ É\`H-Ë&PËBl¾ u²˜–Mªè“Òy#þådÕ{@SG0ÌŽsIØŸù$îÓÇÃAfŸmG¨q`,+Ò^ðñ—Д¾?'WžW›`N¶:S°d¯¢ºm-½°¬<Î$ùvHѹmE´C^“a£¹ØvÄ7ŸßÞC¦F+uÁ1VÐ:X ”̇)­7áïX”yÌ# ÍЬÇîÁÜ”3†ÀX˜uãwW¢h?ŠêÏ@R„½ ˜PZuùÁ“%.?ý-E1šå¥{ %gƒÅhd]m5 !£¹ÚrËXîPŒ¶šﯔªà@xˆî?ûݽÍ«þãl_Fër ÒÎ.Œ«OÛ!-}{ìÚ¹ÒÇënÖî"¶ v¡{³oφWøìç,}é[¥ØÍ`’È/¥)ç«àwU @WÁ‘pŠ‡âcºÔϘþ-IVXÐ eH²îìCc{¨z*®|ü¢uã™"4¿(Í|$ð£Q`˜1uqGÔ­–ÞŸ€• Õc»Ú9ˆF°#¤}­H2´£vðÉ –äTH9–)ØÉ»è{¡ïŸ¸»±e¨mø²ð×÷B¼±˜¿¾'MåÅzL`cã# 5ì0:Žï¿Åž¢ïŸ¸»0Ü{Ð÷"[ìio}ïXÁw¡ÅÉ;oÃaôö˜;ÑÞ.„ŸØ6^X«TzÿÛ(晲çxì×8Æ«E¶ÈÅ•úwdP¦,æ‡ÆÖ‡iâ~º\¼¿å"ƪeqÁéäÑJ]Á¶èÀýÂöêQ6—ª|Õƒ;"+˜ ú¢ãµVAëxcª ÛˆyYØ[ì²súQ;ñoíóë!’.Mn"™³Ì ’j]ä"Z­óÑß"ÚàýGD}NnàOmø]5’i NôloòÍ2UºœMHÕçŒaöölšÐ³—½ÍrùOD=Êß|üã¦ƒÓ 5Vgí¬ŽcNë× +UU8®äýdâûæ}«úì/ïbc‹Ñ»ËˆÀw’¼Ü &2&&aÖÎþl–m®qüµ¦RŒþù5¢:ˆïP'c;Pe”3Mmå¯þžx®z¬ +-§.¹+ö®„ +:9àl+s®ÞfßlýŒJ=`K;¨«…@PÁ!-Y!Ù^\i¨Šóçšû‰½N1/%CF§„õØþrS‹ÇXAmBâÄQ¿¥vD’S"ê’$Œ½è±>G‹îxìà"ZÀÕ–Vré½ûÉ5¥Q÷Ë–1áü>‹ž@~’@PGþù „÷> +ª¨þÜt2$}y¡¸¦¯Mi®úˆ+N%Y¼aûÚRàìÓÇí#Å"^ÖÅgø‘«[‡Z±¶x3Ž_7•–§KáÞ§Â}ì¸ 3Nj™¨hj°Çˆä°Ôâ}KoÿJ*Àl¯ÆÃxŽþ»^óì¬Ì–‚t;³©Œ—ZF !‰Y-¼VfŸß;îa£XW¯Lþš_rÐgˆ»‘h¬òó¼þ¦q®$ y'Mp³šiRDÔv÷Ï9yímDÏ^ߣ·ž.«FË Ô…]ø€B@WINU‡ušþxwœ¥ú¬È@.k“¼ yI^éãu7›ý/VÔÐc#6Ê'qÄì&¶Ë˜Fþ<™‚z73Pr‚yW‚Ï'g+îñÅ£5# +kRA=¯)1±e¨úš“†ïF¨Õb¹¾›Úëv×Ò“Hpï¯ËMNP¶˜ã{Á÷XH^<ß +:õ;É€íú9³¿UÁi€¯ŸÙþ¤-C{˜rMýÏzµ-§ Ñ/ðGp± $¤§ÍN¡!\œÎPRhËÐ0CÒ!CÊ-ÿ_I6¶$´–dóâ.ëݳçìž ö¾F°GÉÁ" +e˜@0v«¤*ò÷愈Ͷ'DŒhÚˆ­i¢ý¹ÚbâiïëðŠ·˜É”²^ºN{ºë¨ˆ],Mà)ow¹á´T !8Žÿ`µª’¦!Q·ê±WmŽ÷þO(o {B+VmN²–âµ8&Ls½¿ZQ7_[ó]<Ç®ÖbŽ9«Å°p–ö‹Á…Ïä>¶Ã½ØKÕVsè«>qèþeA4íi:|Häãxv|i_œ°´Æõ÷o1¥ù÷nÑ"÷®(Œ˜Ó'ß˪êx¸¶PÏ7U° ÃuKÜ+n†ÃóÀbS-ürm +˜LUyv²“4ÀXØî +\ßa—’›»EO +L†72k–¿¾ófñ ÿ‘Y6Ìc’¯=Ö,'!CcäP7 ¥ý«ÅÛƓvÒÂz9ëkÏÍöîÒ”f˜¨¸/Å„ áW[•a*1õÜ)5—· "ˆ‘n™—ZcVØ&|©Š`AªÔ†ÛqÂvãEÇúÍã+ £Smþ¬õeèê * þa£7çl¿–ȼ¶Ð§…U,”]½3XEªówerbR•eS w}NÆÁ)v2ìƒ +-bMgÀ1¬’–c-2ùRsl|OðÖ—0çÇHœÇ)G‰B¯<á´TŒ#V ( +èÆIœÙV'î’30ÞÈÜù¨ŒµšõÎý¿÷ðgÔ-è\zhEÁˆÃ…2L=‡G²Ufäl®3¸/ŠPš>KðKA AöK±D~T¯uóËð-#<Ú±f™¹1&å‚xxì^ËÜ-–DñNéæ6¾ærJjN»ŽÀÒͼM9%6Ø £¢¶ÿ kË·` endstream endobj 1105 0 obj <>stream +H‰¬WëZ;}‚yá63€ZiA*¢Pðr>±VZA.ï’0—$$™ ø'ŸÕéÚ÷µ×~ÖºZùu;9ª&®ó«ìª­•Nj³Ò¹5èiåÂ5ü§õ7_¾z*)ªþ½ùîü½×ÑOQ<‰¨Þj6D.r­•û“ŒžÑÚ×ZÉêÏõéxÔópÄ(Š*ÀYÍ~ÉâðQ^bEý#4£(r8…ø†Óxj–œ«k#Õ3RèÉÆÊú…žÉßhTÁP,NÉJÖdUZÄŸo]+Ùü·ëBV«ÌWßõ麗Éü R™?ÅÉ6•Æ8´¶\ÄúÉq$¡¡OJçÈf¥¬™‚ÐG›¤VJµç[GJÃ㸸‡=îV`!ÒZõ8Yu¬¿ŠŠý½y¢ªÛgÚIGK‹Ä·…ëè•UÕ0®ÖxÌù›œû×JæuZ »Y¼·­‹Ã¬$Sݧ»M`òÇÄdA)*†üÁ¢[0Îö©1¤²zL¢a‡‹@cH¡+ÎácPàR(^ÁñC +ô‡C&ll»a —ªé{‚að™ö¿ëz-ãÓôçØ}Ÿ#tŒþ¯ÇÉ®)gràˆÇ]ÒHòÚëœOë`{øpM¡Ïá-™ )3µ}ì˜wn°ÇM¥%s,?çú•‹|m]zP .L0«µ3Ïb*#tB1•˜§ ¹ŒCíT JÚ¯ú’8 o ýà¨[ƒ‹“ykE§‹ÜÕ2É%x;ÎVîΛç=–ª€Ù(¶SðÉHa!žÚÃBœ^`ŽR +:†Ñâ2ßbäîÆ’ ¢úoÃ[<„ 8• fV\ß0ë(Ê®K!ž÷˘÷Ø`XŒ&3•žÖgì]õ_Õa® +ó* ™ßêü“ã×h)Ú³W`‹IHC¼½Ju! 1£¤aÚ“†®•ážÂж‚—¶š–¤o¡p7òdeŒ#±œ}ÖBÁ©@"• ÈÙù…P61,Î+ÇÍ–¯|ؽ* Ü+‰3Z—Ÿ¡a&«\¹ÛæeÌÛšL\Mg\ç™P +ÆYÙÊ`Ä_2˸, S¨'ü;B´ò©®S†Ë3Ôή¶ÄFÅ–~éÌê¤qörÿrV»«÷o¾gÖ•Ksù_qT¼­ÍŒj%ÓüU O›7µ²vs[œ?i§à'«¾‹U냇ús6V +ÕÒcáæ²óSÎoœ$!sõåg¹‹;2Z†@@c­Ø=}]…ûÖãR8ØÍÀÍ/R¡z?„±8í•iyé™â`³\æƒtîòåËèróT0¸FõºžlSF‘NFfOѳnâ‘ctøÀ5º +Õ9¾Q+|×ÛÎ #ÖÓpìb’þíµþâF¿%p£ù›žÞÎI3ú†Kpì,ºtæ£'¯‰xùþ/Ûh>ñÈ7ªÓ[B³x‚[ß:£§a5ÿ³öÄ1:QF7kENùF­ëðÇè—†zõ§=æí*ª Á?[}¾QÓìÕÉšFà_SîOÛï–›d1CWŸóe®ªK|§?% ?XßÁN&¾Ô +óµé­Nõ0ïIQrpcŒuZeTž‚uÖ÷¨",HêIréO—Ukô¾vÉJQÅ›by€]\àêBkg‚=Ý­º0fuæ•T£•°ëTP—ªKô vñQN… ‚?[°·a'º"OmÆR¿Š*¡S—›`yJþ˜˜^–'{y¢¶ýÞyº Q.aVåiN.úìŽfÞj6»¬›÷àýÄ‚Bj¼?‚IÇg˜+‹®a0 -ˆTãr.¹ê‚pŠd Ÿ¤‹\š¬>i^æÙÇM¥%vI±ï2LÛrÊáÕaê]ŽÝW±^5ª á]E{èêdÊÇaj)q9êÓN:Ê“¤¦w8Úr÷Ü‘¼þ3ÉŒ1Õ¶Óc¶ßù9'´ãÐ!¡+(¸ráºí{—$¢hây9^p…¿;/è±ZQ{Y1øÉ|¨ïÆc ZÖPÜ;ˆM¢[Já•I1BC*-HÁL k³‹~'/²•»óæa—Œ$àäqÈÒH™P§eÂnŸKmšqh°!fw~TÏHÁçÙ¦†5Æð +‹›-q¢+ VÃîÁ§ªãºþRˆßqÀ®®]—0¿@,k˾+iÏ\"eù]Ÿ®{¤p¹@\ªvÛEÔl†!¤á³˜qlŠV$ ±g_0qcw²W2ÏQ qzì~-^j(‹ Ç|ëkÁœ4„âƇZ½©|¶mHKër“rɳrÕuÍŸïKü ¹+ìA' *x‘§õC×ë»*QA©úY,¨í~ æ#i(”1.˜Ÿ”õ iË¡øð $[î™±Ñò3F2ZÀ ^£!N>[ÂÆïâM¼.ùÞXÅIÜ«$g®~7BV$† 1ŽÄØõJCøέ±}|Øy]Ž(ª81ÞcOw'c²se°æ϶¢’ê™)Ų•a(*yµ0ÃT£´l¥99)­ÄËO‘)²>]Í~ ÷‹#iØê¿!5û~ùŒ²âd(2évºÊ…Öü ‰9çëHÊ%ÆzƒS)Cò,ÇkKêÌ>®~ûH ´Å<9WPGA‡ÖŽÞÞ»å‹mú“?&&£Ÿ.¼k’¥.]“ouá]]Î-‰˜ŸŸ§è!yò\RÔ\¹Ûþ„xbvÓbÏ,Ô–‚Ä„]ÂÕøÙy©¦ïý; +Ö>ßN ƒ˜+ß®uèÃO×›ìZS`À‘PŠ£L‡ÇqfîÊ=-|`3,Zpïã2ˆAfjûˆEw¹pãVRQ‰nZ!<4ØcDpäêôéPŽ#ªAE¬íPXóþâæôÜÂõ½+÷LÌ©Äà:OB;Ù!t éuÔ¯³÷!GºxTðwÄõ³ç¢«¢-–VËÝJnhÝJVrË0’º€½Ùòm ø€¶ðô&«Ç Øî¾ Öc0ÛÞèíÝcútdìCëz¥JÃ×}†BC(Œœ©ôga`P¿RV¥¬ŸÛ3¦†yš±c1eiÛXGhÆò Ƙ4L¦2ˆðþ¬õGbÐIµóá2*F3FÙωáEydr@Ô!¨ÑŠi"¼ÈÕÉi¹1>"ù³{0žVu½¬pçòZ¨6nË?µÇÆà¶öX×Ï/.ó·WÕŠô§Z­ÈMü[o¹%îÝ]±¶õé,šÀ7¯>$Ù8¹ŒÞ-߇ŽS¹uò)¤åÓþF׆‡RV{û¹«gf•š)R7NnŠgL´Ô#:Gâi7?K®[4¡NgIîgxAvšô©NCœi¼ÊèübNÓÛÊ Åi>Ê]ÿ×z¡9}vœB„Ás¨GÄé+ÏG§ºQÓ¬«¼¹þL+mªïŽÓ +‰£¹æz1Ç©¾yV$ªSñgé£KqšFWùÍÈt +ys sR‚ò~d¨NÓÅí-Éiˆ3ÝòÚtöJÊ:Òž´oút§õöP5Z“ì.pì3[ÓœÞìXMÛ½ùÉžó"¡ûäo–˜,æFš÷÷ 뛉ŽõM›&µ]['=:©ºÚØCPêÙçÄ5É&\qÁI¶"ke¨ƒ&Ïâ)À¾zÊzìïWHÅl±h·Šå”£A.à³ + SgU$^zäU%é6K'ß ¢Ñ*ŸäþÎOÖ~z«"ì ¥¿ øßoRc„Vzñ“oI›Ž¼F„4PIE#A¹ñ éºk·n¿!ŽÔAï J‚µKLî3vЫX÷Gþ—»1)Ðd9¦lu“¿hŒ`*ýS¾³b™ï¬Xöû*–ó¨˜cÇ6†ò0®è{êp}f¸Â$ð ËEô÷W‡✶•À«¾§Å° “®ÆoëñYìu9æÝ?çÙª>p\ +ÄVúkŽñÚ J±LeáCšè}ñôÞb „”šœ~¦o‡ÒScþ`« ?j!+ÊK†“Ò,Fˆó,‡ï²oÇžZ ó¯ÖeÁt¾DÖ*%¡‰áì "1ñã +êiSi’C²£ÙËj§žÌ¾ýôdL$G?ÁO¿üÕ&h«ðd|†™'fš[3Z âü(pÑn¿¿\ H `‚âXAïNë8ïTÉÒ–_¬“À:çÖ$Óëä>M¿6`’ŸŒ™m³¶.ý&¬ú¤ÀŒœË™/_ï‡Db1~©~yUfÇþJ‰µNZnDÓÎwf4Ö¾`Àß¹eåÊ0ï\'†l¬°Üºrws©ÒÄ×=‘Í™v$žÆ WÎͲÕ0»²=ñ>®pÍîxBÖ¿ TšÂ!©Y×ë•X.Þ´ÿv§öÌsàIðÀl¿F äjcþ•”Õ6ëQ›<•:AÏW™/&ú‚tËÅâ{³ÃÁ‡‡O—Ú¦ô›¸Êb³X Âà@l7º³“läB©Nž¡:4U€ öó…ɨdsövÀ&Ø—MG°[}¡dí9 å;…ó%u§¤à3´Uˆ{P#ìo’UÓwÄác1ïLä_6³±RSók¨Oj§ jj!ŽmB—M7kY\‹÷­ž+ÉÊ¿Æ q9Ò䄸 0 :¤ŽVÁî ^M ¹Ò[óÂÚd*…+ÿIÆö×)‘©ýðpõ[¿‹àzÌ—êÈ +]í|8{çÝIûÕ‘ +D‹)ó±sVÃP40Ó¾†Ø{³ ”?¢Ýí!šØÊ 3vžž®ïd›dY´|B$¬á½¹†ž÷ ã‚FH®2„_Ðq¯®×‡¬!fê>ǨkX "«qPï kHQãÅ¿c ¡°†^¯¦ÿ5tY!±´sàÚV¶K¼ì­aio«JCÊF#C3f èíÀüÄô«0¬¢›£D|¥ôé$âv ‘¤×ÜOõ`­¼…N™LdÎn’½„ÉX6Vjj¬GènÆ\H¥DŽ!^"-+{ËvXp3Ä`‡é†ò´â â8+³R›£Ÿ<[ˆ§Û)ÚQË$› 5ì(Sy,\!¨‹4>U‰ÃË7ÙYBLøװOí­a1îÓs ]K¸ó‚®aßk Qà°ÎÜH)AV¿7tÃdxüJYŒg¨IÚ:yϘæiÆr,£ÛTGh[YŒ³l%Mëàûä¾é®T;ìw%¸Æ:‰iY…!¬˜;¨ÑŠi"˜„¡ds%. $iøaX•(À4?ü;¡Pyb(/1á’Ì[(€\i"‚_A’óÂ/Ï$ÜTœÞ—XŽ×›¾ì À¾ÐX‚WYij˜†!cÑ0#[¾¿my³&ì W%y¼›¦öÉx17´ÒÓI& 9€\/ _zð -'푃–s¼Ó–0d‹l]'P»v4B6V˜Ë°m®Àï–ê2[“,*ºD€Oi¹1Ææ<—êBcÝ{[ûøÖÍNÀ¹3ol £ õÜÝ¢VŒ§Ýü,&6á#YhIY­Û~E3t=c\„“õùˆ+ò!.ûÐ:¾8΋Æ염ؼ—Ó“B²<Œ=F¿։ðE£+G¿óÅXï!ZãæŸ=M˜Î–Gùž¶ÿ˜¿jÏçÏíYsÚ+Ú£ËÕ}OY6ßz³›ÿÞïT¹µ¹«  “Ç㪘ø78þwýÑKoKññ|­s½¸ ëÙX8²˜.d.›•ÚŸ{± ¶âg«Ø‡–~å>kº^.vÂÉ'µÎT®ßÅÒŸÜyº‘.Ÿ¥·´]´Óêu{âtý¿ò‘¾™ †jO`êa³bélUOOÚCØ’°®ç7éúý±nü½Š¦åë)Žì¾Œ§U]?3ÖµÇƅ⧧2ÂY¤f~I’ãYB¨¬ºäb½+¹Þì.¶ÒÏ?óíðC»)Ò3Ý+ÁëW‚iÕô8PÀ}…CYÁ™ +[«×‘y›É•c‘ä<›Óå[Z‘ûK ßÕf¢"ß$2áCx¸y”È£¤_ò/KUë¢äCdËíðD.CŠqô+¾ÀkF ¯ÐÌçoðZ”¬×áæ^¤Oj(—Uî¸AS™ßŠÙÿšÎʦû ¢ò<¢Kdw/”™‰³ ººhæ,DÝ ù¹Ë‹âZ’BõÇíÚUOcç›àÚ\½NɽðšQÈ7Ù™/ñ6|8 ¯P-q‚É+¶lOnÇö™`m™¼"‰¼Já„ TØùµ¶LebåL±k¬Ñ´Ö¡<?³ÍN¯ˆ^Sùª"È­‹¥ +«¿DºŠ¹DÆæWÔò¢p¡”‚Ÿ.SLs·Å¬rr$P&%TŸ}Çüóöj3?X”dñ˜=ßEÅeÉÁMNèf׉B7Ö.%‡3Öê&îúÂo×ïÕ—V/'›R³×íÊ s³å6ð(¥]Ê°QË-Ø‹Z×ud¾1íÄí§§.¶Ÿ>·´Ù±Éïþ˜{dž9ƒŽöË+ãj{§+½·}<–ƒ§\£(o^5ÚûÆÇø´CÛž6½qµ½Ã+´½lXѬ«3GÃRhÐN¶[.ß …Å(I ¯7²* æe¿F ¼ñ‘>O®¡‰‹øRœƒŸü+¼&y«ãÇ‹µMî4ov6 ã‰Hàˆ’¸†i~¹¶Jóþ4™³(sÞÍ öõ˜]TeÂ7v‰8?d‰‘G™t/çnìô»@¾ Α‡p¤:œ°P¦¬Ãbýº«Q¾ƒXäB]Vⓦf±@!yY+–¥hÆWÝ&Åj†#Éi-tRVY j—MA~:_ˢǩBÝ §m4´ˆG½Ññ"=¬JH„)t›,bn&• cUe¦› +抉µ{Më.v°]C“‡næØöoU=5¯ÑìgöN.[¤i<Øt|‹ÐnKÒŽåÏ- ”¾Ýä˜-ä•›pòW´†|nö²*+h†¡× íÒÄP«ÕqÑíÓiù@Õ»Õ!—Ê **gÕðz˜†³N‘¼AO –/Ý”èyÈ%çÌ}#qÔs˜)·~Ųô¢qäNƒuÜ@âobÓ_«û°}ª¢šôªÃLº-xôÇ ñu¥)jØ"˜c`ä¯t2»nŽi4·æWêޑÕnUiô'Fðïx~(‚ø›å9hŒŠ|’0Æù Ó5̇ 8|¤žUfæ/SÏv¿ׇÿg–b •@[øK¤0Ú€m““׎Òؼ_}‹¬Ù–bE'›cH<™ì3è¿+„-¿Í*æBý 8Õ¯‘ÂYyiî\ eP¼4QW‡Ò–ZžŠxÄI4p€÷=ë5R`˜0¯È‹+áf‰•ÆUWSšF¤†‡zóûHP«³<¡ ô©SªÇöÎ%Pºõ¼å´iðÌû›M4EjàµXµTYlÌÒMZ½Ð•_ÕïPif0+ÙJŽÖ/4Z¤ s…Áuí”lIñJ¬©úÃLn;ÚÎíÿÅ©£-ƒMÑBy:PLßçÅÿÐé»ç›|«ÓBP—L1 ú“ôv ùÇåE0#ÊíxFWuùéìGN [î¹õäô´±éŸÍç±±R*_;…X¾ŒÕ“Ó`.buj»P,C¤ÃÖSg¾𯪂8_%ª$«1$,3™8ÕšSå~Ó¨Ó½›Åç«j”ç¦N¹8”LÚQï:ã1R˜Ÿ}8ça” úOø·ôP1•9 +Ä@@¿–±q®v9ÜTуïpàØT8?å_ÞÀ‘ü2¦¡±fM;‡}}Ìw"‚Í'ÿ…ô)åÃx&ýÓêYäK¬äÉ)È%˜/÷ïý:‰5N$ˆSn y›42O1&‰Í(ó;>a Ì#rÌJ*1— ‚,„?‘ƒ,tØLüTZ^h„êž +ÍæiñA|R È¥¨[ÙXñEr$U"ÐôzBÕÔtðWPHƒ€ÜçÛÙÝA¹_“$/¡·T]ì›u® ÇÌf“jÉ”´Ý‚§3ë°¿…ðØ‘M¥õ¨¾gÇí¯¶’ý]˜½éù.L+H‹ðIÂÁ3ò¥öà±!Í@~–;Õw4M$/Ë@."+I‘ßY™>¹’·ã×µoË?Û×ØÛÞEòd³^ãM1ªž¤>„üyâÑÇøÃñ—Š«òÈW \ +ÀÏßÉöKØ>Ïí›àv&üaqaÊ!ͤçŠÊ›xðXúJÝÑý´ª2cžõ:ÿ»iôî¾¼ T*Ì΄rˆËúýÓ’c`ÁÚš{a«>‘++ò/KdÞd%5œ 4"(ý¨ÝÛ’é7¬÷/£Ö·áæN…ÿÞl*&n¥àéî½®…~S§Ä¨q+Â~86í¬×1np¤ÖBvÚi)rç>@®ö°£u`ôÈ{üº7`®?Fy ­gºÀ±Ú`ý2Ī°„¢{óExÞæåÃOœú­Ä6ƒ°"WîüÄiŸ,íÓœè“a:¶˜ÎñOØ`£7)gåàzmDå¦W•2ûöBÆÐÖ-…gøa ã\¾.Ü †! +9"R³^î *ìÛ7“o“1©Äe!õÌÉb¼qúÝ÷÷ÒôhÀp£÷ÑSõ»wP ¸a±ÉÑàã¨5±ûf‚.V×lœúGe³nZs6‹—XÜ/ɧJQ•Üïd-Ö¨×&°‹1ýç,A?ù$‚Ô"ú– <¯³ +ÉñT2™¹RÉýYWÕ¡ ðc7jeipÛM©!û¢F‡í áÍ…ºà…îºýéWÑíúyí㜣ŽGîåìí83Ô¨b…`nÛÂô>ä×õŒ`šå•·”²?/Q\ ,ÌxÁAmN<•ýI°óçˆ,ùþ*€X­ +£¶v—¢åòòé—·>´ ¸e\iÉÚ©8ýØI¼4”,d•¬*­„ø€`£ ï(·@&˜õG;…slAÇØ>*b§6rÏíîEf…Dº®Š(S( žÙŒQÈÆw½o‰cßoM f§÷àjPTfá¥%uŠ8dIá|u”ä•.…ªVÚDh#%E˜Óe´…@ûzxÞ¤ØCN+› ”Yïe°èa/ð­ßÔCH1D‹&¢\‡´9l#“Ìä&¹´ÒjÈ‘x0vñ¬¢ñi!š\¾ é$3î—¤q 9÷Kàå©2«lÁüáþƒ˜ÔsJ° åBòwõÀlà`Vz.¿¯+÷²Ÿ©ôg³Ð>³¹ï¨4( +µÔrx_ƒ*\°Ü+á‚Ùȓ휻•‘­§ì#» +ÁÕ¬fcRû;XGó6Ì1œ?N(Õ•Çw'ã~ «õxa%yë½ú@Œï+‘‹¸.ŠŒÉ‰§ )l!JmÜiÇ_ê“`²¸ƒà©Ò×GÀÔU W(‘½†³!Þ?Ú ˜ `º’]H…ìEÀ©ÁÇG¸ñaÌdW^(Ö»MX¨ÎokPþ v¹„{°»î$3â[-®ÆÌ ŸÉ<Ùl+¹””eM÷¼<$â?ž‘ù©KHJØ]Ò 4Â1lëd#Y‡bØß^šx¦:)M±ô­ ëÑè„DŠ#â§óŒ•´¬ÎQØ,ÇúknxŒ•@쇉ûÉ Êý‡z$†4”z7IœËVšÜqœxÆùv}'CØ‹IœÒ£ÅÑ$] µä¼‘›i¨OÆð§M’ËiJòÌÃuôNö¸É3 þæ˜ þëBœVI¾-mäqBoÎ3Ñ9AH 4tåýÙ‰¤Ÿ5a`Õ/&´ÎÒt+Rÿ«#•}³úÏÂ|6ó§þrlûƒÞ^GôT,rx¹ä‡9*ÿ²<:Éðôǵso§3Ьö K‹«\Ô˜>-wW;ìb)/cr_šÜ×Pì´yUnƒë=¶²‚¿iÔkz¦ÑêÕ›ô‰…ýþ€ZÒ +vŠ7:¡|ð³üæÉ|«ëUºåÒú`å +‘]6 +f½ðKï/vFæH‚ÿ‚tìÊ VBÁ´š¬Èw©|–¨Á‚ÍÌf±è#¼Ü5Ð0©¸ë¹ËSsÏ ÚžûÖtQÕª`;OaÙ¿þ{'ÔÏ™Y#¤;σÚwßu5–ì‘¥†ªYÎ#些.ðr!ACÉ ©Í1Ó”¼hŽmQ2<½ØË1BÂä?‡’+BŠ~~8/ÛÍ¢¨çr¬±ç–Q|ª±—{Y¡häåÌÆ6¢üWÆã^~úL>w<^½Ãf½üÚxÜCÑf©§wP4ÔZ~‹…žÞAQp‰²µNîä9Ñs¦ ëê”c‡Ó .FÊÔn§}1bÈ¥êÊd³p®©LÁODÐሯ/¤À¢ì¥{ÝÇwé3h¢1jŸÍ„ùv1XñêðñÔ%¨hÉOP&»­{Ô%hçÌÊ>J_”š¸Ñ¦|§•f;“Ì·9ûn~}Zex2¿{(?qï); úþGzÿlÝs>!·&ÿš’5#ùmé‘YmI%»¨`Æ„Ôæ˜iJî’‡Å(+BjÁÅ:g'±Pº]–nŒ"xr67¹înPž>¦¡=EÂÞG+¥ž)*ç嬺ô-ILzÇ–FƒD¤@Ì´“£EÂú$Þ Uÿ×g䉫ÿ[ò`×!ÕgUB:c®Ì¥^~s§ºüž„ÞÛ6òwºûxoÃû²&‹èËùË‚„Üö†ý« gˆH>½†^RߪúœÛäxÖ>¿¡‘YøõËÙPoÿ7&áœ4(‚y{¯ŽÔõœ ÔGPOÁ¼Ù—{“ËÁ·[]þ/ÔxŽø/«ñ8Ÿ÷žÅ4ÇÖ<1ÿÒœ) þÏÔxÜ¥0Q´‘'ºÍ<Š–4ÿo h#Í²Ñ +õêŸ7³Ì£x?Å(„±ÐÓfQ´‘VzÚ,Š6ÒJO›EqÇ‹%¹ EùÛ“ÑE}ùÅÉh„¢4ËÆÿèûröŽ±;ùÍ G欥pÕû„ãQÙè'¨ +“ÕÞJ3jç¤ÖÙÆ{ù¡Wì„0α‡ò÷ö%¡·çŒÄ)Îå„<µ¾ÉÀ„Yï2x“âJ§-태>ÇÌòä2½V¼œÉEi–V¸¨OKl´‚âJó¯Ù¨÷ù%«Òa."&›cãyéê«k!ym ?a¡„–d… ¡Š€Q±"ØQ|ÿ=³ÒÅÿâÜì-’YkÊW&æHñt¸mÜóüû½ 2˜ËŽÆq ‘¡œü‡ö;Àsƒ‹Àõ¬1—_{ëÄS~ ln—Sþ\dø=Ͼ7Z yɹÕU)¥^œÀ¥¾«”g6„ sù¿ýÒÝËGʨ¬Žxõæw®.oÛAŽûr §r§^ÿí);‰Œ¾‹íì-`xù©ïù¹ëy‹óâÁmXc.;{ Xïy¾6û‡J§|ç{àõ®ò7JƒØÿ©ïYv=Þú*‚ÿ=þÌÛU¤3¶5=þ¶Wô”áÁ5¯æb˜Ý<øVXÃS~é]OÙÓÛ"zϳ ¦×Ï¢N£RìzfùäÛvÁ1;ÖëÄ'ùµÅJH­³7³büÛBÅ̹™ýÀÖ—c¯ ûÅ]ï<¥Cy÷º‡YzD;ò=&L?ºír}Çƃ¹\¶ƒž×…LÍíÕ™+a~}h7ì_u(ìb› +L"«›“z6*31ÏR.[6‡á‡A1:‹†ÚúEÆ·ý¨qò¼óâÕ¥cîÐq¬š1½¬Sú|åâ]è_÷½œ¼X'³6´ÄØ’Ö„èˆÒº±ÀpKZs‹È†ËB'˜m†µºï+úbvC/ ?,­ q½¼m¸”®_ÊôâeUˆ¥±YHr…w&iŽ½ÀìþOBlª“:^äi'öé_‘X¬mQêoë´êÔ[n]'²©N½DvUû±¢îÑá3ëî°Dv’FÅ6ÕI}üZ['?¹™‹ÕoЊu7ŒäÆv÷?×ßl—¯ìBÇ•nEH=ŽËGÃr=ÍøÉøH²tÿRÐl[™hÜÊYÊh†¯+‚·$àA:!äKaÙŠçôVü—»/^d€<ïöé0WÝ×äö€ÚS4÷UNÌ ³Ó‚Ø^\š†(á« ó4χ}š Ÿ£Í;×3嘣$Gïªn¿ª1ºÿãÆ/m¼< ‘¾ö\"L.ž”¦þѵ† ™¦´îW^NÂQÓi .¢%?l0¤?òUÁôBhƒB¾Q«’›Ù0ìÐ}¸·T‚ΟX>tæK˜ÄÊ¥ÿdŸZuYÈ?NãFMÔ¼Ü{(bíŠ ·IßLúp¨t `_pi44¢.º+Vþî”ä<)ðj±KZÉEžñzöØ?0§ÃTP&±#2Pž³{Xêt[¥Ö ">ßBÑ;"j"âe>ô‘É qsÊ>…D‰«6¯Y¡bZ¼:æ‰Oæ*‹2l#&æ¸ñüVÃ*ìL*¦×³OË:«Øzðjï! a½ šŠv³µl#ͦ]h¥ËCšs¤é+´Myêõ-éŒÂBÈ÷DèÛcÒ z®©5~’^”.F_X˜+c°Ï5óJRð~´ç½Ö!!xî&yþ.²ŠLç¯]¨DN檳yríÏiÙHIÈ”!DOZªÝùip f±C™åÚE| vöEœqlslš[ÿè/Zä…ç²áRžÇ“ÒÝËGj}ùîØ€+¥ÖAg,®z0£bø)ÝÁBf°‰æ´tp[pÖ·«€{å¯ ð#8N¡(¢³áÙ§áU™«>¿]PêÁ6 鞯Jd"&0Íî󥛙ʱsž&EºÌ Þý£œÔû逩*ÈËÁK ‰ùÛÚ 56lÝ!ÊÝû>BÖñ)»èPòXØͪŠuaɳmàÅ؈(ÃÖü‚·qÒ†©§ð‰¿;J°ôÝÈH±³)¥=;hå)]Â>ÅÚÂ\¶…åÙŸöap=s¤¿ ä‚<·šùaºy•ïä,æ?±ÂŠ\µ[ÿùï‚üÝU­ƒ}Ü’ù+hD}»3×qs ¥f +¼U¨°H¼´bü›IqFň2 (BÁ{ß^*ÛùIH?˜ãï´æRÙ8ä¹&Cü=• „Z +ÁU4yŒRN>SBPMj‚Ü“|¨ØÌn@Ž4—>,äÆ7 ]I¤X÷ŠÑ*Ø׫ 2øY;à +&™¾³[ k®ëÛ ¸ìo‰,«Ý~Z„)cßÅ2iŠ’°q¨é7¡û49¾Î8‡Ó|õÔ“,k0^ýÚE§>š ƒi9K¸ˆ÷¼'g%¤ÿ$:Øn=ƒÉU ç Ú=*Á~\Ãñ"0,W…5º ‹¼Zñ4—Ú`Ÿº=UÈî‹Xã<´NNÃgEÔ½fز•%zÞþLÂü É ÌïM_‰Pê<§‹lˆ{ e/MÂP2@7X¢ÝÐY΢ –ôà{Æ>è|fèzäšdÌ?(•¿Þ¬qÎ殡)¤(U©Á†‰c¯Ë[R<£°J”«° ««ä fÌÕñP°p®AÑoÁú=Dã\uØή7wP~j[;óߟÈc.˜Z†k½ëëœ%¸ðúNs3Í\ˆüZ>…Q•°±°öM=ÑY4Ô6Õò¾Ã‡­ e9¨çmi—¹çqŸá2 £Më2íoíIÝóì¼h.³#J8/Q§6… +aôêc¯4ÝZŒc@$ó, È~rYŒ'@ÿ„’þk=F4ƒn|IŒÓ( +ö)yÚ»F¡ ÙK/÷§»WU+eàŸƒÈÿ£"ƒUïÓûÙRÙÎOЈŽOäŲYŠ vwZ~•*nE/ˇmc èÛ›Ž!«ªìà1ü Rër+Aê2¹MÀËwbí 8¨2¯ì— uŠñþr‰'±ÉŒkøZ!àH…1tè`®Z¨Ä[· âˆSó¥¯Ï§Sú3¸ •6ˆÁªÈ:aç¼Ð±·¾µ°_YR~àÐýñK»Ïx +iŠ¬áž¾4î^ô§ÌŠô?õ t”Wmž'Î_»úL¾ß°%Z!²]|” ²š°%Ñ¥'Ù±ò06­&ƒÁÕ»a£ØÀŸhÔH`?UvµM>Êè¾ue'÷ ù@ªBJ™Zb³J§àåx™»ïÍÈ +•¼@ß0º1Š;†ó#Mw}_„Ÿ)¨÷pFS·GÉ$Hÿ*žXJüó²Lú“šWá˜J'²¥{7d½4w_QèM»oCùÁÿ2q¤sþ\Ü(‘w +dË%™ 8dÒšßú[ƒ-¿s¸,X‡£¢Ãß*á™Ý?I›þÖèÁ +²¾§¸d¤æ‡¡:i£™dMëhèzë[0¯ôÖЗEójó8=T­…A2Ê’.˜>9 ºçñB§ã`’¤&Ø*ÒI +Þ’ŒÐ”XÒ¿›Jà.´H<‰w¼6¼—õ\9™`’ÿÑ^æËICa§i¡lYXš@’F +QÂ0•¥ŠUmµ¼¿ç»­€ÿ,ÓäæÞ{~ç;Çf/æí.oDSÉÄ<˨âݺi…mmw£4³5(y3kc{ÙÖ”J*óJ[£{qŸvt*[³G?™­a/þlB¹µ +>d\Ì'¾ 7tWâdýA†öo0XÅTLœ&†ø3¥m™£4þë²j_ô4o Ÿ•]åΫ¼¶Y¸Ñh[걕#úòá_¡Ó©5b•›™ ÙÏ„ Y&`fÛþBKÀ U8ÊUñ²¢µýó÷C>¨ü[+`k}Õ6iZå­8—ç­òwJ‡ƒ:Gí€TÛòEƒRCœV{6ÒœPdKygÀl’sÏ¡ãL6{åd~²ùçøß´ +ÜÉE•_=ãÍ÷¸œ|M•”Aï&” ñ]Xeóef/KÊ:÷Ö@l•˜G:¶žJFyVȱ¼«Ì—Hú:$Y‰{¹£äI: {Q 2ëŸ"öðP~lnÆ!BÂ{ŒXÁ ÙÈè~ãæ±Dìçöb 5 œUö¸ñžÖ…û³5Þw<)ËÀ(M´<{òôÆýABDx·VôÕÚÛk1/Ÿ2 Bi‘<$—å:û~r˜XÕCÇ{|Ù2'5¾W‰øøc?tb§ bËò]‰â%¥#ˆè¥÷±Ÿj¯§ b» +u1#¦Pqã²;œ.¦ˆÙ··n¨m*I„~axxàÖP›>>ká±ÏZiìD¸t-˜ÚW)Šìå$D¼Ìm ·¿Ý`÷ÖÇÛVöaQÔ;æ9L;²“3$бpb÷h˜f_ç-­(û “FëV¥ÆtÚeÇFs°>’ØãnKtObØ;6Ð2‡ñFÚ½6rf !5¸ §ËI?\)®Õ»@I¡ÌÛÂëÿ+.‘1ÆÄ? ¶îyC endstream endobj 1106 0 obj <>stream +H‰´WéZâL¼/‡- $Ýa @ €ˆŠ Ê&‹Ê0ƒ‚÷ÿî°$ççû3ŒÒ}úTª:úd%Ô‹‘"é)“~‰X +§ÿÞèÂÓÌ8ÇÃjÐ@±”F}±Â)[@ü`QâJ1¶„#*[‘´\;Æç“<§28ˆG¹iG +OiNÅ™ÐYH +çú2Ö»[…Sz18"y¿Q²•—1š,ndø#F”46UFükVÆ£æüBx.L°8[ö‹¨²^kp‹X…d‚šI¹(ζc N±Øc5g!N}ÚJÞg8µ3Dk\rÊ#ƒ¡¨+V´Æ]#(1º8ôKb- + M>QÄL[oqÊÝ„&ë•X-ÅÃiDCã`Ä…î²·(•áâK¡$ÖCœÒ¾’qdÞWúQœ—âˆvG +æÌšûA“•œX•Ô›tÅ‚›*âëS´A XhA´? )ëèôž`P•Âowe¸j™íÒØ„§lèrNFTÊ‹óN !…ÙXMx拪XÛl{¤1×Ã÷?EàAI*ŒD›ß> ‰B⪠ºú¥¬™üš|1Ê™®¥Ù··÷ðçBLxnÕUI{Z'Äyä•ž'ý²X{ù•¥G.®Oúžµzï%Ñ1œš̼OáÛèœÁFlca6p™, ¼8k•xðEŒ”‡ùKxFV:` ¾Iï‹«üÒÜ´DkøÆ +Ͻðî-†nwó;Ý)+/u§s«a G”sÎ1–â4Ü#?u^•¬|ŸFNI%c»Ž(&œ÷ÃŽJáæeˆ{Ï‘…á-^:|FgŒ¸eY—tá‘ÐùÖ&Š‰óè… ÿŒ«7ÔNÓvýv¶­Ë˜é[Wg!è|#ƒc U´åq‘œ(A³f¼L|`âíHî`Ô¾†é,t*#€r.˜¿‡©ªa£-¥ü`ÂL°«|w:(E:•¯aš×…Ä×er_“Üœ“Ôëmù‹[`Šò-)»ºÕ "ÓÔ%GÔ`H{ŸLX +wVÏaºÉ†T…Æ ùÐ +ƒ™Ì]µHÇ”{ý§)Á›†›^î–\µ¢ $Lögz–j$ ÔŽ“sx(qáÜ$è/¹}ÍÁÇÐV +79ò1 ÍŸlù°©ÙßF{¤y¸»X¡U€Heç-á7”)xØOÙøaùŽ¡©—’‡¿uxîú‚HOÂÛ»"ÜR›4qn[~½òÕTŠô~ %<£%Ej/QߟJ” ý¿)êe©ð î“Žù°ß¸\iŸ²ÿï¹ +s`?â#Oéo°ÿpKŠ€¡¼ÆR~ÜÇzï$]¡äà$ –|´ “mzÉ&â\Ž&€¿!2Â9*0 ÃĉvÀÎôRÂT],ô]¹½lŽ)õ"M6pâÂKÄhüŽôI¿ck´ý§ÍÒ̬†GÒõ¤Í”W0û{¤1å¼(\ÌxJ]:/Æc;Äi´Ý+™¾Mûýe·×»S…¡‘@d‚³¶F +`ðˆ9v ß–9oÎâ÷Ô]Y'XQá^k™Öö±8ç-§"]»@½TÒš¶µsÒñë­»¤çw‹ –Ý÷a¶¥iíd^hLõ¿=¦Vº¤nŸ¦UÐ'YáÔ\&(…Þ«)^³T<Éo¥_ôgWúѤð­^qn!²ˆ‡dÆð…³ÐA\Ê@¿A=c.gø4I¡Ç•A°’>ó…rIk ²0•¾ìw¿%é´rÏ~¸a’oC|)mJ~܇\X´`à~¶¾×äè&™Í:érD#eú±ßÃýÖʇûÕþžý~ܧ¯wsÿ¸ ÷=ÙÒ—ýˆqÂ~?¥:rß"÷Üñg üg8’!EméÿºðwWkø»IºØwæ– +SÕ¡EÄû ›ùQË#õjKxú/â‡wÝ>€|üm…0NÁ78Ê2r} ü£‚€/v4ÙŠ§2Ÿx|ë7YDÀã ëÇã&… 0@Ѳ‰Ò~ÆÄ{1HpC·ŒÏ·•]ÓwFtÛÙƒ”ÊϬðœ,Éÿ‰ð MpO¥Ó©Dü¢ ¸k¥1¼’¬1$!Lvå¹V„&-"k¡¤ûœ×x{^ÔÚŽ–2p¹F×Øø¾XDŽ·  ÝÕ0c•ÑAM ¯ûÔ¾I´Uॳœ  øaó+àì•ý + ÁÃ^™Q ]èИ)‚_˜'{%㩦ÅS4vRN°p3µcyOGÇ·‡âEBÓöÇ~‘žL½iYj;ñ‘ÎÝ.q1& ãg=UÜK4Ù€>A¢s«÷cëpiFœß¬Ôc<%áTg““ˆ^ K+¿§]"IéùÒq>kQõò4+—ÆF<`‚Ê=|š9 }yøIØÀÄtäoé—à–`ήS—g/³•ù¸|ÍÂ$¯³.?;*ÚNuŽ†b`øn<‚xTÍ8ÂßåÎB'àÖr{ð*¬«ZÖ»›œÎuÃ)ŽTKEÏ]WåZØ©’d~¨Î«‡Ó½,MÔ;ñ1â}¯ùSÒøÞ2y‹KaÉlB­ :æÊÝe˜Î½ƒøTseGøA] +Ê(í̳ï|}<ñuñOln|<¢[ LÄ‹ô¯ñõòvæ;ßß9ûg¾îz “…·ÜÝÎÞ{iJZgšƒ³ë28ûÊ„œ•­yœ}cÒ’ Åë_7Ë0>u¬)2MÆ1~÷•³_ýú†³íëpË÷œE¼\-ü1ÕÂ4ùø:Ù,ä›ÜçÎþO©¶ÕûMGŠä±¹ÖïlÜm Ìô¥ê7R-áAn7 +j‰Žåú2Pä5EgÃœIJ߬Ž•^sðËmÅîGp`ö¿¹â¹ßòÇïdÁ#·ü—o"Ö–%Õ¹”ŒµgÅ»mB¤)ëÄ?R ›Ý ÿari4©÷ l´IÌ=iVóÒ‚*_†›·éÍò…ÌþÿeÅ;Ò12 •Å ø‡ñ¥±òõ(@Çþu¾1 +»[ žt4¬Lÿah rPC.AC]] $ô’;›`º2‘úÍ R¸±Hûo|À„êríÍ®8—$ºñý‹/¸6¾ +°"´-/з7¾yüÏŸç-سñ]ƒÊ'åM'Ç\!…ù˜UVïgBŒ³òK Òûm‚›Œ +®ðÈôiKžL—Ê_O³\¾¤_¿x‘†rl³ÆÅÓ +^®Ébö›ò­„4¼íÒµªQ1Ký”õAË\žb©|;Ú«‚$´nP± :ç0ÿcËgqßbp<ª(0·-ÎŤƒA'N~.ÔM—°ï•eGoñ®`ï÷þ.W$~Pl²1ð…Br¥0kJçÞö¾kž¦™ú¥ð䥄®žJø±¦èׄ™ï°b±RAc$ÇNz=9~«pÊV³ÁÅßOŽïæÆ(N8–¿œßÏÔýïHŽÞܨør#†É=µWnnNŽ©½]uSrTXr´s£¯MIV h åÈX²àg‹6% ÓÝÚ¶ô‹üâm?ÜT¿ÑÂX›Ø#Á"œ}ÆÂr°ã«ºTåî Aô‹býH3È[Q_utó@¸"ß:Š6ß«5öµR¸ ×?Å·ëéÕM¸g#4oÔ$C•¾Â·ÌÇÞ!Ü÷øöâؾC^Ðk=ÙåÉ­×&Ö[jÑ€ 9‰˜ßßV[|84P˜ãxðuÖS;æ‚)üÉô¾P» ždÝ?úƒn]kì¢üMä¬Æß¾< ­êd„hC¶‰ø‚¾==[Cm““#â9|.STé_y|¦Â¹mO‰îÛK&îi‰oÊ¥X|¤ñ¹^o]7F[̼Ùé> ð,áÕåØ+ì>])éÐ –C­Íœê=ŸrŸržC•:õ2mßÛŽ@‹3S ØȲXïdM–üî ÝEeƒ£qPs ‚ù“O²ÅLOíS ˜ÁUQЗÆYn¡9é×Í(*$peЃ⮱÷šPn1Š. ”Æo^ò\ï6iI,K¿o¬ôÛÉð7Ñþ¸1‰µi%D<¼’‹;þêP9¶|Ð1Þ®ÐbÌXšø‚O+ o¯’®^‘é;“ÊÊ/èO¡Š99öål0Ú!O¶ovûÐ/2„tKÒ«iñþÍ 6ÉfM¨¬Ý>H"•”‚OŒ¤Û^t?g‘Äil$“0Ó7© :zU˜3z¢Ë‚*î‡ÑÎr‡µ“ò 5^Ûá[zƒU—ÐZG§¡u£ç!8w=ÙÕ‰ùÙåÉW*ê0E¤ Ÿî%7ä³q@ ;(J{³Ç"yr·›sg=í~ÞÆwÿì±má{ÚŠÕ§\äë`^ª6)Ñ·œt‘Yît¤ìZz +dT [,a€b-õkÉØsˆù?æüÉþ%æ /ÞŽs ó̉²@Æxì#б1G„ëš,Y`ÎíÞ{“Âù3…œ=Í~¤ÀFFÕ§ æfg|Ófþ"¢Ä™†®Þ!íè™ÿrd_>óÿbþé±}ðȯ¸æbþ¹ÐÁ›C©&)9G‹ÇkÌ?ýiˆZ¨{‚SržÚå ý—&-Çr@킘Ÿû_3ÿ³üæà?dÿæ#¥¨ñ)ægmWÖ‡áqfaÎä¥==™Ì¿Áï ÷úg̾í÷`œ¥úM~Ÿ\>–·ÿ×~ƒ/–lm‚›4‡t“ïç‚XÓ +Õºy,þòÀZ&È͆Üýø®ÍÂ`ìï±N£*­ö‡·Xϳ&ß®½@cPã8³V¬uÚI”£õgˆ¯÷l¢J)£ÿ¦†µL^,¢ ítdTÚÃá9&ƒK9"E_摧¤ÝnÙ–ûÒ‡•²1~«‘j%;©BâÂNÌÓºbÒiÉP\ñتQÐ\Ä$¥ÕõØmi"&Ï ûfˆOµQÁ ^†É_÷ëÄWŸõdÁÈñµ÷£.#Ø-ùþúw’Ÿõ­¢®þ3Æ·®/ ÈÌÙ÷‡]!ÙIpd…‚òÃZ¶*…ã…Öúû槀|Aáç—ªÿ}èËJøˆN•ÕÂow<ŸÂoŸv;Ì#ð[¾öìKú;ˆ-¶‚üÿ°^¥ë©*Að òÆEŒ2¸á‚€qÆDãBÌv²xѼÿí6EÌr¿óÇOè™éª®®Vþ¡Éw,>“jŽsE¨Êÿiò—ó›âQ“ï±øÄ)ýÊä/Œ˜ü‹ŸmjØ'GWšŸÉ'Úì/‰ @ºôR6gÛ#d/ì ³zw’"‘nqÆÔZ©ñ5ÝNöž‘‚NR—=d‡»øÓýù tzÞàBüúÌìœ ]óöMQÈÇáxî’ËÊ»ný¹ƒÇÞY'¡I¸NG²¼Ì‚+¹î½pVI °Àø¾ép *¿ò8g~Í>Ås³ì^VòÝëšÿ:ÑXǺ[”Ü´m› !xûŒ|Ÿ*&káí¸Ç. ±í’BïÂDÜÅò1u¶üÑhØ>P%÷dÊ0‰Ù³ç2æI±êJÑ–g‚Ê8ôɾ»Œõ’é«@Nî&Cµ˜ r‘Ñ +ó|š8QéÅûœK2ñ]ãƒ&€Ó¢êò-V éWw#ü"ç™Ù<=÷ ãîä=“&;}HŠÔêœ`¼UÉ…|º& Žc5kr Éµ"'Ë\²%€L|¾!’;,šåö}OX—J›ÀŒ’ /ÖX½òV/¿c æç Þ Òq‰ 6¢ª¦a™M`ÕáÒ.ÔwFŨ9¤ÚõbI†õ³µ1Œ´ôŒµ-¾s.ž¸ ß B(V­Û,f¶½Ðƒ2›,_Ä·|0ƒ mnÌâ~w“@¤Üe‘´Æq­üÖ?IƈH]÷áÖŸ±x…iþM>§§#rÓ¯Ä&ƒ)b«îðýLô—…¨[¤xÂ,Œ=°ÕIØBHçûÈXÚKÖS×ùNíÖœÚw¬fe‰@ê7““0¶÷x‡ ÇÿºÃ× +Øg ß`r*x¨IÐgÿ:ÌŠ^è»{2´_²‹‚Zó_†H~w2pÙ¹ciñ^²îBì[á~R;Lßu°¬FqªiYæ"œˆûÐ¥’j^/Y–©¿¬2ªüßÊ&i¨òõæ°ï#ý]ñˆâŮ֋¬< d˜…òÎ`«yì°Æb?M\HhßJâ»F¿i™š^ŸúÁ,jƒóÍ$J“í± Hâ]hZA:]Ê°ñ×厡h¶ óñjt»¿ÊC ·¹H¬›…N›.ÁÏL-&µ,\¨—†¾m‡¾š"™+iHdexŽIúÍL®}X+ZâÁÍ>ênª­™ 19èÜ´SÒ* àð¹%¡Å[+Ï*—0ÓÀk÷ëm<%A€9dÌšÚ‹·êí±DÆåѵ*œ_]Mñü wq¤‰Æ7°åÞ0ä銛HB?‹YY¼àÁ fÊ éRûê»Àã±/&Uë $,e+ãhm +DùÇ<ÙÀ¶±¸Å› À1ÓD\$JþÚ>þL¥š½†èrjñ!ïˆp_ÿÀó¸V­>kмÕÅü±|ZU\,Az»¡KMŒdþ'H.&/"A’ ŸDðßÃëEØJîïcIí´7ó[÷atÉæ•ÌØ0«×~#ÿõÞAÇha)›mg :ö|EÃ7-×û/Zµ©c¿fýE«~}¦Õªa—/½—=µý¸Us©‡^£AÄÓu^€ ñ^TùOª;Ù! .{0e§¢÷D9p#—¢Œ?ðŒÅYÊßþ(A¢×À¶X‘²9–ã]vös~OÌe%ù\Ç%}b³µæÕo=šªíz´ (?ÒKç¸Ë+D4†qiÿУaa*§ôà·ïñß{4Pþ㦛L#?>Å.[š€«jÞc¹!c³m4 ¸eÀjV%.T8sñe-•(ÞäÍŒï7òŸ‡j»ò_Ð0.±Z‹9SøhpB ¶-ÿip}+-õe [ŽÊ?©ý$óùßÙgá`”^Ï’›Œšy» %NÂóQ’¤‰üXõöLV2ÕËvêú¬™ïÖÎù§Bíñ’—KW꟬ fßáTR_×’Û¬ÚàC§CiÒec–©íB1©_Žå«ú)Te}¦½ËðN©¹çÎõHŸ·Ó¬öû{«¹Ï`–à9e zgï‘Í)ì2ºl «-ý$¼»” +áEµÕs¸A§!b½¬îA ³^‘#;Ãu“-Ù˜æ{Qø8|ãªpÉÑ@ +>0 ü°#ÒàGŒtáçâzçtGê»ì¶;¤ôjc~³*'‚b~àF@ªRyØóvéú†`Ž†°¢Å£°·Œ øw!ØÓ—g8~ Àô–ÇOñƒk° õ'ç°lã8.{Òk)Ì>É×d~Ã:Æ8:5oeͧ½waõÞÚà’ÓA(f´ùpEùõ*]K•‹ÂWÐEX8ƒ28Šˆ˜–JY§Ì©:&vÿßZ4+;_|D½÷z×;¬Fȶåy;b“«H‚ðq)¼>òÑ¢*»J¥¥Wmj®_ &ÚSîrVïñh¹ä­q€ÍÎác:„ +-.JÕù„ÐÌ­ÜÝ„×*×ëÀÊ¡ ù8:ß™í <Š¼6Œ]×FÑá e TÇMãp ©àU‡G)'hOøW.V|áz¤T¸ÊØÔܼ–pD#2;ÿ\œ¸( †p7iøÖ~vg Þ¹vãÛWõÏc “Òç¯è/¾~)b|õ‚‹Wò‚õd±kãÕþŽáí*öOLjúG3¾™Ù4ºV¦3§»¯¶½2{:.lp|ÌÔ0o¶’÷¯q™=¶V`L÷LFOSábÚCÍfI¸Ð³>Ìb7¯úGGÖÌ  f;ô¼•ÑÈ<£" iÆ,qzó0æ Š¯œ„+Çý @Q‹!¦çn¾p“ë®êÏctmæû†¨´êBé~!ò#ãGÕÈÁ*0TåÙc]¥Ý4Pƒ²•hòH¾×³é^`âãƒ+þFžàT¦…7ÓÆ6"׈œˆÑΛgubÚ8Ì"¬BGš´“ô +*_ŽÇ°½ÑÊŽƒ7Õb²Û\‡»°Î +Œ>Ÿo‚Ñ +ö»o.:É»²ÜÁF4±«ø}¤±nºI¾ã£3¡úJGì@wË™yøÖŽûX¹ÎuDûv庨î‹)*¿m`{£ +휎ä…Ðe#³ SÈD'H3èl¦# +Dk†Æ9À4—’ûãNI3µÑJg €i܃æ‚8w°¶¢s¹¦3\–"|Å(d ½dPl9GqÕ+KÇÖG`sPü»"L]Cpd€V,´MMkYN¿³JpÖ¿M¡Ø•dŽæÊÒ7HëMSôæ£T6Ü6kDùÖ)HJ¿4âÌ@ãÎP±=‘†R‡Õ=Þ…³”v" Ò4©þé ¨ÅmBCu’·p>"Ò}›¿%ÙD:Géhð](-ù½uZïΟNëͦ·hu' ‹Üõ´-¥ÞÔé,G7»ª«Óix¢[Õé2( +Ç{è4ôØ¥þV§åÄttl #MÀ³Ûæ×tÚž°0Á~TêOtúao/˜TB‰~Ôéæ¿”êsdfÄ9(£#ÿ€Äkuv)<¢*€tO#Æ*”báÚ.ˆQ-]l ê™1x½ÀÑÒ •_ký£ˆ‚_¬ÿfÆ $ÊzÔƒ˜ðåÿƒùÔŒ ÄP±m'¯ ¤Þ…±ÛŒ§¡#Ôé<š1°WNöÀÔÒAµZ÷£ÚÊìíµU³e¶2°Ôï„y±(ú¸Ow‡ kmIEˆ1óo¤´_fŽ$%"Ìë4¾§0 Zò²ösa¶¹ÿ‹,BÇC• îœ×>$)Ù9ï갞Ðèl/.Æt>:íI¼Ö{Ë9Qº2–¹ñá/ÓÃ_Ã\‰<ö˜§>™¦’2Ö+¯§©SL¿[Î4%ØÍLáèë°’¿z×,I >ªD§ås#+ÖøzC Ï—÷4‚?ÌŸ«ÓÇ7´·TÔ_í´;ð×ÛÓq[u7šaǨ¼uâ‡{4//—Ja¢6$£Y©Â•š´fajâá QÔúçäWøvf–¯ŽŒêe±µ"rìuÌÚ‚‹[ï¶Wá +TÛ|C[ÎÙ1@©½?‘ø(v2äoDÃÓGL\Ñ!‚^†á¦Ä3›ì·^?eº©\ê†äUf/("ꉤ•6ÒO“Yˆl8.¦§‰ +Ó«0t²»3x#ÁÎ+ãÊÁÍ“”4;í4 nkjéU›šëWƒÜ]éìú[ÌÚ,¼ïœs™zCпVËÇI¶!øÖcI*r`¨ádµèâ§!+Ç£”SÇ ÿÊÅÊÃúþÀ{æ#.’e·…’>~Þ0Ѷ=°-yš j §ÆaÈÇ‹‘ÊÒ+ Ã¼Xåo N‰tó¦ 9dÖ‘îlçK'6U¼³åáØÉ™‹†-棽WøO5kM†¯gƒl/'Ä÷¿ ŽÜ×Áq[ôŠ¼’æ¦ÃÓÈL;ÎÞV¨6º˜o:˜,øÑ»)Á»Kœ= ŒžšY¸}ª3¹_º"”!ÿ’Kô¦´mFEäM3‚ˆA§ò cÓNWU+B(ÀÔ09£t~ôÖ 0âA©Ô|nÃŒb8T 5’82ÊuÜÇž1rÏضMKåÁ‘¡˜õ©Þ~ƒ]êò÷úãû§¤µcyíå0y|}%]ç 9փˇ1|;I¡§²$K.²¬Ìi9<Íçé+˦ú.Mòsÿ;UêÓ/¨JÌÏ5é ø#Uâ#bŸú¹&AEgâ [*ÁÑâJôTâŸ4‚¦ÉRv†énR·•éÌ)%®^·ì={:.ì§7C„Šåë¡ ”õ&,į[MVÝäý=Ub89[¥M uÓ6 `¬+/šdŸJ€x¶ësy)ÚƒˆÎ©%Î(š7ßjÿT# Ç<•p¶rÔ‡£É ¥-F7+µ±”=†€P<Ði2Ð0h 2€ŒY¡ØMäãÊãêUñ‰¦ý÷™áо/eØYZ€Ë—ÕÆ„ÍÀze™h2÷¦ƒ@(HÒæ‘‚PºìœÂODH!ÿÑ^­ ©j]ô zó†ˆ + ï¨(‚ ’·ÊÔJͬvíl›æûŸ9 `Ú®s¾ïŸX—9æsŒÿØgM*8¨ü4¡ì÷<2ÐÖ©—÷Ÿ/H<й’0ÓE³bhz+J¢øpYTB+ëX ÙƒnüjTø®TØ`ë:¡XÝ]ì¥ù¸þXóq“yÍÝ䫾X–›àoõ“ /:bT_´ôræã~½>N2á¡îi 2㢙 nWü´¶ýÕ\¯VIõ;0êV˜iô ógâÿïaÊ]†ñx\v«~á.6sgæˆrÍŽ]d·º6y¸gš]Jb¦vëÁXÒ¹m®ðþí;¢U ‡ŸÐˆ›åœ„ËGà»´¼–ç'ö`š°Ú¶³`þ°YàÌky{мZì‡ +¥×o}Ö · ¸’Eàù›0i[Ý`5ŒÁ~ý(™ÌüßQ°ž§—Öa}]ÉŽº*ø{¦Ãîz7íQ…[âôcUù“š¤bXÏž—\ùÈÃd/kÀØ|"ñÜ1e¾ñò6p4b1vÒkèìIBÂfí¸*õ0Î?™Js k—¤O¢‰ÀG">1igìX$%|ÖnO¡´w¢À?šË +¸ @CÇH’Ýy©ˆ!Mk¡šdQǨI3çöèÈ”)Ò=Eª¸€Gª‹$`ª4ÏØ„ÉËU±=Oöm8ÖaFøRè)¸m%r?ò;Gáæ eVùñR««rt6Ô®ª§H2ÙvÅHO[Ñ\jÝ­ý1=ëßÕM:äùÇ|í¢ôÞQ¦­P¾¾ʉ´Qèô‚òS[0_*÷ÝU.¦­noδ+eØ‚e/A^ð—áÓcþM˜°¶ÞIÄó¬èoU¸%_Ö»Y¾ ¤zWzº‡7JõêmýUÕ_ûía-çÿÝQý÷ìT,—f/áIR‡Wž'çS8™vÛÏÚôz†ÚŸþÚ9R59.73ï#XŘÀ¢Logµà c_rtT7ãb*Q»#ŸTÿW§GV7wº Ëž›ôjµìfæ£z4§NÕ_œÔ ¬´Ÿ ¾ZVe*ÓpÒö¨îYh+Bü!ŸèÚàáå!³k)ú» Hüë%H¸Äûò_*.ƒT(AÏgOh;X<#c-v|‰ß9vïŒß=¡ía¾yÆåÖ¥d÷µûÀèÄ  ®Ü…2Œj®ù" t\?Û ÎEï†ì›:¦OþÛ\œ“‰_mà‚Ž=(ŽxFÙn.«×*ÝêµÚ¬ûÎa±¢±Z•Š3 Åù…*3ó‘Z³?°7´b»*3 [zéXæo‰çäMø›S“A9r9kr»ž¯ÁÆûN`¬‡¯n_Ž|6¬­T`’ŽÙðrº/ä`úê; ~ê<8Ôöx±Ï•FÌCC•~iš_mpþF—w&òç ˜}çYâ@cÏCÂÛ¶7‡4Ù}FÌ*鯖(®¾,Áþ ­»ü‹3Z䛾}òÎÙ@ÆÚ5>Ží„…:Ñ|i ¦!—$c¡€ónG]¾•}°“ÿž~Z\¦øêªkY‘R§öÆ6ñ¬8N£èpÂV®Ü®Gùr(óD„£™Ç•xìí»hécƒ‘?¨?‡`†ï*rΆ¡bÁM†Ïe¾ˆá”~³,Deæc-†¤kÑþ4Yc¤”2ð\* †Ô»ðîé™s½óP^£UìˆPÙZI0‚|5™$Rp²b ‰¹DÌÅÊCLiß\ÂM'‡‚72%¢±JZ dÌ>Á¼p?º.À…–UA?•áÎwš—ûÎä Ùv·i©¶yÝþ¶tÐTùÇËü­]Ô”ÀÔ4†P‹POH´ÂȤnäOg)_@k=n§íuµá­d‚¢Vâi!¨ à`˜&žÖÄŽ˜F«ð ¨ÉgꜢÁضaô¶yÞ¸Aξî‡E®å½í\Ûµ!ÞÅ݈œXa}Œ…eð–™0B;À· ôÄtL*D¦‘Až +øqàgã¶LGs0Å&õ@•D‚>°b¡õwàSnàÅ&ÕÙ@á›ÕLVàÎ|ªx0ËÁ„ì[€u®ÂßÌžbd\dÙLÎwã0Øåd^Àbbp†þ­ÆyG>hBy‚¡½áü˜6²@û_ÈÙà™^ùd8’ùN¡³vÙDƒò¼÷³ØÎ.!÷#RÉgÛ%ð²h5oÖè“ó 0ÃL<—êŠX9ç¡ä9Ã¥<íÀîÛpuÜÜ;¯‘©(·(ÜbhuQÿ.ñï$xˆ(Òøñ'JKtˆà-ÿ«ñÂצÅË?zì¡ÏH&œô2°)BM[ƒ°"õ2ÀÀYÖRÐéÙ2¥¬.ÑÍäΤl›Ü›³8´ÆŽCØÉÈ¢X0PÞ±ˆ¿.W÷}:+½º9$Ìb¥V·ÂŒnë(EXYôÿçÃQf6ÜÞh„]þ6¥øö5¨0®áØYüp8ŽªXÑäæÞpDK“9t +³PŠn Çà‡´eOeBüÈÏÄ!-üôª™uá×fŠ$r-Zcò(~ÀJAxGJ»Ñ +òÿb³åZÄ1Ùôø#ƒ‰jýQi^]ÝÀ» nÛµ,΀ÞYÜB·D…z望3†›*T¹dÃô(œFìõrY +iÇ#!‰Uh§”1JC'Öº™ƒ~ì«Üy$u&ß„¢/˜Uá>6 ˜…þíCŽ%RÈnöC#¯«z40¬µ«S'„ÔS(_¾zºÙËmá„®”&ÿh.øÝ‘‡ùûCOøùÈCQµüÑÐûrä…APã3¦©ØÑ\ÙÏ$ž;fI¸d¼Qb1%@3é:4{Ë™‡KV=ii©öíeº–¸„á+ð"¢$î°Ö„$l" +¸Š£ã‚ ÈýŸªN€°9:sæ1ÒK-o}}Ì 2âÌ¢VØw9ÐnÊMÐÚmžÕ~‹ñð÷ÑŸÉ°þÃ~ß@`"Ere¥ bÔúßtà¾þcÎBð?«òÃ]*vp8nF'bŸǃY=ÓºÌrŠ*3ÉrÊÜë2«¨L\’É!+lÙ•L“”‹°b£;Ýç‘–.-Žnj!(†lÇR +zxô?y¨a:B³-|™<$,ÛjB åß´a±GÊ_HF̾f/~ú׉8Õ”Í6A(½L+ 3TYó9V‚úL—BˆÎ— +ç’ËnëÌÖ‰Zn¥,WnCŒ±­¼ SÅžÝMífbcQÁz÷XBÕÇRû(ý­š™îøÆ­Y¹Ê>]½6ŸÍè|ÄÍ_5N*(7\0û’ÆoN¤F‰“®õ³RKz]Zvùn-uäÍ"úOÔÿA–Æå4ô¿a—PÙ ñЛù“„b+ÌfÞ_P—ÃM£íÛw„Oæj°“ž¥'¾Ž¡–"ïp—L+öÄù¦žŸžÛœGÏ9ÞèÈ¿Ð뮓eÅ« м¼½ýêÙHL²õ:ò#±N&+tøôP{ŠW§…var‰Ež¤Å«À-zT¸ïþ̧kÜ  ê¢waø­2ãøýòťĮFõ¦ÆGø0«<~“ƒÙ»Á•—¦ÝE8-Ö¹dFcE2•Ã™Þ+³.®_ìæÅ¿ª Šczu¹Ô¸ŸëA¥·~íX¼²/9°m]ÐïBG¡>µ+ôZªÅnh8 .œcv_$FË­Ö½HìhÝá» Ûwç;/P @ÒøÊråÎ.ù“£ϲŒ¸‹Íf¤¨Õ†Åζ¶þ¡{Î@3Éå}{C^Û1>,ÏŽáQ,¶7Õûó¢R”Oú8°ïÛkdðRèÊkTv˜C[”dÔ#ì‘êV&†#Rº™›LÁ±‰lÇ6ð!ï“Û^#O÷Fœñ^á‡m ¼?Aîco(¥Ú†‚+áT‰¨ä¾ÙIQež“ʨ8'#5?§Öq«(Èï2KMìÓpëë.œùÏáÂ^yݯÏ%œžm¿sRBºå¤°¯Â›Ç§œ X +ìûóœX¿,}Κ#¯\ÿŠ5GÞ5mþk /+Úü;Ö°®th³Ëšë_ËŸ’ëÔjŽc´ùk”ámÖøÇ4³Q…$A¡šeð¶ã¡CKF›ÿ5«ŠwXÃòâÐæÿ`Íõ^+xäÝ2ƒ.Ö úÏ9rߪžÿk„X*†‚'Ì ÔsÄŒHtÃ^” ¢Ùš…E•†ƒÂGåã±äð"öfÂ~7gõJªD‡§ý´Û^<ù4*´Š dRœ +žn +ô˜ð¦Lã€%QOþÔ˜Tõ¸¦ðõÇÑ{‚ƒ4ÊJáiqìÅ{¼„Ú>I³r|{ÔÎʈ@r*9¦þCáyÔŠWóÜqÁŠÅòÉáyÛ«ç‚‚Q^«Zé2×øpslˆÓ•ë%œWƒmÎÝøôU%'œÔžño6jkg2òhíç(L°>ErW+ ù»|Qí|Ÿ¦Xþœ?G‡Ô%¶¶ÜáÁÅû÷y»l!èäÅ?îlÊýF¬ùàÉÚÚ¼§£\v‰üÚ4ùG’@'£'JAõèÞÿ +V½ïÀ@•+gšø;–ú5ØáuqPxìÈëQ‡æj6 + ÷;y +îcZ›=Í>ÁÞb”bðè]À.ÃúÝϽ'™ë9ú’+:,å : (:ô,‡Š1²Tù¢(@'‘ ;E)œ*%¸ÁUÑ­8ÌÇÚú@ö«©1j¥¤xR$‰š#Kî¦c×N@ ¤)ù¢â€›yñ½ÿ¹â‰QæÄ‹›GNL× ”].xª€±)]®þÖâÄÁ¦çÄŸA‡tuNš4;øêš;Ö +Ç®¶_Ö4Ý·ÊSâ½`‰aR…ÙKv¥bïc‡†_ŽCŒ Š'ùöÁÝÝCqYÉB%ÑL&L£ÐÔ®ôj™;‡ºËX³Y6s0;oë9ÿ¸Oøy€Èî2Xc‹qÌûê”.3beò% +œ½,¾JptAøÏ)_lQîò£ò›žšŒDœ¾å”"ßo8Ù¹Äp¼µbºØº8Ýܾ !:ŸbJB›/ªL)Y &Oª~`ÍtNO‚x_é`ì+@G˜Áu“‚jŸ[4À5Ó6dδ,ý ïÜÛ\»…+‹.Zúax¦*»àŠJ¬Y¤wœ†–«•å·y +3µ–“+Ï“.» tôó ôZf½>Šâ '¨”`ü£8È;€ QR<«଼~‹ „ODyBq¨'>”ÂŒsà‘Kk×‚Ò í¹Œóå,€Þq•-AÄÇiŽˆ_y'’gnÉUíÑÚdH |ÎXG +P¹ÒË'ˆX»Íãá)€IÒéP±PòpÐIÈ_ÚëZ\‰@†¬ñײ~•|ˆ!‚bþÄ™[Ã\A|X *Þ?8!P˜lùÎ?–%.˜}Éáÿ î_F0ûÝ—ø!ðNËEÎå}’¾iðÍ|e¢{û¬ÙÌÔnG¬dó|ânâ¿¥WÖlòH§ÀÚ|À|Þ ¶¼)v"¾—ÖÀ¤ÁG$4†¬×­»råx&ù´|áaEÌÔZôõnÌë}£×&#¡9)&%vñ'{WƼèÇkh¸³w󣵂Y‘ +ýsέW0»;(Èr~UE´!ȾÂ;úNÚUË«¿þÍ–H„ÓÒ +píCvU`p¼CÈT3Äh9¯»´å˜'ûc¾Âá·†;9àcWá‡éé+ÞÅRuW]8\$·ª…» h€èÂæöRI~øheÁÇ=v÷j&S;» këû œ´z àYAî· +ĬP…ag’ƒ¶nEŒ†O³ùœíxXÉ›ºgkÓ›·A…ú²Ç:»šÝ¿£êZÁ ÈRçK'EWçé-¹ +ϾžpD¼GpðÄm5LU˜Òåfÿji‹XìXö%FKŠ ++/£ +G–Î.À¹'Å„§½Y«²ìõmÛ;°sÄԮ—bMZ‘î81;s A©‚8ŽÓáeÕ¤úU¬Î"‚FâÕ9à/"m„0j\ÜF¡]‹,D:eƒ´l-µƒ§±b°†ƒ[õDGéf΂8_V5o‡Õ¼9<êêN9Hè·Ò äÿnB£¨L­é]—_f ª3Ê:¨“›PHI@_'U­J$¯„Š¹ŠŠ9“§%a„.wðû5¶ND­SÀì&Á¿Ào +àe<µ:qxµ¡™að$£ÿ¤TL‹jys´A8¦rA-^Ï58¦Pùë£Ayȯ6]¤”ÓÊ-÷¦˜Çiz°P±ÿ<Ú/‚Œíô7Î^[.d9ßgDL$Œ”Õ®ð[ï–à†§Šæϳ2Û¢v±÷d|TškKô½˜>Eã°Ü˜2ñ³ â[s¥ûvêìƒäÿ·é‡ØímÄ?ÛgÚº›ÍÈH3ÿ…êËw{¥lµ§¸¶ù°G[Ñ©šÖ5ª+Ï»ãþâ“ÝÃ[7çÔpŒ*S‘…ŠáB®4ÊZönÄSåvл·v}*)nr”¼I¨6n€Wªm)qÐFÒ‚K"ÆT?ƒzy;zz]êך'>@ +œG6sN‚‚Œ^Œ%–B#²“þ»9é¤úR‘†×¥#•ŠkëuÐùµaòçSK‡Š4õù-ˆ ãîs²À$%Š€¯ÇÛxTV5)——æŽç+àû`ïŠå¤2[>¥š{A2yx@LÁÝKá¹À!‚»ñGˆQæèÄ÷ÛLÅÜ4CŒÖ2ƒpQµl^*¨¥°TCª€ÀÕšŽÙ2™Ð<^o’©šÂ,ti¶»Ù|¿~ý†_Ë⦔A<}qSÊè 9¶¹…ßöôé——ÁR涓/®ÙÙèü®¯êÁ‹l)tÓ`u£Eô¾/¿×Ä{«8X\çÍôsªäµ,xÚ{ó,áJÔÙÉÕòNÂÿóSa#ÑÔß~ܲk™ðuâ‹ØŸ®SHä¯ð.ÉvèŽáæUÜFxÉx +…á„JÝ–Hè²´U$ò}àww«J"\ž3” ôLÙŸp#aË‘MýÎÆÀµâ„¼ªÞÜà²0—ÛßX•°‘OX3‚Ÿvv×µfîÝœþ^Ì­ÓIó׶òzÑðˆ—©Vi‹›/Ä%{ËñzôZüð”¸ÞîžQêî݈–úï9œüön‹ñþM·yK2jÍF7ZepH};Õ)#H 1“Æ4^oá÷\ +`×°À=\¢ŒÂŽ‚²ˆKó —)hXg.Ùúyļܱï}dâ¢páR”j?_„h3Àx™´2›ÄB ç묙$,— `Ö›W*ͤWó0išm0úy. +ö ²¿ÉDäjŇ•JÑQª|/·µok7p;R½¶R~šêO°otä—o®ºSö¨—©*ääÇßúèì©Bÿ²wÕuá¡1.\¤k#¸±¨íyü!‡½ü°ÇrxûÓÈáÑ_Q•ßðxÑÔãa†¦q¢B ïea4„Ï<¾ kþŒâÓÍòVÒ?ûC€¶{7Bƒ?Í#ôô+p?.3û^ã~k§’¸Ñ:ƒ rdøT‹åTì(9J™DêùÔ…'÷µCä8•ë‰ïG‚ÿQn8Ø=rdoZº eN Ç©Ü@Ž}Ÿ§r{ìûä8•Vû„ãaè(9¾âÆxÈÑòÒ4þmrœÊ ú”o“ãTn -¿Oxç,È°ó`€ØYù(7ìÿNÒÑÆ»qèä—à!nÀSŽ£-ßOÆ%Ur—cßH“ë^ ÒÚƒ&¨ØãTn8ØOÈ‘¿P +%ZÙØU3®eäU«EiqêPAG +kzÝ*8+­O!Ú1~:^Àø¼‚7UÑD]>`éL_÷“P‰´¢‘b!ÛÊ Œj™9‡³J–,l99°_i~z©z–^Uõ”ëñÏŠ8o[$‚èkûØa„rh’q°¡T'Pφ¯¦üÞ ¢ÈýÉ>¿Ö–8ö´tw®çîDzªÈ“š¸¹1†FóÃ&m3LQ/4¿]©š¹=Âßÿ§™ÛZ ¹Ç$è±S©¿ö蓮iZ™Ï¡"J! úc1hâ´Ç$x—¦Ò&¥2i’OÜ\XLj¶øõ¥Å$š“¢|Ÿj”µìÝ<ˆ]ÄÉw¯á<^N¾L3ÖÄ÷9“¬}+O2Øûã´¯™”[ÃÝ4ÅN©”z|Y®Aò®°ïDhÕ¢pÜ]Œ')ÏB>NA¬R +½Ôq~ùõ«†ù¨7îq–eŽf¢% LðÑ›¨Je5H©+Õw#„ ÿç¡D„ÐRÂ(¨H-;ljj)Ì”U±ËœÑŽMj óJ7p¡Lùv¿0UR94™Ì~‹Ô)ìÅóMÑËOSÈþ ˜,¨žÖ KÌ\l‡îñmÞgÄ»‡2ã»ñ7)"á‡8ÄË FÊjMF’¹_ ÔÒ`ÜF¤ƒ¾ßa¤JtÄ8sYç6gØzŒz„ƒ¨ÛŒfas +¾´ú.ÊŸ­W&ÐþhÃþ°Ovþˆ•³Xfóõ{ªƒÝ•3Ò¹NÔœ-·¦ª\¯¨ÎQÓOB¶_¬ë°-¾ß:~ÚÙWe#Ôš¹SV‰.µ]Ž[‹„ëãtž’\ÙŠŠ½ªŠw^h¢­¹cj\©ª]‡Ò™Õ¤ÅŠAÜ[1®·»õP8˜aª¨x“«Êkžz«Mô^_‚jw}V p}æ»èxnNbHËñ2­8,t +üº!á[÷w×n›4YW‡'\Š/}EP¢æX…m+«枤zÊ©; è|ŽŒcrá1›$`£C8Ó¸ï]ËÆÛahCgÓƒßât\¡<À‰¯KR^hžøŽ‘°+Œ/ Ⱦ(ýO5µ,bvîÃ]1ï<ÍkºüQ$ŸÈÁF®sDAµÄrºRsMŒ>a¥šgé–ƒZ],ª^®äëò½X(iž{wÞ‰’Q>_ÁÜ®ýe½<××¢0|\J„)ÔÐIBè(‚‚ aPDE‘rÿg­uæü™‡1É.«¼ëû•I(bÙÿÑC÷&Š%†Qà½yÞ ¶à#2<¶ðÿ +_##‘ /+T4oP¬“ñoh+ÉÀ ›r©~Á‡æzµŠ)skîíº)ôŽéèåí<>3Mïj§ažø¦ôÿè7Z@ÄôÍß镯<ÞS6—ôcýeÊ×HÑðLyªXÍ,M¯ CÓöÙF'Ü…¯Ñ44û…ÀCŠ~¦@JNÖGYCžå$\â÷vÅo¥Jí¢Óθ^âêëͬ졎JöGvÆ:ÈÉ?»Ü¦_$‘õP0Ê ŒG¸H@8%RÛZHPhÍ2L16+&£7³/£Ö"+Mó DæÿÖ%ØE¹I ‚¡^É*–<¶ó­!AZ>ÄzXEÕ"‚ñU@ltUdMîÊóÚl) ©CØŠEhŠŒS5`TàÕÕ-¨ ¦˜aqOôgØ «tÜ—ì?À_.Î4ümRøPW÷Ò½zUnÕ‹Oý^û_¼Š›S!½ÿ¿z[ïËÕÛ;r² õ©Ÿ `"d\›rþÓµšÅdÎìr|sæ{Ôî®S³ÒÅÖ7W¯§gœ +`H„»í}ÑC–d8ŽÙ,Iu¼Iï¼J'¬»¦”NúQ㺓Š±4$†NË“ȘY+c¿ƒ¶ÔY›Ÿ<#ß\㌸¶¾±P`ögi“Ÿ*µÓ&Aî€0¢ÚŠÎµŠŠÅ Å'-eAéŠ2:õÒ¹Áz¿g…‡í`Ô+-7!“®·2ŒÖg¯s)5}GÌউ1zO$Ý-FuP$'ŠáßH”˜>ŽèT¾âX6AïÏŸr–tŽA€¸D–á E¢ Ò ;’ .‚ì þ7®%©§´òæGyÔ+ìw»Fqs‹°Ó¾}Ž¾#Ž—”€aýK¶ÜÄ (x”÷$ßg…‡«§K‡Wå˜á•ùC>L«Ñç§ÐË#ÏkË—’¨….H”¸¬« ¾²ðPDðBßÅ‚NÊ3¶ØþXqP;¯[V5_ù(¯V….W~h Êv|{Aࢮn²#øÅ(òÖÀÓÓv%}úz$ºí£ëiøó§óþ½ÿ$É"õgÆ‚ êwëgÒ©Ò“ v|Ä>‚¢§ël¨ŸYI³w4%«Æ>t%E/û~4_=pVɵ×_és^:XoÛ”Y–Ô/ :Ý^Úùs}áµ[Ž]oùÙfhŸ¿åËçFìk¯Â2.i†¶ˆð †Óyà­óÐw—"ü7ïGÃ"š†D‚µŠ’$|ñž0UHü •Dm A/ƒ‰ˆ|à7®~±®…²Âè†QÑ$@ª«‡ % 9Ë2¯¼-˜X冦ÏAÂ8ŽöIËÀ º®%f9‹vZì"oÐÂŒ» +0Å~Í‹-i庒©ÚÕP:¾è”U¡Z„‘Ÿi(ˆWÞ3 #ÞóPð‡óZ4ì›.CJì’+jç)îX“©Vz…Ú¨T%ïBéD~%Ĭ=ì•Ø…?G¤@([¯¤½ó7·W*×Ðcò]ýb œ>²úV°¶ÖR]àyzõJj;n‘ÅàÃÑ¥Î,³Ú™E½™ö"bÉ3¤åNNÁ¯3ÙNë×ÔAœìK„Ýžm˜á’µäåp‰èñ%à.ª‘:lšïí¡‡ú RohÈ¢ÂüØ'ç á4xeIc»ë£ýÛ'ôPîÐ?¶ˆ·NÄ"C¢Ÿ“äRߟy/f¸KYŠ¨cÐÆS?©èÊt,²æËé0'Ƚ‰=M½y +ôRÀM±õwKyÃ~Þé±ì›à6Ÿk«=Ћm’Ç•ôˆîÒ”Š|“驨8R2ëhŠô™â¡þ~ªD>ôÓº?%§1ΰÓünÉ›úùw³îB‡Ú]Ã¥t÷€ˆ¹W¿s 9’=v†ÜÊÊÍ /ì‚öìdˆyÙ±ÃÇ +,ð0BÑóA{GlR¤¦L d×" +£3Ý€Ñ)å¹zç¶oì2x ï­‡K\¦\µ«Ôf%ŒN°¾¨Ã0N÷ñ ƒ Ict©xÚ@-wÝ„Ò¬ aXò6PVœo«˜°´QÚh¥Oö=‘ðBWóXŸe¬øcûš‡‘à­Š%5«yˆ<§õ9LMxh-19)˜wÍZxP^À +nJm»IPÇŸ92!…Ñu1=‹Áö^Õ&–U1[ToBÅðÆ “@KAeÊ ËÙ*x•n…gû|‚gå¥Ìk÷³"h…±jŽ¿½”(ðESFoͯõ%ÑÕk LOòÕ"­À÷yÃY¸Œ¿LX\ô¥?ÅP¾²Ý@êFÌþ6î'¹[DyífĨÆjgJg"H•N¯R\ 5ŒvfZå™ʶmM“6{u€JI`ã¤vu¨ƒ^›µå­/ñlË«Øê ç:„!AG[h!haÑ1ê'˜•»5ðCB}Ec®hC¨Bœø°¯ÆÆfŽf#žlPÞû‡­ š›ó­yûù«ÖäÿÔ|XÉ ^K{­)úƒ~â)œbÖ¢y)ú•±„=‡Ôü§7w‘„@B  º)ŠRÄxÿ;' `àôþq=gÚ™½÷LT-è(ÚÆÐb™´0êce¾¯öõYVÜÅУó=ë¥l3?%BOæ ÿâ ¸ÊÝí@^  +æÓj–ãU©_v•e‰É¶RÌ*V|Ó¢œ1µî,¿<ÊOÂ(>SF¿¥6w•"Ô&˜Ô*®1•‡@øü•ìH%0‹WàÏíÒ€h#OP¬NîÔ½Ú9€\vMdD÷Ý Ï–þéï•ÔùîÀâªL Ž@0C Ã΄ý¥1×÷L­Å•uj9ÛÚE~l»V7Q"£ÇL„ÿ2°“/GL0ÇM°¯¥PÜÌ›R5éÏÐÓ¨¾¿láî[/5bS[±Ý"° F£Ù«ŸŠ–0Í87^¥=ä¥f(d2R£÷B?Ëgôpô’7è-Iç×2F6£Zämæmfbjhï¿»~Ǽ‹&ŽQïŸ Ì­Ü,1<Ód1|$‘Þ$ëÌzjleã*3¡G$JÀTš M Îä»6ë²’”Ðl‰ó¤SùÍÏç±¼Äz^”öîé†`:­â_p·À Äý¤ByÖ"—Ïùc’'òlÌ0H“{õ4/Yú7ïK¦¢º@¾€DIáUÕ=ÎöºÃOi*¸–iO\šÆ/† ø1êbx¼Âax(¤ƒ%aeO3yk× ¤5h8U˜¡z¶¶+ÆûºuLM¾E³OŒÃs»#rÑ2f>ŽùYZjZHù@Ñú¸´©9Ók£ç Ür\þ0ªX{BY³à–¤NâO4]ÒúÌzK^Øh†9£%/lH”Ë-‰ŠNýÆËI-yaC"N>£%6$Z/ôÃÌôÊóG™È—[ûî.Lƒ¼D7™ˆÏ'6{ÃØö‘In€Ïê<ý5/-Ñ<Ö‹÷ + Íci³Tø +BM²™tM~PŠy¬%ç4-^y@íë %I¼t9|éçØ~Ûÿ¸“5ñeÆÙ"nUºÒíÍY¹ˆ¼ôàB«Yw·U“Çþ?ŽD^8ÜQ¶^ÿ,aö8Äf–éThýpP‚Âu399ÝÄ£³T”m¹õ§ÕÕðtø‚ÃLÕ)3D/9I °¼ÜnIÄHhü–‘?—kTO{mN_…Ìéb§YZóƒÍò¯V‰â´S«èŠ ³Â‡8ο‘à±?A÷qllù{tÇ6håéèî,.|0ôúîãØÖ_ÿWè& Ú¡zu†"Ÿ¢Ý•›÷&ˆÜ¥^ÎC÷qlûü ºa{aM}+"b¤ ËvÂH}ГÌ~MÕÖls„›í5Ÿµ4PÎœÌ Ÿ<â&@Ý@nÍLfNŠåßZ9Ûq—Xí­â´°ãÁsÈŒ{ÈÏ9¿üÐÝê1tãç²GKSbA¼4,/µ}æn2@!W;?yZ×nb¾·ŠŽ9ë¹\hb¨aë9Ñë;š˜cÇMJþ`ÿ;gá endstream endobj 1107 0 obj <>stream +H‰¬WÙZâJ~B!¤; „@ž°Š¨€ãÈ&¨È8¢àûŸªNÈ¢xt–>0¦»ºêß:j F&ɒЦÉÜiVtó0’ÜÐV +¾-¶©Ã2¿Œû­r–¥üå™ýï2ÊÈ´{t)KÕ è©þ³®Z¡ø6Éú1e)•àˆØDÙš4 xPYéªù0%³»‹¬`ðZA^t}ÍçÖðÔü O/º°‹5‰à#³V—««ïCm¡•)—ndiú<ð2y2å9wU–©pØþXl{9\…WŠ1Î$7߯¦^™á]¡ýŒ,%Ïz‚®W÷9HH÷‹JD¾=é$ÖÑôut>ônä‘–¾+VÒ8™èJ)£dTëòµ »d2[¯›°¨ÍøÖáéxÎ5ÂPmW„¾ ;ͽÞÌân¿Oý¶K‰ˆ}.ý¦²5~¤e·e(Åûõ1´òBƒâšðÏ£»²Ý ¬æ ìÕ£]?Ž”Ó*¬ßD”ep–Y¡ô´éà|5Å8ßÖ>Ú%C£wU:éÜ0œz‘VŒ a§‡ŽÁu2='ƒJGJD»RIõG_YŠð“^$YQJH«6`‰A»(*%ùÛ ¶£®iƒNrjƒX µÇ ÉՈí%p—óƒ0‰¥¸[OR¾åžEi9»ªÉ‡_*œeÓÞ.wQK±¸ª¯x„Àž~ƤååÈ<+Å—Cö>®É·äJ‚³–u¨â$nOC ýJÃIS!:¡£,- Ÿ5øÞ*‘ö†?U˜ÆlCª×S–¶ÑK8P&O+ñMÕÓŸ –Cû,‘—¨Z­R¾žkÁ!Û* ìºAùã{–ŠÑï4áÅÉÔ84í7½+ k”¿ªv¼ÞAÇÜîù‰fs±²Âq§»—Ü$Åžv³ÿ´ëŽªý¡Ul?>l>°Òn?,á)Â{ð²´Lå ¸© P)JþŲÑ[Œˆwy) +8x÷ ì¼Ý?oã.%ÓR'•½ØN-Ÿ.,†ïõçRýÉ‹¬B\[b3Ý${ÊAzñÜþ].Æ•H®y +KÈ^‹LCœ…zׇöÓÃNRa| mKEÞ›La~lüŠG#; =«Ùठr£›Ms½.RÎëP|’÷W“²b>î»Í$cûÓœ•÷늯ø£„lM‡•wý“Ê÷J]ÀÆ€S"“ÛO9D[ÑwRÝ¿ ’+98k¸¯ ¯xÖP`þŠ¸»bÊKL»M³8ÉCñ£&Ò ãz¨B«ô.ÿö¤r4ñ1vq¹£DC?srqf€À§º˜Öÿöi¶pú )­Ui;ƒ½å=ÍfŠ ²Vá™mMägÏÅþ‰+£Lï9èöuôüÃYÜÅ>“÷m{4^èg !~ÄÚFâa%íúYÄÃ":ò#\¾ZH4ÕE½»Ø°Á¿±-Ä‹¾orÓ‹¥Ì–å¡ :f¯¬ò'iÀÁY +F~UŠZ/ -šåeo @‘)ÃSé[P³€»|•˜£D + PP=K€Ÿ7Qap[˜/!&©¼ùþ…%Ô”ðœ+ÉÍJ,Ã`ð±)üCKM “ˬ +6Wö»s^'"9)ì9ø‡W˧åC£ªÊ’´|­¯ɼ–%ªœƒ©.5X;V‘–bìo³½´Áé¡m÷tëñ¯ZÂçŽü‰%Ðø,]l¯)¸oÿ K@¾|` +_²óF”o[u—ÑKÀL~­§Yê[3ØãRÊ]içß{ÒýSÖ@åäƬ—ÿª8:ätÀ|ÕžÅ>ó€ïßT•ÏóìØΔb0Ïåï3ÌþÐòÐE=éü¤£ä;Kè ê»VI"^ÀΞ%€Ú7Éì9”‹ÆÍtÇ[Ö6ÊÏã%:~4Úïz785¥û[èÒÚ6/xaJ^LÇIWXð¥èÈ«Ì0xòbÖQá…'½ò<Ú»¬ù«¦¼Ø¬8ŸþJ©tÌËÄâmÒ¸ðŽÑX³ˆÄáþraPþuH'G|`±¼è,ÖSŽƒšÅ49æ¾;s@ê]RþÒ—œ[’?­_ã~±#ºØˆÚ¦öŽ (žì²HA«ò”ÿ9J8v¶†>^»*ˆ!¡YŒ*‘þcï iû)xE†^o£ªc ·lV0ýÀ!<¾ÔÛX•U@Ѧ…PèØz¬ü¾9àôßÙƒ‚ N4QNµÿ¿1`µè ù +Èöµú9Üg™&Ãáàn¤7´¤ÿŠ0}Î yw⌤6æäÛr"ç*ÑÊÖEeÎfS1§–À:[ìbÞŠ¯k;>i*à`Ú •yI6É=lš[Im@:lœ‚…³äæû(ÑjTMökQ‚stsÚ+¹ +š€µ»O0ƒYÕ/Ç_"xæ›×ê ?Óª­D‹‡ÅãA¸z³zV¿xíá8=ñ,¿BYq´z(W‰Øç\Y!¸”ˆÓ·{´˜¿xgaƒ÷ÞÞÁ…×1[š¯.ŒÜlå¤qíúq ¤Ñ*l+"–ÃYcÒ¯Îb)®”T¾ÀÝg„dtdþò#¾˜-£&+ŸòLqP‰‰#”M@ZӤ÷ñlÑ%€—ðiá]ÌM)‘åyÄåNGØ$Ù½óŸÄ³*t¬‹—Y|ùœ»x6ÚÐߊgè/€öBó"š 䣳_twŠuÓ‘–fYc™ørsTq J¨Pc-UÚûì ÃÌØLFYÓÁ°TF?ÓñÖEhìP­1{sÒø>ƒ³k”N¢ €¤¢°#iJôü:üá©!YRúKBN:UëCB²¥0õ}DH÷í¿!$£ãAø!×eã=!­?%$ËDÀ—ûÛhÉßCÈ'$ñCÄå<Òot¢Ís»D…–Xª˜¨R Äh˜ +;çÈJ±PcÖ*7£ÃœÏ&oËC„ÚD’AÒú’§ï§ib$S1;LüC’‚Žýï-* OZââÜ„ÅF¨r\óSey ¬›ÕDx ¾M‘pãcÎèNÊ©Ìv¾É¼“̽²Çr“Œò¿ç{v†±c5dÆ“×R$Z«ÒváÎÀÒYÊv.Ål9©7·5+œ&&Èl½ÆüAx³˜{i€ÝÎâ~L#LÝ·½ë¦Ì#_$]l{9Ÿcüîˆs e +„š±Sè~ëº62BQ6ŒUË„Ö01Èà]ìÁ:½©U  =6”Ý€InÖL€rÆT×®AÙÌj¥§q æ3­l¸åôböÏN1¢v…ÍOQUâù¬nsl¸²LMtVð· ’:Kp¿'üÁé Í/ßßœéSÏ:±äç†í «òAA2N¾¥»ÏUˆQº+M6–cS(–êW@´nk—‹‰ÝQ°xÈIch×@¬ªÅw§I\¸+ Ê3T¸“ÄÆØž1Ѷ”Xf7̧Sh¯+ àØ©ÕÁYH}þcetAÐRj縷×å& +ä!jƒ°Û6h”—£ +4VµË¸Ê$ŽÎª#“ âSït» %%èµ,ÕŸˆÚ³r̾UŽÐ¦íüþ;r.OTRK¹ý_ ¤t(ЪDÝ£ïõ;è¤vú?öÀ TéQM~)éÂõQÿ7=Y:òqµß<(=y@^ +dÇ¢Þ/„|—x_PÂî2V—5ð–ÀªJ-²vÄ +yÑ¿-²f¹ ]_¡—Hå^Š†„='`{*̱ˆØ/¡„úÀÏž%ôo°Ûôb +|òÊèö~~ëjÐæ¸WÅ·–±ÀrõXë¼Æ<0$ëö~=ñUÈ—Jõ-${Æ“‘¬2+Øw…Ú¨&ªó¾³ÀÓ 9ÿ/`íWDz©©ÿÈôÂSúÑYP©ÉW~¶j>©VªzÎ38 õz©'œ%‰^X&TôL!íi¤·WÍŒVƒè6hÅöüNìKÁze¸Ô$|–K·ðAÇÌ¢bw)[/¢9çRNÙüJ™u|KÂŽº·¶`Uéåüj +V¹ñ*ê­Æ.b×2]ÝfWþ‚çzFR½ñѵ´Ï¾ÚcJ%@Nåü¸¼ƒxÊ^<Ï#ªtéQŽÖ)¡™\Eõf¸–‰ÝY\}¡ZrÝ+†ð·s̼.ªëL¥Õê»ô +rÛñÔÆ»EQ´ŸF}dËbw0ß-kíó£z„\ÍšA4YàëímÏj +ÜàÁÑtÌIX+Þr%Ýañß@¬@Þ¦ÅÕ’¼2³Nwˆ,³íì"©#‘»5ºu*/¹b½¸!·åÑ- +SƒWÄm$Ê>~“_MÙù£"nÚ&ÚϨb™%~©‡YW`çlÂ^Hð”4(‡Õ øŠ»ãFU·—Š2µp¿íöBUuèÏ—Øh9úË%CÀ/ÃWzô·KEÉ…l‚†KU… R]D„/þºÛf*h¢7qš}ôŠH´~Q›²ù¶µ¥rQ•ñKðön*øüPƒŸ1ºm[aA/sÙÊç“9Û«Ê8“𕜖$íÔeòÌùä¤Ç=枣ÅzvvrtBQ§ÖšCP°ý‚† ¼ÃNCù(žÇÿ÷›*¥Êf:‘Ë4É~î9VYf®3óVS0{Ë÷€É–Ú/¶]‡zõéü«¢“°4W2øå°‹ Õ›[Wï n¨äW<¾3ÌvC&ªÖ\úÝÝÒí¨3ëÙƒ™Rþ,{,z¤}GöPÌXTÝÎP»@Ú7‘;½þ@ö¾o¹¤ç°OþC—Äùä?tI0ÃC>ù]Òj·ßù¤3`NÛM˜æÖ;\;}ó`AdÝ ò‚éà+ô"ÐÇ56«Up} ù}LÙù­®Ñ3 žl­—£“ +úJâ³a×N€‹½ÈÅÂ|I›™WuìŸçW«ôª5®Õ­V)éSIVÖ3ÈÆ;!×0áYôˆñg}y;À(°}%«P!´b»¤9#V–ˆ9²9Ÿ+/z³W DóÓ~vóf?묔—ú\p»OY\äÉ©ÃÜ’ƒáRÉ׆ª´ÐF GÖBrÖ¬QØYÕ hÛO±k¢­?{ü¼Ñâ "¡K˜T ¼Ä±§·3T§9+¡X} +çko![ }ÔK>:ªŸ£ÁЋvÓq“ƒZTáœsk3 ¥½€²s‹qÂe‡¾Æ©pÉÚ …|_[:ôh‚O—œ}®Kƒû¡A˜ž,mŒ ïO1}‚°âB€v3u!€î¡#ÙL>Âib:·Aàý; =—ÈG1$%4 ýAHolöo&¼¾ |…§‰‹ !v–«àji`ÞËÞßMY:Èu2x8¶QLhŠ4‘èÿ»¿'ì·B¼ °žr{<þï…€*ϤˆÏõÝBó +?:Æc‚? +°§ÊOCädéûÇÀpöQˆjXþÙ1š Å%Õ¥€É_ Ñ3Ôƒ!¾ ŽÇÏìOÔ­‰êœû<T˜?„ˆoøŸí!ç Z~â‹ÙÐĪ ~a8û‡vÑÔ¤?ecª¹B·3í„ЋY”ˆý ©p7óUVí¿ ±Ì©„ò3Ë“|e X{KfÖ­–Ñ#þvz£Þ„ÆŠ#okî{þp-ߧ߫zyYì{YËŽ}L©'%¿‡³[z +K.Çþ*9¥eâ+%ƒæØ~Óg(?:†)U›cW443ZÕ¾º±ý] {þŸ)~¸Ô¨tRŽüõ#_Lú™#k#)-ÿHñ£hn_®_tdh›U%¢±ÁlG+ã?îô¼W¬&LcǨŸîÉ*³Â’3%=b…›¶‰®û0¿Í¬‰WÐK•€@f?´œÙL†ùؾÒX<«Y+ãñÄíWúÍHZÄ·0ñeÛ·0¡Er²ýǢ⠒*=†ýŸöjíŠG¢¿ÀÿÁ»2Ýapë-9™!!ç$KïÌIØôvL÷œ¦s€/ûÛ·J¶e¹Ë9™–>ÝØWRUI÷ªTúñÓQè‹aÜ/'¡a=n(Ö}ƒgÿÅFÜôÁNØQÜðçöihX¹âÁÅowönð±éü +n¯qÓ©: M™¿'àÖdx£½ƒ{ 5ýŠW·Îç÷ñu/¶ýñtWv¯¾˜‚ï77¾ÍŸ^Îý党ؿ¹{Îîâ²íÕf÷Þy³ðóÃÏ?àz¯ÃEk±æcà …¢y-‹>|ô½<À[ÒÛåþã2;½·õæø÷bçÅîßz®¡Ûhl­û³ßÞcÅðùÓŸ½Å>{-¿Ãâ^ñK°øX/ßÜ™>{ÿ«;Ý:ø|çìɯ'oΊ·ê½ÿüáÑöÙdÄÙè_d#¯·*šúÎC{Üv¸áPììmcz85²?Ûßã…ëÖxó êêP`Ôs¯±CéŸ}´xmÐõ ŒÁüFw‘ƒA3ðå¬Vüë齊’9¸Wo3Ћxñi¯‡ÌŸL’5‰Å˪ãùÀuûOøz¯rôHìlü¶î7ZØM^9~¯mæÏ^ëY={1äOÞ_<ó[Þ½ñ¼Üm~&ãhÚeÐ4Þlx·þñ ›j€÷7ÈrƒÁýz7ïûi ø“'¿ù€q/nT›ÐK÷_²íÀMûÏçÛÿþcëDÞÉÿ(—Ÿ=Å2áSØCCÙ«d§}VÏïÙ£uLk/Õ…¼³ë~ñßÍÔ»µËùÑ“ÙÉÛéÕrg~üù|:[&kk€L—ŸÿÄúèñôôlVL®§‹„§Õ‡ÁmžráR¡5¼hD‹ÉÀ÷Mù0-fÉÚÑhk±Ü9;^žÍg“Åuz¡ñËâÝîNz?­úAßé¢aGК†)ä‰#ˆð(aé|Ǘ𳟰Lç†Y——Vh|`Ú9øUá;8]L¯‡>Ðñ8auÔ`é^þÿÁ!:½LUú2ýí=KOÐÏ~bȸPé†t.“Ö¦ç‰Q+PAºÚ5TR[Er^^ÃD˜’0ˆá=ôN#Ëq"W@ÁebÈ`é78ÏÁ:<'R¹ÊQÀŠsjF6HImÉïÉ…¢ºX:zº˜NßÍNæ@Š›œOwg'Ó+x×éè`9_\7ï’Ä/òIbÒÁ0~ƒ-v‹¶øŠ-T! ÿ-[&ÜÉt„“ «l£×¿:¾_ûu™%ãõäA:ÚžÏ~?›ÍNßL–Ÿn4põ¥¨¥à™”‚`BPÈ9Ïí +V´˜:Œê¾Eýÿú4®Û¤«ÙÊþÙZ²‹ZH4P;P4‹ØBÙò*DLÒAŒ†d¹cþ[£|ÐLæ¢?WÀc•- 1“ÃVÓŒgÎä¸ç23¹’qÔ[ [1)}ÜÍà^0}‘T¹¶£3%n^¶v-û&_ílùE¨xÑÅ ï;ž·V$·@8æ*#ëuÊ4ÉJóU¬ˆ0°vlƒ•=ö¾-a™[L2 ¢Û3ænÑV~›“\M¥7JË·9KùU[ÿ‡„`dØÓLfLX :`AëÌJ#½ ›Á½`4úâ[µG†¸õ¾Ÿ¥¯¯l”|†…o +1^/qÿ’Z]e››ºdÑJ©ñ6.s6ÆŠ“2ÖŽm°²ÇfV—}=¡ð*ŽÛÒfï\e¦ +AWaUS⬈0ÈfZÕSjÆ6XÙc¯H>â’‹\ë¾o“³ÍáÏOvðjz™nÏËù"=¸œ,?a5Îâ¤Ì³\1¬ÉY +gTªLæÜ`¤BË_ëá]ÖE×=[›<³ÆŸÂ‰wdª÷ª0ÀåÇ¿!JrJ:xüxëî*ûóå»Fw…oøj©u£¬v›U0¿ÍŠÛÛ4öõ“ Î7§1X¤¢äA¹6~ a‡…+‡K—QÒ vDá¡»X=¶ƒ™L;^aÁl+“ãV£~¿¬É (—×¹üK¢l-ÃU‹ÌÈq:#Ç茚±LÓ9EgÔŽ½ÝNÆ”Bä’¹œ÷U÷p Bñg'f"Ÿ±d©ªÖ¬,Ó¹aÖ¢ý±†Í©Ö?Ÿnþ7d*F‘©´P¸á´à ‡ØÂÔÿ ›µ§¼QQðõ„yóͯ-œôpÉe¸*¨.R´ ˆí"%±ƒÂo0¨RV¨ˆ ±j>@%µÕqÀ-qÐ@‘®ˆƒ*©­Ž‘ 9†8†8hF ¥®:h ÈAu«ì8h ’Úê8ЄàE4¡8@%µ…¸# +ÂŒ¸"†b-DDDluD2 +æ"9D˜ >"%Q{]/­–Z‹­&"LQ/­œ¨½®—VP­ÅVf¨—VSÔ^×K«ªÖb«ŽkuÕŽm…Eíu½hÊ|$‘£ÜGê¢öº^Lû¦‡}Óþéaßô²o{Ø·=ìÛömû¶—}×þëaßõ°ïzØw})ÅÒ¤hiR´4)Zšm_Rt4):šMŠŽ&žF’b€"9MŠ9IŠÑÀÈ'I1@E‘¤ ’Úê8„ƒE$á @%µÕq Š(ÂA€Jj«ãÀ‘³;@‘GÎî•ÔVÇAN—(§K”Ó%Êéå}Kd¨Š Q‘ÐDE*©­Øã«TDá @%µÕq È 9àd*©­ŽEHPä@’TR[š È&$¨¤¶: åÀP åÀPLŽrà(–r`)®‡E‹dE‹dE‹dE‹dÕW$+Z$+Z$+Z$+Z$«¾"YÑ"YÑ"YÑ"YÑ"YõÉŠÉŠÉŠÉŠɪ¯HV´HV´HV´HV´HVºçôYºŸ K¹â"åðaS’K 6‡¿”Ágðjz™nÏËù"=¸œ,?¥|0OÇãD)5°ÌŒ1žÑº}-’ƒ¦ÝÖíÚ¸öÛ58†ü(q¬È«GÄG[»œ¿^-Óûåèéb:}7;™§?Uo“óéîìdzå|qÝ’£Ÿ$& Óñ!¼¾ÆiJ+ > endobj xref 0 1130 0000000003 65535 f +0000000016 00000 n +0000028251 00000 n +0000000004 00000 f +0000000006 00000 f +0000028302 00000 n +0000000007 00000 f +0000000008 00000 f +0000000009 00000 f +0000000010 00000 f +0000000011 00000 f +0000000012 00000 f +0000000013 00000 f +0000000014 00000 f +0000000015 00000 f +0000000016 00000 f +0000000017 00000 f +0000000018 00000 f +0000000019 00000 f +0000000020 00000 f +0000000021 00000 f +0000000022 00000 f +0000000023 00000 f +0000000024 00000 f +0000000025 00000 f +0000000026 00000 f +0000000027 00000 f +0000000028 00000 f +0000000029 00000 f +0000000030 00000 f +0000000031 00000 f +0000000032 00000 f +0000000033 00000 f +0000000034 00000 f +0000000035 00000 f +0000000036 00000 f +0000000037 00000 f +0000000038 00000 f +0000000039 00000 f +0000000040 00000 f +0000000041 00000 f +0000000042 00000 f +0000000043 00000 f +0000000044 00000 f +0000000045 00000 f +0000000046 00000 f +0000000047 00000 f +0000000048 00000 f +0000000049 00000 f +0000000050 00000 f +0000000051 00000 f +0000000052 00000 f +0000000053 00000 f +0000000054 00000 f +0000000055 00000 f +0000000056 00000 f +0000000057 00000 f +0000000058 00000 f +0000000059 00000 f +0000000060 00000 f +0000000061 00000 f +0000000062 00000 f +0000000063 00000 f +0000000064 00000 f +0000000065 00000 f +0000000066 00000 f +0000000067 00000 f +0000000068 00000 f +0000000069 00000 f +0000000070 00000 f +0000000071 00000 f +0000000072 00000 f +0000000073 00000 f +0000000074 00000 f +0000000075 00000 f +0000000076 00000 f +0000000077 00000 f +0000000078 00000 f +0000000079 00000 f +0000000080 00000 f +0000000081 00000 f +0000000082 00000 f +0000000083 00000 f +0000000084 00000 f +0000000085 00000 f +0000000086 00000 f +0000000087 00000 f +0000000088 00000 f +0000000089 00000 f +0000000090 00000 f +0000000091 00000 f +0000000092 00000 f +0000000093 00000 f +0000000094 00000 f +0000000095 00000 f +0000000096 00000 f +0000000097 00000 f +0000000098 00000 f +0000000099 00000 f +0000000100 00000 f +0000000101 00000 f +0000000102 00000 f +0000000103 00000 f +0000000104 00000 f +0000000105 00000 f +0000000106 00000 f +0000000107 00000 f +0000000108 00000 f +0000000109 00000 f +0000000110 00000 f +0000000111 00000 f +0000000112 00000 f +0000000113 00000 f +0000000114 00000 f +0000000115 00000 f +0000000116 00000 f +0000000117 00000 f +0000000118 00000 f +0000000119 00000 f +0000000120 00000 f +0000000121 00000 f +0000000122 00000 f +0000000123 00000 f +0000000124 00000 f +0000000125 00000 f +0000000126 00000 f +0000000127 00000 f +0000000128 00000 f +0000000129 00000 f +0000000130 00000 f +0000000131 00000 f +0000000132 00000 f +0000000133 00000 f +0000000134 00000 f +0000000135 00000 f +0000000136 00000 f +0000000137 00000 f +0000000138 00000 f +0000000139 00000 f +0000000140 00000 f +0000000141 00000 f +0000000142 00000 f +0000000143 00000 f +0000000144 00000 f +0000000145 00000 f +0000000146 00000 f +0000000147 00000 f +0000000148 00000 f +0000000149 00000 f +0000000150 00000 f +0000000151 00000 f +0000000152 00000 f +0000000153 00000 f +0000000154 00000 f +0000000155 00000 f +0000000156 00000 f +0000000157 00000 f +0000000158 00000 f +0000000159 00000 f +0000000160 00000 f +0000000161 00000 f +0000000162 00000 f +0000000163 00000 f +0000000164 00000 f +0000000165 00000 f +0000000166 00000 f +0000000167 00000 f +0000000168 00000 f +0000000169 00000 f +0000000170 00000 f +0000000171 00000 f +0000000172 00000 f +0000000173 00000 f +0000000174 00000 f +0000000175 00000 f +0000000176 00000 f +0000000177 00000 f +0000000178 00000 f +0000000179 00000 f +0000000180 00000 f +0000000181 00000 f +0000000182 00000 f +0000000183 00000 f +0000000184 00000 f +0000000185 00000 f +0000000186 00000 f +0000000187 00000 f +0000000188 00000 f +0000000189 00000 f +0000000190 00000 f +0000000191 00000 f +0000000192 00000 f +0000000193 00000 f +0000000194 00000 f +0000000195 00000 f +0000000196 00000 f +0000000197 00000 f +0000000198 00000 f +0000000199 00000 f +0000000200 00000 f +0000000201 00000 f +0000000202 00000 f +0000000203 00000 f +0000000204 00000 f +0000000205 00000 f +0000000206 00000 f +0000000207 00000 f +0000000208 00000 f +0000000209 00000 f +0000000210 00000 f +0000000211 00000 f +0000000212 00000 f +0000000213 00000 f +0000000214 00000 f +0000000215 00000 f +0000000216 00000 f +0000000217 00000 f +0000000218 00000 f +0000000219 00000 f +0000000220 00000 f +0000000221 00000 f +0000000222 00000 f +0000000223 00000 f +0000000224 00000 f +0000000225 00000 f +0000000226 00000 f +0000000227 00000 f +0000000228 00000 f +0000000229 00000 f +0000000230 00000 f +0000000231 00000 f +0000000232 00000 f +0000000233 00000 f +0000000234 00000 f +0000000235 00000 f +0000000236 00000 f +0000000237 00000 f +0000000238 00000 f +0000000239 00000 f +0000000240 00000 f +0000000241 00000 f +0000000242 00000 f +0000000243 00000 f +0000000244 00000 f +0000000245 00000 f +0000000246 00000 f +0000000247 00000 f +0000000248 00000 f +0000000249 00000 f +0000000250 00000 f +0000000251 00000 f +0000000252 00000 f +0000000253 00000 f +0000000254 00000 f +0000000255 00000 f +0000000256 00000 f +0000000257 00000 f +0000000258 00000 f +0000000259 00000 f +0000000260 00000 f +0000000261 00000 f +0000000262 00000 f +0000000263 00000 f +0000000264 00000 f +0000000265 00000 f +0000000266 00000 f +0000000267 00000 f +0000000268 00000 f +0000000269 00000 f +0000000270 00000 f +0000000271 00000 f +0000000272 00000 f +0000000273 00000 f +0000000274 00000 f +0000000275 00000 f +0000000276 00000 f +0000000277 00000 f +0000000278 00000 f +0000000279 00000 f +0000000280 00000 f +0000000281 00000 f +0000000282 00000 f +0000000283 00000 f +0000000284 00000 f +0000000285 00000 f +0000000286 00000 f +0000000287 00000 f +0000000288 00000 f +0000000289 00000 f +0000000290 00000 f +0000000291 00000 f +0000000292 00000 f +0000000293 00000 f +0000000294 00000 f +0000000295 00000 f +0000000296 00000 f +0000000297 00000 f +0000000298 00000 f +0000000299 00000 f +0000000300 00000 f +0000000301 00000 f +0000000302 00000 f +0000000303 00000 f +0000000304 00000 f +0000000305 00000 f +0000000306 00000 f +0000000307 00000 f +0000000308 00000 f +0000000309 00000 f +0000000310 00000 f +0000000311 00000 f +0000000312 00000 f +0000000313 00000 f +0000000314 00000 f +0000000315 00000 f +0000000316 00000 f +0000000317 00000 f +0000000318 00000 f +0000000319 00000 f +0000000320 00000 f +0000000321 00000 f +0000000322 00000 f +0000000323 00000 f +0000000324 00000 f +0000000325 00000 f +0000000326 00000 f +0000000327 00000 f +0000000328 00000 f +0000000329 00000 f +0000000330 00000 f +0000000331 00000 f +0000000332 00000 f +0000000333 00000 f +0000000334 00000 f +0000000335 00000 f +0000000336 00000 f +0000000337 00000 f +0000000338 00000 f +0000000339 00000 f +0000000340 00000 f +0000000341 00000 f +0000000342 00000 f +0000000343 00000 f +0000000344 00000 f +0000000345 00000 f +0000000346 00000 f +0000000347 00000 f +0000000348 00000 f +0000000349 00000 f +0000000350 00000 f +0000000351 00000 f +0000000352 00000 f +0000000353 00000 f +0000000354 00000 f +0000000355 00000 f +0000000356 00000 f +0000000357 00000 f +0000000358 00000 f +0000000359 00000 f +0000000360 00000 f +0000000361 00000 f +0000000362 00000 f +0000000363 00000 f +0000000364 00000 f +0000000365 00000 f +0000000366 00000 f +0000000367 00000 f +0000000368 00000 f +0000000369 00000 f +0000000370 00000 f +0000000371 00000 f +0000000372 00000 f +0000000373 00000 f +0000000374 00000 f +0000000375 00000 f +0000000376 00000 f +0000000377 00000 f +0000000378 00000 f +0000000379 00000 f +0000000380 00000 f +0000000381 00000 f +0000000382 00000 f +0000000383 00000 f +0000000384 00000 f +0000000385 00000 f +0000000386 00000 f +0000000387 00000 f +0000000388 00000 f +0000000389 00000 f +0000000390 00000 f +0000000391 00000 f +0000000392 00000 f +0000000393 00000 f +0000000394 00000 f +0000000395 00000 f +0000000396 00000 f +0000000397 00000 f +0000000398 00000 f +0000000399 00000 f +0000000400 00000 f +0000000401 00000 f +0000000402 00000 f +0000000403 00000 f +0000000404 00000 f +0000000405 00000 f +0000000406 00000 f +0000000407 00000 f +0000000408 00000 f +0000000409 00000 f +0000000410 00000 f +0000000411 00000 f +0000000412 00000 f +0000000413 00000 f +0000000414 00000 f +0000000415 00000 f +0000000416 00000 f +0000000417 00000 f +0000000418 00000 f +0000000419 00000 f +0000000420 00000 f +0000000421 00000 f +0000000422 00000 f +0000000423 00000 f +0000000424 00000 f +0000000425 00000 f +0000000426 00000 f +0000000427 00000 f +0000000428 00000 f +0000000429 00000 f +0000000430 00000 f +0000000431 00000 f +0000000432 00000 f +0000000433 00000 f +0000000434 00000 f +0000000435 00000 f +0000000436 00000 f +0000000437 00000 f +0000000438 00000 f +0000000439 00000 f +0000000440 00000 f +0000000441 00000 f +0000000442 00000 f +0000000443 00000 f +0000000444 00000 f +0000000445 00000 f +0000000446 00000 f +0000000447 00000 f +0000000448 00000 f +0000000449 00000 f +0000000450 00000 f +0000000451 00000 f +0000000452 00000 f +0000000453 00000 f +0000000454 00000 f +0000000455 00000 f +0000000456 00000 f +0000000457 00000 f +0000000458 00000 f +0000000459 00000 f +0000000460 00000 f +0000000461 00000 f +0000000462 00000 f +0000000463 00000 f +0000000464 00000 f +0000000465 00000 f +0000000466 00000 f +0000000467 00000 f +0000000468 00000 f +0000000469 00000 f +0000000470 00000 f +0000000471 00000 f +0000000472 00000 f +0000000473 00000 f +0000000474 00000 f +0000000475 00000 f +0000000476 00000 f +0000000477 00000 f +0000000478 00000 f +0000000479 00000 f +0000000480 00000 f +0000000481 00000 f +0000000482 00000 f +0000000483 00000 f +0000000484 00000 f +0000000485 00000 f +0000000486 00000 f +0000000487 00000 f +0000000488 00000 f +0000000489 00000 f +0000000490 00000 f +0000000491 00000 f +0000000492 00000 f +0000000493 00000 f +0000000494 00000 f +0000000495 00000 f +0000000496 00000 f +0000000497 00000 f +0000000498 00000 f +0000000499 00000 f +0000000500 00000 f +0000000501 00000 f +0000000502 00000 f +0000000503 00000 f +0000000504 00000 f +0000000505 00000 f +0000000506 00000 f +0000000507 00000 f +0000000508 00000 f +0000000509 00000 f +0000000510 00000 f +0000000511 00000 f +0000000512 00000 f +0000000513 00000 f +0000000514 00000 f +0000000515 00000 f +0000000516 00000 f +0000000517 00000 f +0000000518 00000 f +0000000519 00000 f +0000000520 00000 f +0000000521 00000 f +0000000522 00000 f +0000000523 00000 f +0000000524 00000 f +0000000525 00000 f +0000000526 00000 f +0000000527 00000 f +0000000528 00000 f +0000000529 00000 f +0000000530 00000 f +0000000531 00000 f +0000000532 00000 f +0000000533 00000 f +0000000534 00000 f +0000000535 00000 f +0000000536 00000 f +0000000537 00000 f +0000000538 00000 f +0000000539 00000 f +0000000540 00000 f +0000000541 00000 f +0000000542 00000 f +0000000543 00000 f +0000000544 00000 f +0000000545 00000 f +0000000546 00000 f +0000000547 00000 f +0000000548 00000 f +0000000549 00000 f +0000000550 00000 f +0000000551 00000 f +0000000552 00000 f +0000000553 00000 f +0000000554 00000 f +0000000555 00000 f +0000000556 00000 f +0000000557 00000 f +0000000558 00000 f +0000000559 00000 f +0000000560 00000 f +0000000561 00000 f +0000000562 00000 f +0000000563 00000 f +0000000564 00000 f +0000000565 00000 f +0000000566 00000 f +0000000567 00000 f +0000000568 00000 f +0000000569 00000 f +0000000570 00000 f +0000000571 00000 f +0000000572 00000 f +0000000573 00000 f +0000000574 00000 f +0000000575 00000 f +0000000576 00000 f +0000000577 00000 f +0000000578 00000 f +0000000579 00000 f +0000000580 00000 f +0000000581 00000 f +0000000582 00000 f +0000000583 00000 f +0000000584 00000 f +0000000585 00000 f +0000000586 00000 f +0000000587 00000 f +0000000588 00000 f +0000000589 00000 f +0000000590 00000 f +0000000591 00000 f +0000000592 00000 f +0000000593 00000 f +0000000594 00000 f +0000000595 00000 f +0000000596 00000 f +0000000597 00000 f +0000000598 00000 f +0000000599 00000 f +0000000600 00000 f +0000000601 00000 f +0000000602 00000 f +0000000603 00000 f +0000000604 00000 f +0000000605 00000 f +0000000606 00000 f +0000000607 00000 f +0000000608 00000 f +0000000609 00000 f +0000000610 00000 f +0000000611 00000 f +0000000612 00000 f +0000000613 00000 f +0000000614 00000 f +0000000615 00000 f +0000000616 00000 f +0000000617 00000 f +0000000618 00000 f +0000000619 00000 f +0000000620 00000 f +0000000621 00000 f +0000000622 00000 f +0000000623 00000 f +0000000624 00000 f +0000000625 00000 f +0000000626 00000 f +0000000627 00000 f +0000000628 00000 f +0000000629 00000 f +0000000630 00000 f +0000000631 00000 f +0000000632 00000 f +0000000633 00000 f +0000000634 00000 f +0000000635 00000 f +0000000636 00000 f +0000000637 00000 f +0000000638 00000 f +0000000639 00000 f +0000000640 00000 f +0000000641 00000 f +0000000642 00000 f +0000000643 00000 f +0000000644 00000 f +0000000645 00000 f +0000000646 00000 f +0000000647 00000 f +0000000648 00000 f +0000000649 00000 f +0000000650 00000 f +0000000651 00000 f +0000000652 00000 f +0000000653 00000 f +0000000654 00000 f +0000000655 00000 f +0000000656 00000 f +0000000657 00000 f +0000000658 00000 f +0000000659 00000 f +0000000660 00000 f +0000000661 00000 f +0000000662 00000 f +0000000663 00000 f +0000000664 00000 f +0000000665 00000 f +0000000666 00000 f +0000000667 00000 f +0000000668 00000 f +0000000669 00000 f +0000000670 00000 f +0000000671 00000 f +0000000672 00000 f +0000000673 00000 f +0000000674 00000 f +0000000675 00000 f +0000000676 00000 f +0000000677 00000 f +0000000678 00000 f +0000000679 00000 f +0000000680 00000 f +0000000681 00000 f +0000000682 00000 f +0000000683 00000 f +0000000684 00000 f +0000000685 00000 f +0000000686 00000 f +0000000687 00000 f +0000000688 00000 f +0000000689 00000 f +0000000690 00000 f +0000000691 00000 f +0000000692 00000 f +0000000693 00000 f +0000000694 00000 f +0000000695 00000 f +0000000696 00000 f +0000000697 00000 f +0000000698 00000 f +0000000699 00000 f +0000000700 00000 f +0000000701 00000 f +0000000702 00000 f +0000000703 00000 f +0000000704 00000 f +0000000705 00000 f +0000000706 00000 f +0000000707 00000 f +0000000708 00000 f +0000000709 00000 f +0000000710 00000 f +0000000711 00000 f +0000000712 00000 f +0000000713 00000 f +0000000714 00000 f +0000000715 00000 f +0000000716 00000 f +0000000717 00000 f +0000000718 00000 f +0000000719 00000 f +0000000720 00000 f +0000000721 00000 f +0000000722 00000 f +0000000723 00000 f +0000000724 00000 f +0000000725 00000 f +0000000726 00000 f +0000000727 00000 f +0000000728 00000 f +0000000729 00000 f +0000000730 00000 f +0000000731 00000 f +0000000732 00000 f +0000000733 00000 f +0000000734 00000 f +0000000735 00000 f +0000000736 00000 f +0000000737 00000 f +0000000738 00000 f +0000000739 00000 f +0000000740 00000 f +0000000741 00000 f +0000000742 00000 f +0000000743 00000 f +0000000744 00000 f +0000000745 00000 f +0000000746 00000 f +0000000747 00000 f +0000000748 00000 f +0000000749 00000 f +0000000750 00000 f +0000000751 00000 f +0000000752 00000 f +0000000753 00000 f +0000000754 00000 f +0000000755 00000 f +0000000756 00000 f +0000000757 00000 f +0000000758 00000 f +0000000759 00000 f +0000000760 00000 f +0000000761 00000 f +0000000762 00000 f +0000000763 00000 f +0000000764 00000 f +0000000765 00000 f +0000000766 00000 f +0000000767 00000 f +0000000768 00000 f +0000000769 00000 f +0000000770 00000 f +0000000771 00000 f +0000000772 00000 f +0000000773 00000 f +0000000774 00000 f +0000000775 00000 f +0000000776 00000 f +0000000777 00000 f +0000000778 00000 f +0000000779 00000 f +0000000780 00000 f +0000000781 00000 f +0000000782 00000 f +0000000783 00000 f +0000000784 00000 f +0000000785 00000 f +0000000786 00000 f +0000000787 00000 f +0000000788 00000 f +0000000789 00000 f +0000000790 00000 f +0000000791 00000 f +0000000792 00000 f +0000000793 00000 f +0000000794 00000 f +0000000795 00000 f +0000000796 00000 f +0000000797 00000 f +0000000798 00000 f +0000000799 00000 f +0000000800 00000 f +0000000801 00000 f +0000000802 00000 f +0000000803 00000 f +0000000804 00000 f +0000000805 00000 f +0000000806 00000 f +0000000807 00000 f +0000000808 00000 f +0000000809 00000 f +0000000810 00000 f +0000000811 00000 f +0000000812 00000 f +0000000813 00000 f +0000000814 00000 f +0000000815 00000 f +0000000816 00000 f +0000000817 00000 f +0000000818 00000 f +0000000819 00000 f +0000000820 00000 f +0000000821 00000 f +0000000822 00000 f +0000000823 00000 f +0000000824 00000 f +0000000825 00000 f +0000000826 00000 f +0000000827 00000 f +0000000828 00000 f +0000000829 00000 f +0000000830 00000 f +0000000831 00000 f +0000000832 00000 f +0000000833 00000 f +0000000834 00000 f +0000000835 00000 f +0000000836 00000 f +0000000837 00000 f +0000000838 00000 f +0000000839 00000 f +0000000840 00000 f +0000000841 00000 f +0000000842 00000 f +0000000843 00000 f +0000000844 00000 f +0000000845 00000 f +0000000846 00000 f +0000000847 00000 f +0000000848 00000 f +0000000849 00000 f +0000000850 00000 f +0000000851 00000 f +0000000852 00000 f +0000000853 00000 f +0000000854 00000 f +0000000855 00000 f +0000000856 00000 f +0000000857 00000 f +0000000858 00000 f +0000000859 00000 f +0000000860 00000 f +0000000861 00000 f +0000000862 00000 f +0000000863 00000 f +0000000864 00000 f +0000000865 00000 f +0000000866 00000 f +0000000867 00000 f +0000000868 00000 f +0000000869 00000 f +0000000870 00000 f +0000000871 00000 f +0000000872 00000 f +0000000873 00000 f +0000000874 00000 f +0000000875 00000 f +0000000876 00000 f +0000000877 00000 f +0000000878 00000 f +0000000879 00000 f +0000000880 00000 f +0000000881 00000 f +0000000882 00000 f +0000000883 00000 f +0000000884 00000 f +0000000885 00000 f +0000000886 00000 f +0000000887 00000 f +0000000888 00000 f +0000000889 00000 f +0000000890 00000 f +0000000891 00000 f +0000000892 00000 f +0000000893 00000 f +0000000894 00000 f +0000000895 00000 f +0000000896 00000 f +0000000897 00000 f +0000000898 00000 f +0000000899 00000 f +0000000900 00000 f +0000000901 00000 f +0000000902 00000 f +0000000903 00000 f +0000000904 00000 f +0000000905 00000 f +0000000906 00000 f +0000000907 00000 f +0000000908 00000 f +0000000909 00000 f +0000000910 00000 f +0000000911 00000 f +0000000912 00000 f +0000000913 00000 f +0000000914 00000 f +0000000915 00000 f +0000000916 00000 f +0000000917 00000 f +0000000918 00000 f +0000000919 00000 f +0000000920 00000 f +0000000921 00000 f +0000000922 00000 f +0000000923 00000 f +0000000924 00000 f +0000000925 00000 f +0000000926 00000 f +0000000927 00000 f +0000000928 00000 f +0000000929 00000 f +0000000930 00000 f +0000000931 00000 f +0000000932 00000 f +0000000933 00000 f +0000000934 00000 f +0000000935 00000 f +0000000936 00000 f +0000000937 00000 f +0000000938 00000 f +0000000939 00000 f +0000000940 00000 f +0000000941 00000 f +0000000942 00000 f +0000000943 00000 f +0000000944 00000 f +0000000945 00000 f +0000000946 00000 f +0000000947 00000 f +0000000948 00000 f +0000000949 00000 f +0000000950 00000 f +0000000951 00000 f +0000000952 00000 f +0000000953 00000 f +0000000954 00000 f +0000000955 00000 f +0000000956 00000 f +0000000957 00000 f +0000000958 00000 f +0000000959 00000 f +0000000960 00000 f +0000000961 00000 f +0000000962 00000 f +0000000963 00000 f +0000000964 00000 f +0000000965 00000 f +0000000966 00000 f +0000000967 00000 f +0000000968 00000 f +0000000969 00000 f +0000000970 00000 f +0000000971 00000 f +0000000972 00000 f +0000000973 00000 f +0000000974 00000 f +0000000975 00000 f +0000000976 00000 f +0000000977 00000 f +0000000978 00000 f +0000000979 00000 f +0000000980 00000 f +0000000981 00000 f +0000000982 00000 f +0000000983 00000 f +0000000984 00000 f +0000000985 00000 f +0000000986 00000 f +0000000987 00000 f +0000000988 00000 f +0000000989 00000 f +0000000990 00000 f +0000000991 00000 f +0000000992 00000 f +0000000993 00000 f +0000000994 00000 f +0000000995 00000 f +0000000996 00000 f +0000000997 00000 f +0000000998 00000 f +0000000999 00000 f +0000001000 00000 f +0000001001 00000 f +0000001002 00000 f +0000001003 00000 f +0000001004 00000 f +0000001005 00000 f +0000001006 00000 f +0000001007 00000 f +0000001008 00000 f +0000001009 00000 f +0000001010 00000 f +0000001011 00000 f +0000001012 00000 f +0000001013 00000 f +0000001014 00000 f +0000001015 00000 f +0000001016 00000 f +0000001017 00000 f +0000001018 00000 f +0000001019 00000 f +0000001020 00000 f +0000001021 00000 f +0000001022 00000 f +0000001023 00000 f +0000001024 00000 f +0000001025 00000 f +0000001026 00000 f +0000001027 00000 f +0000001028 00000 f +0000001029 00000 f +0000001030 00000 f +0000001031 00000 f +0000001032 00000 f +0000001033 00000 f +0000001034 00000 f +0000001035 00000 f +0000001036 00000 f +0000001037 00000 f +0000001038 00000 f +0000001039 00000 f +0000001040 00000 f +0000001041 00000 f +0000001042 00000 f +0000001043 00000 f +0000001044 00000 f +0000001045 00000 f +0000001046 00000 f +0000001047 00000 f +0000001048 00000 f +0000001049 00000 f +0000001050 00000 f +0000001051 00000 f +0000001052 00000 f +0000001053 00000 f +0000001054 00000 f +0000001055 00000 f +0000001056 00000 f +0000001057 00000 f +0000001058 00000 f +0000001059 00000 f +0000001060 00000 f +0000001061 00000 f +0000001062 00000 f +0000001063 00000 f +0000001064 00000 f +0000001065 00000 f +0000001066 00000 f +0000001067 00000 f +0000001068 00000 f +0000001069 00000 f +0000001070 00000 f +0000001071 00000 f +0000001072 00000 f +0000001073 00000 f +0000001074 00000 f +0000001075 00000 f +0000001076 00000 f +0000001077 00000 f +0000001078 00000 f +0000001079 00000 f +0000001080 00000 f +0000001081 00000 f +0000001082 00000 f +0000001083 00000 f +0000001084 00000 f +0000001085 00000 f +0000001086 00000 f +0000001087 00000 f +0000001088 00000 f +0000001089 00000 f +0000001090 00000 f +0000001091 00000 f +0000001092 00000 f +0000001093 00000 f +0000001094 00001 f +0000001095 00000 f +0000001096 00000 f +0000001097 00000 f +0000001098 00000 f +0000001099 00000 f +0000000000 00000 f +0000061265 00000 n +0000061343 00000 n +0000061589 00000 n +0000062768 00000 n +0000073154 00000 n +0000089171 00000 n +0000103838 00000 n +0000126350 00000 n +0000031715 00000 n +0000060764 00000 n +0000060926 00000 n +0000031830 00000 n +0000054651 00000 n +0000032148 00000 n +0000045311 00000 n +0000032585 00000 n +0000038610 00000 n +0000061093 00000 n +0000032905 00000 n +0000036312 00000 n +0000033282 00000 n +0000033680 00000 n +0000028893 00000 n +0000134602 00000 n +0000054933 00000 n +0000045575 00000 n +0000038884 00000 n +0000036574 00000 n +0000033944 00000 n +0000000079 00000 n +trailer <]>> startxref 134721 %%EOF \ No newline at end of file diff --git a/media/swfobject.js b/media/swfobject.js new file mode 100644 index 0000000..ca8b227 --- /dev/null +++ b/media/swfobject.js @@ -0,0 +1,216 @@ +/** + * SWFObject v2.0: Flash Player detection and embed - http://blog.deconcept.com/swfobject/ + * + * SWFObject is (c) 2006 Geoff Stearns and is released under the MIT License: + * http://www.opensource.org/licenses/mit-license.php + * + */ +if(typeof deconcept == "undefined") var deconcept = new Object(); +if(typeof deconcept.util == "undefined") deconcept.util = new Object(); +if(typeof deconcept.SWFObjectUtil == "undefined") deconcept.SWFObjectUtil = new Object(); +deconcept.SWFObject = function(swf, id, w, h, ver, c, quality, xiRedirectUrl, redirectUrl, detectKey) { + if (!document.getElementById) { return; } + this.DETECT_KEY = detectKey ? detectKey : 'detectflash'; + this.skipDetect = deconcept.util.getRequestParameter(this.DETECT_KEY); + this.params = new Object(); + this.variables = new Object(); + this.attributes = new Array(); + if(swf) { this.setAttribute('swf', swf); } + if(id) { this.setAttribute('id', id); } + if(w) { this.setAttribute('width', w); } + if(h) { this.setAttribute('height', h); } + if(ver) { this.setAttribute('version', new deconcept.PlayerVersion(ver.toString().split("."))); } + this.installedVer = deconcept.SWFObjectUtil.getPlayerVersion(); + if (!window.opera && document.all && this.installedVer.major > 7) { + // only add the onunload cleanup if the Flash Player version supports External Interface and we are in IE + deconcept.SWFObject.doPrepUnload = true; + } + if(c) { this.addParam('bgcolor', c); } + var q = quality ? quality : 'high'; + this.addParam('quality', q); + this.setAttribute('useExpressInstall', false); + this.setAttribute('doExpressInstall', false); + var xir = (xiRedirectUrl) ? xiRedirectUrl : window.location; + this.setAttribute('xiRedirectUrl', xir); + this.setAttribute('redirectUrl', ''); + if(redirectUrl) { this.setAttribute('redirectUrl', redirectUrl); } +} +deconcept.SWFObject.prototype = { + useExpressInstall: function(path) { + this.xiSWFPath = !path ? "expressinstall.swf" : path; + this.setAttribute('useExpressInstall', true); + }, + setAttribute: function(name, value){ + this.attributes[name] = value; + }, + getAttribute: function(name){ + return this.attributes[name]; + }, + addParam: function(name, value){ + this.params[name] = value; + }, + getParams: function(){ + return this.params; + }, + addVariable: function(name, value){ + this.variables[name] = value; + }, + getVariable: function(name){ + return this.variables[name]; + }, + getVariables: function(){ + return this.variables; + }, + getVariablePairs: function(){ + var variablePairs = new Array(); + var key; + var variables = this.getVariables(); + for(key in variables){ + variablePairs.push(key +"="+ variables[key]); + } + return variablePairs; + }, + getSWFHTML: function() { + var swfNode = ""; + if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) { // netscape plugin architecture + if (this.getAttribute("doExpressInstall")) { + this.addVariable("MMplayerType", "PlugIn"); + this.setAttribute('swf', this.xiSWFPath); + } + swfNode = ' 0){ swfNode += 'flashvars="'+ pairs +'"'; } + swfNode += '/>'; + } else { // PC IE + if (this.getAttribute("doExpressInstall")) { + this.addVariable("MMplayerType", "ActiveX"); + this.setAttribute('swf', this.xiSWFPath); + } + swfNode = ''; + swfNode += ''; + var params = this.getParams(); + for(var key in params) { + swfNode += ''; + } + var pairs = this.getVariablePairs().join("&"); + if(pairs.length > 0) {swfNode += '';} + swfNode += ""; + } + return swfNode; + }, + write: function(elementId){ + if(this.getAttribute('useExpressInstall')) { + // check to see if we need to do an express install + var expressInstallReqVer = new deconcept.PlayerVersion([6,0,65]); + if (this.installedVer.versionIsValid(expressInstallReqVer) && !this.installedVer.versionIsValid(this.getAttribute('version'))) { + this.setAttribute('doExpressInstall', true); + this.addVariable("MMredirectURL", escape(this.getAttribute('xiRedirectUrl'))); + document.title = document.title.slice(0, 47) + " - Flash Player Installation"; + this.addVariable("MMdoctitle", document.title); + } + } + if(this.skipDetect || this.getAttribute('doExpressInstall') || this.installedVer.versionIsValid(this.getAttribute('version'))){ + var n = (typeof elementId == 'string') ? document.getElementById(elementId) : elementId; + n.innerHTML = this.getSWFHTML(); + return true; + }else{ + if(this.getAttribute('redirectUrl') != "") { + document.location.replace(this.getAttribute('redirectUrl')); + } + } + return false; + } +} + +/* ---- detection functions ---- */ +deconcept.SWFObjectUtil.getPlayerVersion = function(){ + var PlayerVersion = new deconcept.PlayerVersion([0,0,0]); + if(navigator.plugins && navigator.mimeTypes.length){ + var x = navigator.plugins["Shockwave Flash"]; + if(x && x.description) { + PlayerVersion = new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split(".")); + } + }else{ + // do minor version lookup in IE, but avoid fp6 crashing issues + // see http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/ + try{ + var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7"); + }catch(e){ + try { + var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6"); + PlayerVersion = new deconcept.PlayerVersion([6,0,21]); + axo.AllowScriptAccess = "always"; // throws if player version < 6.0.47 (thanks to Michael Williams @ Adobe for this code) + } catch(e) { + if (PlayerVersion.major == 6) { + return PlayerVersion; + } + } + try { + axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); + } catch(e) {} + } + if (axo != null) { + PlayerVersion = new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(",")); + } + } + return PlayerVersion; +} +deconcept.PlayerVersion = function(arrVersion){ + this.major = arrVersion[0] != null ? parseInt(arrVersion[0]) : 0; + this.minor = arrVersion[1] != null ? parseInt(arrVersion[1]) : 0; + this.rev = arrVersion[2] != null ? parseInt(arrVersion[2]) : 0; +} +deconcept.PlayerVersion.prototype.versionIsValid = function(fv){ + if(this.major < fv.major) return false; + if(this.major > fv.major) return true; + if(this.minor < fv.minor) return false; + if(this.minor > fv.minor) return true; + if(this.rev < fv.rev) return false; + return true; +} +/* ---- get value of query string param ---- */ +deconcept.util = { + getRequestParameter: function(param) { + var q = document.location.search || document.location.hash; + if(q) { + var pairs = q.substring(1).split("&"); + for (var i=0; i < pairs.length; i++) { + if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) { + return pairs[i].substring((pairs[i].indexOf("=")+1)); + } + } + } + return ""; + } +} +/* fix for video streaming bug */ +deconcept.SWFObjectUtil.cleanupSWFs = function() { + var objects = document.getElementsByTagName("OBJECT"); + for (var i=0; i < objects.length; i++) { + objects[i].style.display = 'none'; + for (var x in objects[i]) { + if (typeof objects[i][x] == 'function') { + objects[i][x] = function(){}; + } + } + } +} +// fixes bug in fp9 see http://blog.deconcept.com/2006/07/28/swfobject-143-released/ +if (deconcept.SWFObject.doPrepUnload) { + deconcept.SWFObjectUtil.prepUnload = function() { + __flash_unloadHandler = function(){}; + __flash_savedUnloadHandler = function(){}; + window.attachEvent("onunload", deconcept.SWFObjectUtil.cleanupSWFs); + } + window.attachEvent("onbeforeunload", deconcept.SWFObjectUtil.prepUnload); +} +/* add Array.push if needed (ie5) */ +if (Array.prototype.push == null) { Array.prototype.push = function(item) { this[this.length] = item; return this.length; }} + +/* add some aliases for ease of use/backwards compatibility */ +var getQueryParamValue = deconcept.util.getRequestParameter; +var FlashObject = deconcept.SWFObject; // for legacy support +var SWFObject = deconcept.SWFObject; diff --git a/rte/.htaccess b/rte/.htaccess new file mode 100644 index 0000000..3a5e702 --- /dev/null +++ b/rte/.htaccess @@ -0,0 +1,24 @@ +# +# Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +# For licensing, see LICENSE.html or http://ckeditor.com/license +# + +# +# On some specific Linux installations you could face problems with Firefox. +# It could give you errors when loading the editor saying that some illegal +# characters were found (three strange chars in the beginning of the file). +# This could happen if you map the .js or .css files to PHP, for example. +# +# Those characters are the Byte Order Mask (BOM) of the Unicode encoded files. +# All FCKeditor files are Unicode encoded. +# + +AddType application/x-javascript .js +AddType text/css .css + +# +# If PHP is mapped to handle XML files, you could have some issues. The +# following will disable it. +# + +AddType text/xml .xml diff --git a/rte/CHANGES.html b/rte/CHANGES.html new file mode 100644 index 0000000..8ad5502 --- /dev/null +++ b/rte/CHANGES.html @@ -0,0 +1,752 @@ + + + + + Changelog - CKEditor + + + + +

    + CKEditor Changelog +

    +

    + CKEditor 3.3.1

    +

    + Fixed issues:

    +
      +
    • #5780 : Text selection lost when opening some of the dialogs.
    • +
    • #5787 : Liststyle plugin wasn't packaged into the core (CKEDITOR.resourceManager.load exception).
    • +
    • #5637 : Fix wrong nesting that generated "<head> must be a child of <html>" warning in Webkit.
    • +
    • #5790 : Internal only attributes output on fullpage <html> tag.
    • +
    • #5761 : [IE] Color dialog matrix buttons are barely clickable in quirks mode.
    • +
    • #5759 : [IE] Clicking on the scrollbar and then on the host page causes error.
    • +
    • #5772 : List style dialog is missing tab page ids.
    • +
    • #5782 : [FF] Wysiwyg mode is broken by 'display' style changes on editor's parent DOM tree.
    • +
    • #5801 : [IE] contentEditable="false" doesn't apply in effect on inline-elements.
    • +
    • #5794 : Empty find matching twice results in JavaScript error.
    • +
    • #5732 : If it isn't possible to connect to the SCAYT servers the dialogs might hang in Firefox. Fix for Firefox>=3.6.
    • +
    • #5807 : [FF2] New page command results in uneditable document.
    • +
    • #5807 : [FF2] SCAYT plugin is disabled in Firefox2 due to selection interference.
    • +
    • #5772 : [IE] Some numbered list style types are not supported by IE6/7 and causes JavaScript error.
    • +
    +

    + CKEditor 3.3

    +

    + New features:

    +
      +
    • #635 : The properties dialog will now open when double clicking on objects.
    • +
    • #3893 : It's now possible to indent/outdent lists when selecting the first list item.
    • +
    • #4968 : The contentsLangDirection setting now has a default value 'ui' which inherit language direction from the editor UI language.
    • +
    • #4649 : The color picker dialog is now accessible.
    • +
    • #3593 : The editing area is now enabled by contentEditable="true" instead of designMode="on" to allow creating uneditable content elements in all browsers.
    • +
    • #4056 : Hidden fields will now be displayed as fake element just like in FCKeditor 2.
    • +
    +

    + CKEditor 3.2.2

    +

    + New features:

    +
      +
    • The SCAYT spell checker is now enabled by default through the autoStartup setting.
    • +
    • #5631 : The SCAYT context menu options can now be reorganized through the scayt_contextMenuItemsOrder setting.
    • +
    • #4231 : Introducing the resize_dir setting, to be able to restrict manual resizing of the editor to only one direction (horizontal/vertical).
    • +
    • #5479 : Introducing the classic ASP integration files and samples.
    • +
    • #5024 : Added samples (HTML and XHTML) to show how to output HTML using fonts and other attributes instead of styles.
    • +
    • #4358 : Introduced the List Properties dialog.
    • +
    • #5485 : Adding the contentsLanguage configuration option to be able to set the language for the editor contents.
    • +
    +

    + Fixed issues:

    +
      +
    • #5330 : Corrected detection of CTRL and META keys in Macs for the context menu.
    • +
    • #5434 : Fixed access denied issues with IE when accessing web sites through IPv6 IP addresses.
    • +
    • #4476 : [IE] Inaccessible empty list item contains sub list.
    • +
    • #4881 : [IE] Selection range broken because of cutting a single control type element from it.
    • +
    • #5505 : Image dialog throw JavaScript error when click close dialog before preview area is loading.
    • +
    • #5144 : [Chrome] Paste in Webkit sometimes leaves extra 'div' element.
    • +
    • #5021 : [Firefox] Typing in empty document start from second line when enterMode = CKEDITOR.ENTER_BR.
    • +
    • #5416 : [IE] Delete table throws a error when enterMode = CKEDITOR.ENTER_BR.
    • +
    • #4459 : [IE] Select element is penetrating the maximized editor in IE6.
    • +
    • #5559 : [IE] The first call to setData is affected by iframe cache when loading the wysiwyg mode.
    • +
    • #5567 : [IE] Remove inline styles in some case doesn't join identical siblings.
    • +
    • #5450 : [FireFox] Press ENTER on 'replace' button result wrong.
    • +
    • #5121 : Recognizes the <br /> tag as a separator when apply block styles and enterMode = CKEDITOR.ENTER_BR.
    • +
    • #5575 : CKEDITOR.replaceAll should consider all kind of white spaces between class names.
    • +
    • #5582 : Prevent the default behavior when click the 'x' button to close dialog box.
    • +
    • #5584 : ENTER key with forceEnterMode turns on doesn't inherit current block attributes.
    • +
    • #4797 : [Opera] Press ENTER key in dialog fields to close throws JavaScript error.
    • +
    • #5578 : Add flash fake element align property when switch mode (source to wysiwyg).
    • +
    • #5577 : Update delete column behavior when choose multiple cells in the same column.
    • +
    • #5512 : Open context menu with SHIFT+F10 doesn't get correct editor selection.
    • +
    • #5433 : English protocol text directions in Link dialog are not incorrect in 'rtl' UI languages.
    • +
    • #5553 : Paste dialog clipboard area text direction is incorrect for 'rtl' content languages.
    • +
    • #4734 : Font size resets when font name is changed in an empty numbered list.
    • +
    • #5237 : English text in dialogs' title is flipped when using RTL language.
    • +
    • #3257 : Create list doesn't keep blocks as headings.
    • +
    • #5111 : [Firefox] JAWS doesn't respect PC cursor mode (application role) on toolbar.
    • +
    • #5530 : Page break for printing can't be removed with undo.
    • +
    • #5381 : Unable to place cursor between two paragraphs in body.
    • +
    • #5568 : [IE6/7] Selecting a entire table cell changes the original range.
    • +
    • #5623 : [Firefox] Apply style that edges another inline style result incorrect.
    • +
    • #5586 : [Firefox] Maximize the second editor ruins full screen mode.
    • +
    • #5617 : HTML filter system does not allow two 'text' filter rules.
    • +
    • #5663 : General memory clean up after destroying last instance.
    • +
    • #5461 : [IE] Fix Paste from Word dialog doesn't accept imput problem.
    • +
    • #5676 : Make color buttons use RRGGBB instead of RGB for better compatibility with IE.
    • +
    • #4948 : [Safari] Select the first/last cell of table to open context menu may lead to undetected table.
    • +
    • #5591 : [Firefox] Select a list item makes selected element broken.
    • +
    • #5667 : Pasting in a RTL page content causes shows up the horizontal scrollbar.
    • +
    • #5688 : Duplicate ids are used in dialog definition.
    • +
    • #5719 : [IE] 'change' dialog event should not be triggered when dialog is already closed.
    • +
    • #5747 : [IE] Error thrown when IE input field editing mode is turned on.
    • +
    • #5516 : IE8: Toolbar buttons have higher bottom padding.
    • +
    • #5402 : SHIFT-ENTER could now be used to exit from preformat block.
    • +
    • SCAYT plugin related:
        +
      • #4836 : Using SCAYT result in fragile elements when applying inline styles.
      • +
      • #5425 : [Opera] Disable SCAYT plugin for Opera browser.
      • +
      • #5632 : SCAYT word marker is not visible on text with background-color set.
      • +
      • #4125 : Remove Format command incorrectly removes SCAYT word markers.
      • +
      • #5671 : SCAYT bootstrap script could be added multiple times unnecessarily.
      • +
      • #5573 : SCAYT move cursor position after insert element into marked word text.
      • +
      • #5546 : SCAYT interferes with undo/redo commands.
      • +
      • #5570 : [IE] First enabling SCAYT blind cursor in editor.
      • +
      • #5741 : Enable SCAYT cause error in multiple editor instances.
      • +
      • #5744 : Remove editor with SCAYT enabled in source mode throws error.
      • +
    • +
    • Updated the following language files:
    • +
    +

    + CKEditor 3.2.1

    +

    + New features:

    +
      +
    • #4478 : Enable the SelectAll command in source mode.
    • +
    • #5150 : Allow names in the CKEDITOR.config.colorButton_colors setting.
    • +
    • #4810 : Adding configuration option for image dialog preview area filling text.
    • +
    • #536 : Object style now could be applied on any parent element of current selection.
    • +
    • #5290 : Unified stylesSet loading removing dependencies from the styles combo. + Now the configuration entry is named 'config.stylesSet' instead of config.stylesCombo_stylesSet and the default location + is under the 'styles' plugin instead of 'stylescombo'.
    • +
    • #5352 : Allow to define the stylesSet array in the config object for the editor.
    • +
    • #5302 : Adding config option "forceEnterMode".
    • +
    • #5216 : Extend CKEDITOR.appendTo to allow a data parameter for the initial value.
    • +
    • #5024 : Added sample to show how to output XHTML and avoid deprecated tags.
    • +
    +

    + Fixed issues:

    +
      +
    • #5152 : Indentation using class attribute doesn't work properly.
    • +
    • #4682 : It wasn't possible to edit block elements in IE that had styles like width, height or float.
    • +
    • #4750 : Correcting default order of buttons layout in dialogs on Mac.
    • +
    • #4932 : Fixed collapse button not clickable on simple toolbar.
    • +
    • #5228 : Link dialog is automatically changes protocol when URLs that starts with '?'.
    • +
    • #4877 : Fixed CKEditor displays source code in one long line (IE quirks mode + office2003 skin).
    • +
    • #5132 : Apply inline style leaks into sibling words which are seperated spaces.
    • +
    • #3599 : Background color style on sized text displayed as narrow band behind.
    • +
    • #4661 : Translation missing in link dialog.
    • +
    • #5240 : Flash alignment property is not presented visually on fake element.
    • +
    • #4910 : Pasting in IE scrolls document to the end.
    • +
    • #5041 : Table summary attribute can't be removed with dialog.
    • +
    • #5124 : All inline styles cannot be applied on empty spaces.
    • +
    • #3570 : SCAYT marker shouldn't appear inside elements path bar.
    • +
    • #4553 : Dirty check result incorrect when editor document is empty.
    • +
    • #4555 : Unreleased memory when editor is created and destroyed.
    • +
    • #5118 : Arrow keys navigation in RTL languages is incorrect.
    • +
    • #4721 : Remove attribute 'value' of checkbox in IE.
    • +
    • #5278 : IE: Add validation to check for bad window names of popup window.
    • +
    • #5171 : Dialogs contains lists don't have proper voice labels.
    • +
    • #4791 : Can't place cursor inside a form that end with a checkbox/radio.
    • +
    • #4479 : StylesCombo doesn't reflect the selection state until it's first opened.
    • +
    • #4717 : 'Unlink' and 'Outdent' command buttons should be disabled on editor startup.
    • +
    • #5119 : Disabled command buttons are not being properly styled when focused.
    • +
    • #5307 : Hide dialog page cause problem when there's two tab pages remain.
    • +
    • #5343 : Active list item ARIA role is wrongly placed.
    • +
    • #3599 : Background color style applying to text with font size style has been narrowly rendered.
    • +
    • #4711 : Line break character inside preformatted text makes it unable to type text at the end of previous line.
    • +
    • #4829 : [IE] Apply style from combo has wrong result on manually created selection.
    • +
    • #4830 : Retrieving selected element isn't always right, especially selecting using keyboard (SHIFT+ARROW).
    • +
    • #5128 : Element attribute inside preformatted text is corrupted when converting to other blocks.
    • +
    • #5190 : Template list entry shouldn't gain initial focus open templates list dialog opens.
    • +
    • #5238 : Menu button doesn't display arrow icon in high-contrast mode.
    • +
    • #3576 : Non-attributed element of the same name with the applied style is incorrectly removed.
    • +
    • #5221 : Insert table into empty document cause JavaScript error thrown.
    • +
    • #5242 : Apply 'automatic' color option of text color incorrectly removes background-color style.
    • +
    • #4719 : IE does not escape attribute values properly.
    • +
    • #5170 : Firefox does not insert text into styled element properly.
    • +
    • #4026 : Office2003 skin has no toolbar button borders in High Contrast in IE7.
    • +
    • #4348 : There should have exception thrown when 'CKEDITOR_BASEPATH' couldn't be figured out automatically.
    • +
    • #5364 : Focus may not be put into dialog correctly when dialog skin file is loading slow.
    • +
    • #4016 : Justify the layout of forms select dialog in Chrome and IE7.
    • +
    • #5373 : Variable 'pathBlockElements' defines wrong items in CKEDITOR.dom.elementPath.
    • +
    • #5082 : Ctrl key should be described as Cmd key on Mac.
    • +
    • #5182 : Context menu is not been announced correctly by ATs.
    • +
    • #4898 : Can't navigate outside table under the last paragraph of document.
    • +
    • #4950 : List commands could compromise list item attribute and styles.
    • +
    • #5018 : Find result highlighting remove normal font color styles unintentionally.
    • +
    • #5376 : Unable to exit list from within a empty block under list item.
    • +
    • #5145 : Various SCAYT fixes.
    • +
    • #5319 : Match whole word doesn't work anymore after replacement has happened.
    • +
    • #5363 : 'title' attribute now presents on all editor iframes.
    • +
    • #5374 : Unable to toggle inline style when the selection starts at the linefeed of the previous paragraph.
    • +
    • #4513 : Selected link element is not always correctly detected when using keyboard arrows to perform such selection.
    • +
    • #5372 : Newly created sub list should inherit nothing from the original (parent) list, except the list type.
    • +
    • #5274 : [IE6] Templates preview image is displayed in wrong size.
    • +
    • #5292 : Preview in font size and family doesn't work with custom styles.
    • +
    • #5396 : Selection is lost when use cell properties dialog to change cell type to header.
    • +
    • #4082 : [IE+Quirks] Preview text in the image dialog is not wrapping.
    • +
    • #4197 : Fixing format combo don't hide when editor blur on Safari.
    • +
    • #5401 : The context menu break layout with Office2003 and V2 skin on IE quirks mode.
    • +
    • #4825 : Fixing browser context menu is opened when clicking right mouse button twice.
    • +
    • #5356 : The SCAYT dialog had issues with Prototype enabled pages.
    • +
    • #5266 : SCAYT was disturbing the rendering of TH elements.
    • +
    • #4688 : SCAYT was interfering on checkDirty.
    • +
    • #5429 : High Contrast mode was being mistakenly detected when loading the editor through Dojo's xhrGet.
    • +
    • #5221 : Range is mangled when making collapsed selection in an empty paragraph.
    • +
    • #5261 : Config option 'scayt_autoStartup' slow down editor loading.
    • +
    • #3846 : Google Chrome - No Img properties after inserting.
    • +
    • #5465 : ShiftEnter=DIV doesn't respect list item when pressing ENTER at end of list item.
    • +
    • #5454 : After replaced success, the popup window couldn't be closed and a js error occured.
    • +
    • #4784 : Incorrect cursor position after delete table cells.
    • +
    • #5149 : [FF] Cursor disappears after maximize when the editor has focus.
    • +
    • #5220 : DTD now shows tolerance to <style> appear inside content.
    • +
    • #5440 : Mobile browsers (iPhone, Android...) are marked as incompatible as they don't support editing features.
    • +
    • #5504 : [IE6/7] 'Paste' dialog will always get opened even when user allows the clipboard access dialog when using 'Paste' button.
    • +
    • Updated the following language files:
    • +
    +

    + CKEditor 3.2

    +

    + New features:

    +
      +
    • Several accessibility enhancements:
        +
      • #4502 : The editor accessibility is now totally based on WAI-ARIA.
      • +
      • #5015 : Adding accessibility help dialog plugin.
      • +
      • #5014 : Keyboard navigation compliance with screen reader suggested keys.
      • +
      • #4595 : Better accessibility in the Templates dialog.
      • +
      • #3389 : Esc/Arrow Key now works for closing sub menu.
      • +
    • +
    • #4973 : The Style field in the Div Container dialog is now loading the styles defined in the default styleset used by the Styles toolbar combo.
    • +
    +

    + Fixed issues:

    +
      +
    • #5049 : Form Field list command in JAWS incorrectly lists extra fields.
    • +
    • #5008 : Lock/Unlock ratio buttons in the Image dialog was poorly designed in High Contrast mode.
    • +
    • #3980 : All labels in dialogs now use <label> instead of <div>.
    • +
    • #5213 : Reorganization of some entries in the language files to make it more consistent.
    • +
    • #5199 : In IE, single row toolbars didn't have the bottom padding.
    • +
    +

    + CKEditor 3.1.1

    +

    + New features:

    +
      +
    • #4399 : Improved support for external file browsers by allowing executing a callback function.
    • +
    • #4612 : The text of links is now updated if it matches the URL to which it points to.
    • +
    • #4936 : New localization support for the Welsh language.
    • +
    +

    + Fixed issues:

    +
      +
    • #4272 : Kama skin toolbar was broken in IE+Quirks+RTL.
    • +
    • #4987 : Changed the url which is called by the Browser Server button in the Link tab of Image Properties dialog.
    • +
    • #5030 : The CKEDITOR.timestamp wasn't been appended to the skin.js file.
    • +
    • #4993 : Removed the float style from images when the user selects 'not set' for alignment.
    • +
    • #4944 : Fixed a bug where nested list structures with inconsequent levels were not being pasted correctly from MS Word.
    • +
    • #4637 : Table cells' 'nowrap' attribute was not being loaded by the cell property dialog. Thanks to pomu0325.
    • +
    • #4724 : Using the mouse to insert a link in IE might create incorrect results.
    • +
    • #4640 : Small optimizations for the fileBrowser plugin.
    • +
    • #4583 : The "Target Frame Name" field is now visible when target is set to 'frame' only.
    • +
    • #4863 : Fixing iframedialog's height doesn't stretch to 100% (except IE Quirks).
    • +
    • #4964 : The BACKSPACE key positioning was not correct in some cases with Firefox.
    • +
    • #4980 : Setting border, vspace and hspace of images to zero was not working.
    • +
    • #4773 : The fileBrowser plugin was overwriting onClick functions eventually defined on fileButton elements.
    • +
    • #4731 : The clipboard plugin was missing a reference to the dialog plugin.
    • +
    • #5051 : The about plugin was missing a reference to the dialog plugin.
    • +
    • #5146 : The wsc plugin was missing a reference to the dialog plugin.
    • +
    • #4632 : The print command will now properly break on the insertion point of page break for printing.
    • +
    • #4862 : The English (United Kingdom) language file has been renamed to en-gb.js.
    • +
    • #4618 : Selecting an emoticon or the lock and reset buttons in the image dialog fired the onBeforeUnload event in IE.
    • +
    • #4678 : It was not possible to set tables' width to empty value.
    • +
    • #5012 : Fixed dependency issues with the menu plugin.
    • +
    • #5040 : The editor will not properly ignore font related settings that have extra item separators (semi-colons).
    • +
    • #4046 : Justify should respect config.enterMode = CKEDITOR.ENTER_BR.
    • +
    • #4622 : Inserting tables multiple times was corrupting the undo system.
    • +
    • #4647 : [IE] Selection on an element within positioned container is lost after open context-menu then click one menu item.
    • +
    • #4683 : Double-quote character in attribute values was not escaped in the editor output.
    • +
    • #4762 : [IE] Unexpected vertical-scrolling behavior happens whenever focus is moving out of editor in source mode.
    • +
    • #4772 : Text color was not being applied properly on links.
    • +
    • #4795 : [IE] Press 'Del' key on horizontal line or table result in error.
    • +
    • #4824 : [IE] <br/> at the very first table cell breaks the editor selection.
    • +
    • #4851 : [IE] Delete table rows with context-menu may cause error.
    • +
    • #4951 : Replacing text with empty string was throwing errors.
    • +
    • #4963 : Link dialog was not opening properly for e-mail type links.
    • +
    • #5043 : Removed the possibility of having an unwanted script tag being outputted with the editor contents.
    • +
    • #3678 : There were issues when editing links inside floating divs with IE.
    • +
    • #4763 : Pressing ENTER key with text selected was not deleting the text in some situations.
    • +
    • #5096 : Simple ampersand attribute value doesn't work for more than one occurrence.
    • +
    • #3494 : Context menu is too narrow in some translations.
    • +
    • #5005 : Fixed HTML errors in PHP samples.
    • +
    • #5123 : Fixed broken XHTML in User Interface Languages sample.
    • +
    • #4893 : Editor now understands table cell inline styles.
    • +
    • #4611 : Selection around <select> in editor doesn't cause error anymore.
    • +
    • #4886 : Extra BR tags were being created in the output HTML.
    • +
    • #4933 : Empty tags with BR were being left in the DOM.
    • +
    • #5127 : There were errors when removing dialog definition pages through code.
    • +
    • #4767 : CKEditor was not working when ckeditor_source.js is loaded in the <body> .
    • +
    • #5062 : Avoided security warning message when loading the wysiwyg area in IE6 under HTTPS.
    • +
    • #5135 : The TAB key will now behave properly when in Source mode.
    • +
    • #4988 : It wasn't possible to use forcePasteAsPlainText with Safari on Mac.
    • +
    • #5095 : Safari on Mac deleted the current selection in the editor when Edit menu was clicked.
    • +
    • #5140 : In High Contrast mode, arrows were now been displayed for menus with submenus.
    • +
    • #5163 : The undo system was not working on some specific cases.
    • +
    • #5162 : The ajax sample was throwing errors when loading data.
    • +
    • #4999 : The Template dialog was not generating an undo snapshot.
    • +
    • Updated the following language files:
    • +
    +

    + CKEditor 3.1

    +

    + New features:

    +
      +
    • #4067 : Introduced the full page editing support (from <html> to </html>).
    • +
    • #4228 : Introduced the Shared Spaces feature.
    • +
    • #4379 : Introduced the new powerful pasting system and word cleanup procedure, including enhancements to the paste as plain text feature.
    • +
    • #2872 : Introduced the new native PHP API, the first standardized server side support.
    • +
    • #4210 : Added CKEditor plugin for jQuery.
    • +
    • #2885 : Added 'div' dialog and corresponding context menu options.
    • +
    • #4574 : Added the table merging tools and corresponding context menu options.
    • +
    • #4340 : Added the email protection option for link dialog.
    • +
    • #4463 : Added inline CSS support in all places where custom stylesheet could apply.
    • +
    • #3881 : Added color dialog for 'more color' option in color buttons.
    • +
    • #4341 : Added the 'showborder' plugin.
    • +
    • #4549 : Make the anti-cache query string configurable.
    • +
    • #4708 : Added the 'htmlEncodeOutput' config option.
    • +
    • #4342 : Introduced the bodyId and bodyClass settings to specify the id and class. to be used in the editing area at runtime.
    • +
    • #3401 : Introduced the baseHref setting so it's possible to set the URL to be used to resolve absolute and relative URLs in the contents.
    • +
    • #4729 : Added support to fake elements for comments.
    • +
    +

    + Fixed issues:

    +
      +
    • #4707 : Fixed invalid link is requested in image preview.
    • +
    • #4461 : Fixed toolbar separator line along side combo enlarging the toolbar height.
    • +
    • #4596 : Fixed image re-size lock buttons aren't accessible in high-contrast mode.
    • +
    • #4676 : Fixed editing tables using table properties dialog overwrites original style values.
    • +
    • #4714 : Fixed IE6 JavaScript error when editing flash by commit 'Flash' dialog.
    • +
    • #3905 : Fixed 'wysiwyg' mode causes unauthenticated content warnings over SSL in FF 3.5.
    • +
    • #4768 : Fixed open context menu in IE throws js error when focus is not inside document.
    • +
    • #4822 : Fixed applying 'Headers' to existing table does not work in IE.
    • +
    • #4855 : Fixed toolbar doesn't wrap well for 'v2' skin in all browsers.
    • +
    • #4882 : Fixed auto detect paste from MS-Word is not working for Safari.
    • +
    • #4882 : Fixed unexpected margin style left behind on content cleaning up from MS-Word.
    • +
    • #4896 : Fixed paste nested list from MS-Word with measurement units set to cm is broken.
    • +
    • #4899 : Fixed unable to undo pre-formatted style.
    • +
    • #4900 : Fixed ratio-lock inconsistent between browsers.
    • +
    • #4901 : Fixed unable to edit any link with popup window's features in Firefox.
    • +
    • #4904 : Fixed when paste happen from dialog, it always throw JavaScript error.
    • +
    • #4905 : Fixed paste plain text result incorrect when content from dialog.
    • +
    • #4889 : Fixed unable to undo 'New Page' command after typing inside editor.
    • +
    • #4892 : Fixed table alignment style is not properly represented by the wrapping div.
    • +
    • #4918 : Fixed switching mode when maximized is showing background page contents.
    • +
    +

    + CKEditor 3.0.2

    +

    + New features:

    +
      +
    • #4343 : Added the configuration option 'browserContextMenuOnCtrl' so it's possible to enable the default browser context menu by holding the CTRL key.
    • +
    +

    + Fixed issues:

    +
      +
    • #4552 : Fixed float panel doesn't show up since editor instanced been destroyed once.
    • +
    • #3918 : Fixed fake object is editable with Image dialog.
    • +
    • #4053 : Fixed 'Form Properties' missing from context menu when selection collapsed inside form.
    • +
    • #4401 : Fixed customized by removing 'upload' tab page from 'Link dialog' cause JavaScript error.
    • +
    • #4477 : Adding missing tag names in object style elements.
    • +
    • #4567 : Fixed IE throw error when pressing BACKSPACE in source mode.
    • +
    • #4573 : Fixed 'IgnoreEmptyPargraph' config doesn't work with the config 'entities' is set to 'false'.
    • +
    • #4614 : Fixed attribute protection fails because of line-break.
    • +
    • #4546 : Fixed UIColor plugin doesn't work when editor id contains CSS selector preserved keywords.
    • +
    • #4609 : Fixed flash object is lost when loading data from outside editor.
    • +
    • #4625 : Fixed editor stays visible in a div with style 'visibility:hidden'.
    • +
    • #4621 : Fixed clicking below table caused an empty table been generated.
    • +
    • #3373 : Fixed empty context menu when there's no menu item at all.
    • +
    • #4473 : Fixed setting rules on the same element tag name throws error.
    • +
    • #4514 : Fixed press 'Back' button breaks wysiwyg editing mode is Firefox.
    • +
    • #4542 : Fixed unable to access buttons using tab key in Safari and Opera.
    • +
    • #4577 : Fixed relative link url is broken after opening 'Link' dialog.
    • +
    • #4597 : Fixed custom style with same attribute name but different attribute value doesn't work.
    • +
    • #4651 : Fixed 'Deleted' and 'Inserted' text style is not rendering in wysiwyg mode and is wrong is source mode.
    • +
    • #4654 : Fixed 'CKEDITOR.config.font_defaultLabel(fontSize_defaultLabel)' is not working.
    • +
    • #3950 : Fixed table column insertion incorrect when selecting empty cell area.
    • +
    • #3912 : Fixed UIColor not working in IE when page has more than 30+ editors.
    • +
    • #4031 : Fixed mouse cursor on toolbar combo has more than 3 shapes.
    • +
    • #4041 : Fixed open context menu on multiple cells to remove them result in only one removed.
    • +
    • #4185 : Fixed resize handler effect doesn't affect flash object on output.
    • +
    • #4196 : Fixed 'Remove Numbered/Bulleted List' on nested list doesn't work well on nested list.
    • +
    • #4200 : Fixed unable to insert 'password' type filed with attributes.
    • +
    • #4530 : Fixed context menu couldn't open in Opera.
    • +
    • #4536 : Fixed keyboard navigation doesn't work at all in IE quirks mode.
    • +
    • #4584 : Fixed updated link Target field is not updating when updating to certain values.
    • +
    • #4603 : Fixed unable to disable submenu items in contextmenu.
    • +
    • #4672 : Fixed unable to redo the insertion of horizontal line.
    • +
    • #4677 : Fixed 'Tab' key is trapped by hidden dialog elements.
    • +
    • #4073 : Fixed insert template with replace option could result in empty document.
    • +
    • #4455 : Fixed unable to start editing when image inside document not loaded.
    • +
    • #4517 : Fixed 'dialog_backgroundCoverColor' doesn't work on IE6.
    • +
    • #3165 : Fixed enter key in empty list item before nested one result in collapsed line.
    • +
    • #4527 : Fixed checkbox generate invalid 'checked' attribute.
    • +
    • #1659 : Fixed unable to click below content to start editing in IE with 'config.docType' setting to standard compliant.
    • +
    • #3933 : Fixed extra <br> left at the end of document when the last element is a table.
    • +
    • #4736 : Fixed PAGE UP and PAGE DOWN keys in standards mode are not working.
    • +
    • #4725 : Fixed hitting 'enter' before html comment node produces a JavaScript error.
    • +
    • #4522 : Fixed unable to redo when typing after insert an image with relative url.
    • +
    • #4594 : Fixed context menu goes off-screen when mouse is at right had side of screen.
    • +
    • #4673 : Fixed undo not available straight away if shift key is used to enter first character.
    • +
    • #4690 : Fixed the parsing of nested inline elements.
    • +
    • #4450 : Fixed selecting multiple table cells before apply justify commands generates spurious paragraph in Firefox.
    • +
    • #4733 : Fixed dialog opening sometimes hang up Firefox and Safari.
    • +
    • #4498 : Fixed toolbar collapse button missing tooltip.
    • +
    • #4738 : Fixed inserting table inside bold/italic/underline generates error on ENTER_BR mode.
    • +
    • #4246 : Fixed avoid XHTML deprecated attributes for image styling.
    • +
    • #4543 : Fixed unable to move cursor between table and hr.
    • +
    • #4764 : Fixed wrong exception message when CKEDITOR.editor.append() to non-existing elements.
    • +
    • #4521 : Fixed dialog layout in IE6/7 may have scroll-bar and other weird effects.
    • +
    • #4709 : Fixed inconsistent scroll-bar behavior on IE.
    • +
    • #4776 : Fixed preview page failed to open when relative URl contains in document.
    • +
    • #4812 : Fixed 'Esc' key not working on dialogs in Opera.
    • +
    • Updated the following language files:
    • +
    +

    + CKEditor 3.0.1

    +

    + New features:

    +
      +
    • #4219 : Added fallback mechanism for config.language.
    • +
    • #4194 : Added support for using multiple css style sheets within the editor.
    • +
    +

    + Fixed issues:

    +
      +
    • #3898 : Added validation for URL value in Image dialog.
    • +
    • #3528 : Fixed Context Menu issue when triggered using Shift+F10.
    • +
    • #4028 : Maximize control's tool tip was wrong once it is maximized.
    • +
    • #4237 : Toolbar is chopped off in Safari browser 3.x.
    • +
    • #4241 : Float panels are left on screen while editor is destroyed.
    • +
    • #4274 : Double click event is incorrect handled in 'divreplace' sample.
    • +
    • #4354 : Fixed TAB key on toolbar to not focus disabled buttons.
    • +
    • #3856 : Fixed focus and blur events in source view mode.
    • +
    • #3438 : Floating panels are off by (-1px, 0px) in RTL mode.
    • +
    • #3370 : Refactored use of CKEDITOR.env.isCustomDomain().
    • +
    • #4230 : HC detection caused js error.
    • +
    • #3978 : Fixed setStyle float on IE7 strict.
    • +
    • #4262 : Tab and Shift+Tab was not working to cycle through CTRL+SHIFT+F10 context menu in IE.
    • +
    • #3633 : Default context menu isn't disabled in toolbar, status bar, panels...
    • +
    • #3897 : Now there is no image previews when the URL is empty in image dialog.
    • +
    • #4048 : Context submenu was lacking uiColor.
    • +
    • #3568 : Dialogs now select all text when tabbing to text inputs.
    • +
    • #3727 : Cell Properties dialog was missing color selection option.
    • +
    • #3517 : Fixed "Match cyclic" field in Find & Replace dialog.
    • +
    • #4368 : borderColor table cell attribute haven't worked for none-IE
    • +
    • #4203 : In IE quirks mode + toolbar collapsed + source mode editing block height was incorrect.
    • +
    • #4387 : Fixed: right clicking in Kama skin can lead to a javascript error.
    • +
    • #4397 : Wysiwyg mode caused the host page scroll.
    • +
    • #4385 : Fixed editor's auto adjusting on DOM structure were confusing the dirty checking mechanism.
    • +
    • #4397 : Fixed regression of [3816] where turn on design mode was causing Firefox3 to scroll the host page.
    • +
    • #4254 : Added basic API sample.
    • +
    • #4107 : Normalize css font-family style text for correct comparision.
    • +
    • #3664 : Insert block element in empty editor document should not create new paragraph.
    • +
    • #4037 : 'id' attribute is missing with Flash dialog advanced page.
    • +
    • #4047 : Delete selected control type element when 'Backspace' is pressed on it.
    • +
    • #4191 : Fixed: dialog changes confirmation on image dialog appeared even when no changes have been made.
    • +
    • #4351 : Dash and dot could appear in attribute names.
    • +
    • #4355 : 'maximize' and 'showblock' commands shouldn't take editor focus.
    • +
    • #4504 : Fixed 'Enter'/'Esc' key is not working on dialog button.
    • +
    • #4245 : 'Strange Template' now come with a style attribute for width.
    • +
    • #4512 : Fixed styles plugin incorrectly adding semicolons to style text.
    • +
    • #3855 : Fixed loading unminified _source files when ckeditor_source.js is used.
    • +
    • #3717 : Dialog settings defaults can now be overridden in-page through the CKEDITOR.config object.
    • +
    • #4481 : The 'stylesCombo_stylesSet' configuration entry didn't work for full URLs.
    • +
    • #4480 : Fixed scope attribute in th.
    • +
    • #4467 : Fixed bug to use custom icon in context menus. Thanks to george.
    • +
    • #4190 : Fixed select field dialog layout in Safari.
    • +
    • #4518 : Fixed unable to open dialog without editor focus in IE.
    • +
    • #4519 : Fixed maximize without editor focus throw error in IE.
    • +
    • Updated the following language files:
    • +
    +

    + CKEditor 3.0

    +

    + New features:

    +
      +
    • #3188 : Introduce + <pre> formatting feature when converting from other blocks.
    • +
    • #4445 : editor::setData now support an optional callback parameter.
    • +
    +

    + Fixed issues:

    +
      +
    • #2856 : Fixed problem with inches in Paste From Word plugin.
    • +
    • #3929 : Using Paste dialog, + the text is pasted into current selection
    • +
    • #3920 : Mouse cursor over characters in + Special Character dialog now is correct
    • +
    • #3882 : Fixed an issue + with PasteFromWord dialog in which default values was ignored
    • +
    • #3859 : Fixed Flash dialog layout in Webkit
    • +
    • #3852 : Disabled textarea resizing in dialogs
    • +
    • #3831 : The attempt to remove the contextmenu plugin + will not anymore break the editor
    • +
    • #3781 : Colorbutton is now disabled in 'source' mode
    • +
    • #3848 : Fixed an issue with Webkit in witch + elements in the Image and Link dialogs had wrong dimensions.
    • +
    • #3808 : Fixed UI Color Picker dialog size in example page.
    • +
    • #3658 : Editor had horizontal scrollbar in IE6.
    • +
    • #3819 : The cursor was not visible + when applying style to collapsed selections in Firefox 2.
    • +
    • #3809 : Fixed beam cursor + when mouse cursor is over text-only buttons in IE.
    • +
    • #3815 : Fixed an issue + with the form dialog in which the "enctype" attribute is outputted as "encoding".
    • +
    • #3785 : Fixed an issue + in CKEDITOR.tools.htmlEncode() which incorrectly outputs &nbsp; in IE8.
    • +
    • #3820 : Fixed an issue in + bullet list command in which a list created at the bottom of another gets merged to the top. +
    • +
    • #3830 : Table cell properties dialog + doesn't apply to all selected cells.
    • +
    • #3835 : Element path is not refreshed + after click on 'newpage'; and safari is not putting focus on document also. +
    • +
    • #3821 : Fixed an issue with JAWS in which + toolbar items are read inconsistently between virtual cursor modes.
    • +
    • #3789 : The "src" attribute + was getting duplicated in some situations.
    • +
    • #3591 : Protecting flash related elements + including '<object>', '<embed>' and '<param>'. +
    • +
    • #3759 : Fixed CKEDITOR.dom.element::scrollIntoView + logic bug which scroll even element is inside viewport. +
    • +
    • #3773 : Fixed remove list will merge lines. +
    • +
    • #3829 : Fixed remove empty link on output data.
    • +
    • #3730 : Indent is performing on the whole + block instead of selected lines in enterMode = BR.
    • +
    • #3844 : Fixed UndoManager register keydown on obsoleted document
    • +
    • #3805 : Enabled SCAYT plugin for IE.
    • +
    • #3834 : Context menu on table caption was incorrect.
    • +
    • #3812 : Fixed an issue in which the editor + may show up empty or uneditable in IE7, 8 and Firefox 3.
    • +
    • #3825 : Fixed JS error when opening spellingcheck.
    • +
    • #3862 : Fixed html parser infinite loop on certain malformed + source code.
    • +
    • #3639 : Button size was inconsistent.
    • +
    • #3874 : Paste as plain text in Safari loosing lines.
    • +
    • #3849 : Fixed IE8 crashes when applying lists and indenting.
    • +
    • #3876 : Changed dialog checkbox and radio labels to explicit labels.
    • +
    • #3843 : Fixed context submenu position in IE 6 & 7 RTL.
    • +
    • #3864 : [FF]Document is not editable after inserting element on a fresh page.
    • +
    • #3883 : Fixed removing inline style logic incorrect on Firefox2.
    • +
    • #3884 : Empty "href" attribute was duplicated on output data.
    • +
    • #3858 : Fixed the issue where toolbars + break up in IE6 and IE7 after the browser is resized.
    • +
    • #3868 : [chrome] SCAYT toolbar options was in reversed order.
    • +
    • #3875 : Fixed an issue in Safari where + table row/column/cell menus are not useable when table cells are selected.
    • +
    • #3896 : The editing area was + flashing when switching forth and back to source view.
    • +
    • #3894 : Fixed an issue where editor failed to initialize when using the on-demand loading way.
    • +
    • #3903 : Color button plugin doesn't read config entry from editor instance correctly.
    • +
    • #3801 : Comments at the start of the document was lost in IE.
    • +
    • #3871 : Unable to redo when undos to the front of snapshots stack.
    • +
    • #3909 : Move focus from editor into a text input control is broken.
    • +
    • #3870 : The empty paragraph + desappears when hitting ENTER after "New Page".
    • +
    • #3887 : Fixed an issue in which the create + list command may leak outside of a selected table cell and into the rest of document.
    • +
    • #3916 : Fixed maximize does not enlarge editor width when width is set.
    • +
    • #3879 : [webkit] Color button panel had incorrect size on first open.
    • +
    • #3839 : Update Scayt plugin to reflect the latest change from SpellChecker.net.
    • +
    • #3742 : Fixed wrong dialog layout for dialogs without tab bar in IE RTL mode .
    • +
    • #3671 : Fixed body fixing should be applied to the real type under fake elements.
    • +
    • #3836 : Fixed remove list in enterMode=BR will merge sibling text to one line.
    • +
    • #3949 : Fixed enterKey within pre-formatted text introduce wrong line-break.
    • +
    • #3878 : Whenever possible, + dialogs will not present scrollbars if the content is too big for its standard + size.
    • +
    • #3782 : Remove empty list in table cell result in collapsed cell.
    • +
    • Updated the following language files:
    • +
    • #3984 : [IE]The pre-formatted style is generating error.
    • +
    • #3946 : Fixed unable to hide contextmenu.
    • +
    • #3956 : Fixed About dialog in Source Mode for IE.
    • +
    • #3953 : Fixed keystroke for close Paste dialog.
    • +
    • #3951 : Reset size and lock ratio options were not accessible in Image dialog.
    • +
    • #3921 : Fixed Container scroll issue on IE7.
    • +
    • #3940 : Fixed list operation doesn't stop at table.
    • +
    • #3891 : [IE] Fixed 'automatic' font color doesn't work.
    • +
    • #3972 : Fixed unable to remove a single empty list in document in Firefox with enterMode=BR.
    • +
    • #3973 : Fixed list creation error at the end of document.
    • +
    • #3959 : Pasting styled text from word result in content lost.
    • +
    • #3793 : Combined images into sprites.
    • +
    • #3783 : Fixed indenting command in table cells create collapsed paragraph.
    • +
    • #3968 : About dialog layout was broken with IE+Standards+RTL.
    • +
    • #3991 : In IE quirks, text was not visible in v2 and office2003 skins.
    • +
    • #3983 : In IE, we'll now + silently ignore wrong toolbar definition settings which have extra commas being + left around.
    • +
    • Fixed the following test cases:
        +
      • #3992 : core/ckeditor2.html
      • +
      • #4138 : core/plugins.html
      • +
      • #3801 : plugins/htmldataprocessor/htmldataprocessor.html
      • +
    • +
    • #3989 : Host page horizontal scrolling a lot when on having righ-to-left direction.
    • +
    • #4001 : Create link around existing image result incorrect.
    • +
    • #3988 : Destroy editor on form submit event cause error.
    • +
    • #3994 : Insert horizontal line at end of document cause error.
    • +
    • #4074 : Indent error with 'indentClasses' config specified.
    • +
    • #4057 : Fixed anchor is lost after switch between editing modes.
    • +
    • #3644 : Image dialog was missin radio lock.
    • +
    • #4014 : Firefox2 had no dialog button backgrounds.
    • +
    • #4018 : Firefox2 had no richcombo text visible.
    • +
    • #4035 : [IE6] Paste dialog size was too small.
    • +
    • #4049 : Kama skin was too wide with config.width.
    • +
    • The following released files now doesn't require the _source folder
        +
      • #4086 : _samples/ui_languages.html
      • +
      • #4093 : _tests/core/dom/document.html
      • +
      • #4094 : Smiley plugin file
      • +
      • #4097 : No undo/redo support for fontColor and backgroundColor buttons.
      • +
    • +
    • #4085 : Paste and Paste from Word dialogs were not well styled in IE+RTL.
    • +
    • #3982 : Fixed enterKey on empty list item result in weird dom structure.
    • +
    • #4101 : Now it is possible to close dialog before gets focus.
    • +
    • #4075 : [IE6/7]Fixed apply custom inline style with "class" attribute failed.
    • +
    • #4087 : [Firefox]Fixed extra blocks created on create list when full document selected.
    • +
    • #4097 : No undo/redo support for fontColor and backgroundColor buttons.
    • +
    • #4111 : Fixed apply block style after inline style applied on full document error.
    • +
    • #3622 : Fixed shift enter with selection not deleting highlighted text.
    • +
    • #4092 : [IE6] Close button was missing for dialog without multiple tabs.
    • +
    • #4003 : Markup on the image dialog was disrupted when removing the border input.
    • +
    • #4096 : Editor content area was pushed down in IE RTL quirks.
    • +
    • #4112 : [FF] Paste dialog had scrollbars in quirks.
    • +
    • #4118 : Dialog dragging was + occasionally behaving strangely .
    • +
    • #4077 : The toolbar combos + were rendering incorrectly in some languages, like Chinese.
    • +
    • #3622 : The toolbar in the v2 + skin was wrapping improperly in some languages.
    • +
    • #4119 : Unable to edit image link with image dialog.
    • +
    • #4117 : Fixed dialog error when transforming image into button.
    • +
    • #4058 : [FF] wysiwyg mode is sometimes not been activated.
    • +
    • #4114 : [IE] RTE + IE6/IE7 Quirks = dialog mispositoned.
    • +
    • #4123 : Some dialog buttons were broken in IE7 quirks.
    • +
    • #4122 : [IE] The image dialog + was being rendered improperly when loading an image with long URL.
    • +
    • #4144 : Fixed the white-spaces at the end of <pre> is incorrectly removed.
    • +
    • #4143 : Fixed element id is lost when extracting contents from the range.
    • +
    • #4007 : [IE] Source area overflow from editor chrome.
    • +
    • #4145 : Fixed the on demand + ("basic") loading model of the editor.
    • +
    • #4139 : Fixed list plugin regression of [3903].
    • +
    • #4147 : Unify style text normalization logic when comparing styles.
    • +
    • #4150 : Fixed enlarge list result incorrect at the inner boundary of block.
    • +
    • #4164 : Now it is possible to paste text + in Source mode even if forcePasteAsPlainText = true.
    • +
    • #4129 : [FF]Unable to remove list with Ctrl-A.
    • +
    • #4172 : [Safari] The trailing + <br> was not been always added to blank lines ending with &nbsp;.
    • +
    • #4178 : It's now possible to + copy and paste Flash content among different editor instances.
    • +
    • #4193 : Automatic font color produced empty span on Firefox 3.5.
    • +
    • #4186 : [FF] Fixed First open float panel cause host page scrollbar blinking.
    • +
    • #4227 : Fixed destroy editor instance created on textarea which is not within form cause error.
    • +
    • #4240 : Fixed editor name containing hyphen break editor completely.
    • +
    • #3828 : Malformed nested list is now corrected by the parser.
    • +
    +

    + CKEditor 3.0 RC

    +

    + Changelog starts at this release.

    + + + diff --git a/rte/INSTALL.html b/rte/INSTALL.html new file mode 100644 index 0000000..7b593e6 --- /dev/null +++ b/rte/INSTALL.html @@ -0,0 +1,92 @@ + + + + + Installation Guide - CKEditor + + + + +

    + CKEditor Installation Guide

    +

    + What's CKEditor?

    +

    + CKEditor is a text editor to be used inside web pages. It's not a replacement + for desktop text editors like Word or OpenOffice, but a component to be used as + part of web applications and web sites.

    +

    + Installation

    +

    + Installing CKEditor is an easy task. Just follow these simple steps:

    +
      +
    1. Download the latest version of the editor from our web site: http://ckeditor.com. You should have already completed + this step, but be sure you have the very latest version.
    2. +
    3. Extract (decompress) the downloaded file into the root of your + web site.
    4. +
    +

    + Note: CKEditor is by default installed in the "ckeditor" + folder. You can place the files in whichever you want though.

    +

    + Checking Your Installation +

    +

    + The editor comes with a few sample pages that can be used to verify that installation + proceeded properly. Take a look at the _samples directory.

    +

    + To test your installation, just call the following page at your web site:

    +
    +http://<your site>/<CKEditor installation path>/_samples/index.html
    +
    +For example:
    +http://www.example.com/ckeditor/_samples/index.html
    +

    + Documentation

    +

    + The full editor documentation is available online at the following address:
    + http://docs.cksource.com/ckeditor

    + + + diff --git a/rte/LICENSE.html b/rte/LICENSE.html new file mode 100644 index 0000000..f7ba067 --- /dev/null +++ b/rte/LICENSE.html @@ -0,0 +1,1334 @@ + + + + + License - CKEditor + + +

    + Software License Agreement +

    +

    + CKEditor™ - The text editor for Internet™ - + http://ckeditor.com
    + Copyright © 2003-2010, CKSource - Frederico Knabben. All rights reserved. +

    +

    + Licensed under the terms of any of the following licenses at your choice: +

    + +

    + You are not required to, but if you want to explicitly declare the license you have + chosen to be bound to when using, reproducing, modifying and distributing this software, + just include a text file titled "LEGAL" in your version of this software, indicating + your license choice. In any case, your choice will not restrict any recipient of + your version of this software to use, reproduce, modify and distribute this software + under any of the above licenses. +

    +

    + Sources of Intellectual Property Included in CKEditor +

    +

    + Where not otherwise indicated, all CKEditor content is authored by CKSource engineers + and consists of CKSource-owned intellectual property. In some specific instances, + CKEditor will incorporate work done by developers outside of CKSource with their + express permission. +

    +

    + YUI Test: At _source/tests/yuitest.js + can be found part of the source code of YUI, which is licensed under the terms of + the BSD License. YUI is + Copyright © 2008, Yahoo! Inc. +

    +

    + Trademarks +

    +

    + CKEditor is a trademark of CKSource - Frederico Knabben. All other brand and product + names are trademarks, registered trademarks or service marks of their respective + holders. +

    + + diff --git a/rte/_samples/ajax.html b/rte/_samples/ajax.html new file mode 100644 index 0000000..0d8953a --- /dev/null +++ b/rte/_samples/ajax.html @@ -0,0 +1,87 @@ + + + + + Ajax - CKEditor Sample + + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +

    + + +

    + +
    +
    + + + + diff --git a/rte/_samples/api.html b/rte/_samples/api.html new file mode 100644 index 0000000..206348d --- /dev/null +++ b/rte/_samples/api.html @@ -0,0 +1,152 @@ + + + + + API usage - CKEditor Sample + + + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +
    +

    + This sample shows how to use the CKeditor JavaScript API to interact with the editor + at runtime.

    + + + + +
    +
    + +
    + + + diff --git a/rte/_samples/api_dialog.html b/rte/_samples/api_dialog.html new file mode 100644 index 0000000..4fa83a2 --- /dev/null +++ b/rte/_samples/api_dialog.html @@ -0,0 +1,181 @@ + + + + + Using API to customize dialogs - CKEditor Sample + + + + + + + + + +

    + CKEditor Sample +

    + +
    + +
    + +

    + This sample shows how to use the dialog API to customize dialogs whithout changing + the original editor code. The following customizations are being done::

    +
      +
    1. Add dialog pages ("My Tab" in the Link dialog).
    2. +
    3. Remove a dialog tab ("Target" tab from the Link dialog).
    4. +
    5. Add dialog fields ("My Custom Field" into the Link dialog).
    6. +
    7. Remove dialog fields ("Link Type" and "Browser Server" the Link + dialog).
    8. +
    9. Set default values for dialog fields (for the "URL" field in the + Link dialog).
    10. +
    11. Create a custom dialog ("My Dialog" button).
    12. +
    + + + + + diff --git a/rte/_samples/api_dialog/my_dialog.js b/rte/_samples/api_dialog/my_dialog.js new file mode 100644 index 0000000..3ceedf8 --- /dev/null +++ b/rte/_samples/api_dialog/my_dialog.js @@ -0,0 +1,28 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dialog.add( 'myDialog', function( editor ) +{ + return { + title : 'My Dialog', + minWidth : 400, + minHeight : 200, + contents : [ + { + id : 'tab1', + label : 'First Tab', + title : 'First Tab', + elements : + [ + { + id : 'input1', + type : 'text', + label : 'Input 1' + } + ] + } + ] + }; +} ); diff --git a/rte/_samples/asp/advanced.asp b/rte/_samples/asp/advanced.asp new file mode 100644 index 0000000..7ab7dff --- /dev/null +++ b/rte/_samples/asp/advanced.asp @@ -0,0 +1,105 @@ +<%@ codepage="65001" language="VBScript" %> +<% Option Explicit %> + +<% + + ' You must set "Enable Parent Paths" on your web site + ' in order for the above relative include to work. + ' Or you can use #INCLUDE VIRTUAL="/full path/ckeditor.asp" + +%> + + + + + Sample - CKEditor + + + + +

    + CKEditor Sample +

    + +
    + +
    + +
    + Output +
    +

    +
    +

    +<% + ' Create class instance. + dim editor, initialValue, code, textareaAttributes + set editor = New CKEditor + + ' Do not print the code directly to the browser, return it instead + editor.returnOutput = true + + ' Path to CKEditor directory, ideally instead of relative dir, use an absolute path: + ' editor.basePath = "/ckeditor/" + ' If not set, CKEditor will default to /ckeditor/ + editor.basePath = "../../" + + ' Set global configuration (will be used by all instances of CKEditor). + editor.config("width") = 600 + + ' Change default textarea attributes + set textareaAttributes = CreateObject("Scripting.Dictionary") + textareaAttributes.Add "rows", 10 + textareaAttributes.Add "cols", 80 + Set editor.textareaAttributes = textareaAttributes + + ' The initial value to be displayed in the editor. + initialValue = "

    This is some sample text. You are using CKEditor.

    " + + ' Create first instance. + code = editor.editor("editor1", initialValue) + + response.write code +%> +

    +
    +

    +<% +' Configuration that will be used only by the second editor. + +editor.instanceConfig("toolbar") = Array( _ + Array( "Source", "-", "Bold", "Italic", "Underline", "Strike" ), _ + Array( "Image", "Link", "Unlink", "Anchor" ) _ + ) + +editor.instanceConfig("skin") = "v2" + +' Create second instance. +response.write editor.editor("editor2", initialValue) +%> +

    + +

    +
    +
    + + + diff --git a/rte/_samples/asp/events.asp b/rte/_samples/asp/events.asp new file mode 100644 index 0000000..a23eb0d --- /dev/null +++ b/rte/_samples/asp/events.asp @@ -0,0 +1,136 @@ +<%@ codepage="65001" language="VBScript" %> +<% Option Explicit %> + +<% + + ' You must set "Enable Parent Paths" on your web site + ' in order for the above relative include to work. + ' Or you can use #INCLUDE VIRTUAL="/full path/ckeditor.asp" + +%> + + + + + Sample - CKEditor + + + + +

    + CKEditor Sample +

    + +
    + +
    + +
    + Output +
    +

    +
    +

    +<% + +'' + ' Adds global event, will hide "Target" tab in Link dialog in all instances. + ' +function CKEditorHideLinkTargetTab(editor) + dim functionCode + functionCode = "function (ev) {" & vbcrlf & _ + "// Take the dialog name and its definition from the event data" & vbcrlf & _ + "var dialogName = ev.data.name;" & vbcrlf & _ + "var dialogDefinition = ev.data.definition;" & vbcrlf & _ + "" & vbcrlf & _ + "// Check if the definition is from the Link dialog." & vbcrlf & _ + "if ( dialogName == 'link' )" & vbcrlf & _ + " dialogDefinition.removeContents('target')" & vbcrlf & _ + "}" & vbcrlf + + editor.addGlobalEventHandler "dialogDefinition", functionCode +end function + +'' + ' Adds global event, will notify about opened dialog. + ' +function CKEditorNotifyAboutOpenedDialog(editor) + dim functionCode + functionCode = "function (evt) {" & vbcrlf & _ + "alert('Loading dialog: ' + evt.data.name);" & vbcrlf & _ + "}" + + editor.addGlobalEventHandler "dialogDefinition", functionCode +end function + + +dim editor, initialValue + +' Create class instance. +set editor = new CKEditor + +' Set configuration option for all editors. +editor.config("width") = 750 + +' Path to CKEditor directory, ideally instead of relative dir, use an absolute path: +' editor.basePath = "/ckeditor/" +' If not set, CKEditor will default to /ckeditor/ +editor.basePath = "../../" + +' The initial value to be displayed in the editor. +initialValue = "

    This is some sample text. You are using CKEditor.

    " + +' Event that will be handled only by the first editor. +editor.addEventHandler "instanceReady", "function (evt) { alert('Loaded editor: ' + evt.editor.name );}" + +' Create first instance. +editor.editor "editor1", initialValue + +' Clear event handlers, instances that will be created later will not have +' the 'instanceReady' listener defined a couple of lines above. +editor.clearEventHandlers empty +%> +

    +
    +

    +<% +' Configuration that will be used only by the second editor. +editor.instanceConfig("width") = 600 +editor.instanceConfig("toolbar") = "Basic" + +' Add some global event handlers (for all editors). +CKEditorHideLinkTargetTab(editor) +CKEditorNotifyAboutOpenedDialog(editor) + +' Event that will be handled only by the second editor. +editor.addInstanceEventHandler "instanceReady", "function (evt) { alert('Loaded second editor: ' + evt.editor.name );}" + +' Create second instance. +editor.editor "editor2", initialValue +%> +

    + +

    +
    +
    + + + diff --git a/rte/_samples/asp/index.html b/rte/_samples/asp/index.html new file mode 100644 index 0000000..69f3673 --- /dev/null +++ b/rte/_samples/asp/index.html @@ -0,0 +1,103 @@ + + + + + ASP integration Samples List - CKEditor + + + +

    + CKEditor Samples List for ASP +

    +

    + Overview +

    +

    The ckeditor.asp file provides a wrapper to ease the work of creating CKEditor instances from classic Asp.

    +

    To use it, you must first include it into your page: + + <!-- #INCLUDE file="../../ckeditor.asp" --> + + Of course, you should adjust the path to make it point to the correct location, and maybe use a full path (with virtual="" instead of file="") +

    +

    After that script is included, you can use it in different ways, based on the following pattern:

    + +
      +
    1. + Create an instance of the CKEditor class: +
      dim editor
      +set editor = New CKEditor
      +
    2. +
    3. + Set the path to the folder where CKEditor has been installed, by default it will use /ckeditor/ +
      editor.basePath = "../../"
      +
    4. +
    5. + Now use one of the three main methods to create the CKEditor instances: +
        +
      • + Replace textarea with id (or name) "editor1". +
        editor.replaceInstance "editor1"
        +
      • +
      • + Replace all textareas with CKEditor. +
        editor.replaceAll empty
        +
      • +
      • + Create a textarea element and attach CKEditor to it. +
        editor.editor "editor1", initialValue
        +
      • +
      +
    6. +
    +

    Before step 3 you can use a number of methods and properties to adjust the behavior of this class and the CKEditor instances +that will be created:

    +
      +
    • returnOutput : if set to true, the functions won't dump the code with response.write, but instead they will return it so + you can do anything you want
    • +
    • basePath: location of the CKEditor scripts
    • +
    • initialized: if you set it to true, it means that you have already included the CKEditor.js file into the page and it + doesn't have to be generated again.
    • +
    • textareaAttributes: You can set here a Dictionary object with the attributes that you want to output in the call to the "editor" method.
    • + +
    • config: Allows to set config values for all the instances from now on.
    • +
    • instanceConfig: Allows to set config values just for the next instance.
    • + +
    • addEventHandler: Adds an event handler for all the instances from now on.
    • +
    • addInstanceEventHandler: Adds an event handler just for the next instance.
    • +
    • addGlobalEventHandler: Adds an event handler for the global CKEDITOR object.
    • + +
    • clearEventHandlers: Removes one or all the event handlers from all the instances from now on.
    • +
    • clearInstanceEventHandlers: Removes one or all the event handlers from the next instance.
    • +
    • clearGlobalEventHandlers: Removes one or all the event handlers from the global CKEDITOR object.
    • + +
    + +

    + Basic Samples +

    + +

    + Advanced Samples +

    + + + + diff --git a/rte/_samples/asp/replace.asp b/rte/_samples/asp/replace.asp new file mode 100644 index 0000000..ff67b5a --- /dev/null +++ b/rte/_samples/asp/replace.asp @@ -0,0 +1,72 @@ +<%@ codepage="65001" language="VBScript" %> +<% Option Explicit %> + +<% + + ' You must set "Enable Parent Paths" on your web site + ' in order for the above relative include to work. + ' Or you can use #INCLUDE VIRTUAL="/full path/ckeditor.asp" + +%> + + + + + Sample - CKEditor + + + + +

    + CKEditor Sample +

    + +
    + +
    + +
    + Output +
    +

    +
    + +

    +

    + +

    +
    +
    + + <% + ' Create class instance. + dim editor + set editor = New CKEditor + ' Path to CKEditor directory, ideally instead of relative dir, use an absolute path: + ' editor.basePath = "/ckeditor/" + ' If not set, CKEditor will default to /ckeditor/ + editor.basePath = "../../" + ' Replace textarea with id (or name) "editor1". + editor.replaceInstance "editor1" + %> + + diff --git a/rte/_samples/asp/replaceall.asp b/rte/_samples/asp/replaceall.asp new file mode 100644 index 0000000..9cde44c --- /dev/null +++ b/rte/_samples/asp/replaceall.asp @@ -0,0 +1,77 @@ +<%@ codepage="65001" language="VBScript" %> +<% Option Explicit %> + +<% + + ' You must set "Enable Parent Paths" on your web site + ' in order for the above relative include to work. + ' Or you can use #INCLUDE VIRTUAL="/full path/ckeditor.asp" + +%> + + + + + Sample - CKEditor + + + + +

    + CKEditor Sample +

    + +
    + +
    + +
    + Output +
    +

    +
    + +

    +

    +
    + +

    +

    + +

    +
    +
    + + <% + ' Create class instance. + dim editor + set editor = New CKEditor + ' Path to CKEditor directory, ideally instead of relative dir, use an absolute path: + ' editor.basePath = "/ckeditor/" + ' If not set, CKEditor will default to /ckeditor/ + editor.basePath = "../../" + ' Replace all textareas with CKEditor. + editor.replaceAll empty + %> + + diff --git a/rte/_samples/asp/sample_posteddata.asp b/rte/_samples/asp/sample_posteddata.asp new file mode 100644 index 0000000..52afb5c --- /dev/null +++ b/rte/_samples/asp/sample_posteddata.asp @@ -0,0 +1,46 @@ +<%@ codepage="65001" language="VBScript" %> +<% Option Explicit %> + + + + + Sample - CKEditor + + + + +

    + CKEditor - Posted Data +

    + + + + + + + + + <% + Dim sForm + For Each sForm in Request.Form + %> + + + + + <% Next %> +
    Field NameValue
    <%=Server.HTMLEncode( sForm )%>
    <%=Server.HTMLEncode( Request.Form(sForm) )%>
    + + + diff --git a/rte/_samples/asp/standalone.asp b/rte/_samples/asp/standalone.asp new file mode 100644 index 0000000..df0a8ea --- /dev/null +++ b/rte/_samples/asp/standalone.asp @@ -0,0 +1,72 @@ +<%@ codepage="65001" language="VBScript" %> +<% Option Explicit %> + +<% + + ' You must set "Enable Parent Paths" on your web site + ' in order for the above relative include to work. + ' Or you can use #INCLUDE VIRTUAL="/full path/ckeditor.asp" + +%> + + + + + Sample - CKEditor + + + + +

    + CKEditor Sample +

    + +
    + +
    + +
    + Output +
    +

    + Editor 1: +

    +

    + <% + dim initialValue, editor + ' The initial value to be displayed in the editor. + initialValue = "

    This is some sample text.

    " + ' Create class instance. + set editor = New CKEditor + ' Path to CKEditor directory, ideally instead of relative dir, use an absolute path: + ' editor.basePath = "/ckeditor/" + ' If not set, CKEditor will default to /ckeditor/ + editor.basePath = "../../" + ' Create textarea element and attach CKEditor to it. + editor.editor "editor1", initialValue + %> + +

    +
    +
    + + + diff --git a/rte/_samples/assets/output_xhtml.css b/rte/_samples/assets/output_xhtml.css new file mode 100644 index 0000000..0dfbfc1 --- /dev/null +++ b/rte/_samples/assets/output_xhtml.css @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.html or http://ckeditor.com/license + * + * Styles used by the XHTML 1.1 sample page (xhtml.html). + */ + +/** + * Basic definitions for the editing area. + */ +body +{ + font-family: Arial, Verdana, sans-serif; + font-size: 80%; + color: #000000; + background-color: #ffffff; + padding: 5px; + margin: 0px; +} + +/** + * Core styles. + */ + +.Bold +{ + font-weight: bold; +} + +.Italic +{ + font-style: italic; +} + +.Underline +{ + text-decoration: underline; +} + +.StrikeThrough +{ + text-decoration: line-through; +} + +.Subscript +{ + vertical-align: sub; + font-size: smaller; +} + +.Superscript +{ + vertical-align: super; + font-size: smaller; +} + +/** + * Font faces. + */ + +.FontComic +{ + font-family: 'Comic Sans MS'; +} + +.FontCourier +{ + font-family: 'Courier New'; +} + +.FontTimes +{ + font-family: 'Times New Roman'; +} + +/** + * Font sizes. + */ + +.FontSmaller +{ + font-size: smaller; +} + +.FontLarger +{ + font-size: larger; +} + +.FontSmall +{ + font-size: 8pt; +} + +.FontBig +{ + font-size: 14pt; +} + +.FontDouble +{ + font-size: 200%; +} + +/** + * Font colors. + */ +.FontColor1 +{ + color: #ff9900; +} + +.FontColor2 +{ + color: #0066cc; +} + +.FontColor3 +{ + color: #ff0000; +} + +.FontColor1BG +{ + background-color: #ff9900; +} + +.FontColor2BG +{ + background-color: #0066cc; +} + +.FontColor3BG +{ + background-color: #ff0000; +} + +/** + * Indentation. + */ + +.Indent1 +{ + margin-left: 40px; +} + +.Indent2 +{ + margin-left: 80px; +} + +.Indent3 +{ + margin-left: 120px; +} + +/** + * Alignment. + */ + +.JustifyLeft +{ + text-align: left; +} + +.JustifyRight +{ + text-align: right; +} + +.JustifyCenter +{ + text-align: center; +} + +.JustifyFull +{ + text-align: justify; +} + +/** + * Other. + */ + +code +{ + font-family: courier, monospace; + background-color: #eeeeee; + padding-left: 1px; + padding-right: 1px; + border: #c0c0c0 1px solid; +} + +kbd +{ + padding: 0px 1px 0px 1px; + border-width: 1px 2px 2px 1px; + border-style: solid; +} + +blockquote +{ + color: #808080; +} diff --git a/rte/_samples/divreplace.html b/rte/_samples/divreplace.html new file mode 100644 index 0000000..6bd99e7 --- /dev/null +++ b/rte/_samples/divreplace.html @@ -0,0 +1,137 @@ + + + + + Replace DIV - CKEditor Sample + + + + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +

    + Double-click on any of the following DIVs to transform them into editor instances.

    +
    +

    + Part 1

    +

    + Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi + semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna + rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla + nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce + eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus. +

    +
    +
    +

    + Part 2

    +

    + Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi + semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna + rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla + nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce + eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus. +

    +

    + Donec velit. Mauris massa. Vestibulum non nulla. Nam suscipit arcu nec elit. Phasellus + sollicitudin iaculis ante. Ut non mauris et sapien tincidunt adipiscing. Vestibulum + vitae leo. Suspendisse nec mi tristique nulla laoreet vulputate. +

    +
    +
    +

    + Part 3

    +

    + Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi + semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna + rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla + nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce + eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus. +

    +
    + + + diff --git a/rte/_samples/enterkey.html b/rte/_samples/enterkey.html new file mode 100644 index 0000000..7bb39d8 --- /dev/null +++ b/rte/_samples/enterkey.html @@ -0,0 +1,88 @@ + + + + + ENTER Key Configuration - CKEditor Sample + + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +
    + When ENTER is pressed:
    + +
    +
    + When SHIFT + ENTER is pressed:
    + +
    +
    +
    +

    +
    + +

    +

    + +

    +
    + + + diff --git a/rte/_samples/fullpage.html b/rte/_samples/fullpage.html new file mode 100644 index 0000000..8d4733a --- /dev/null +++ b/rte/_samples/fullpage.html @@ -0,0 +1,62 @@ + + + + + Full Page Editing - CKEditor Sample + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +
    +

    + In this sample the editor is configured to edit entire HTML pages, from the <html> + tag to </html>.

    +

    +
    + + +

    +

    + +

    +
    + + + diff --git a/rte/_samples/index.html b/rte/_samples/index.html new file mode 100644 index 0000000..35438d2 --- /dev/null +++ b/rte/_samples/index.html @@ -0,0 +1,55 @@ + + + + + Samples List - CKEditor + + + +

    + CKEditor Samples List +

    +

    + Basic Samples +

    + +

    + Basic Customization +

    + +

    + Advanced Samples +

    + + + + diff --git a/rte/_samples/jqueryadapter.html b/rte/_samples/jqueryadapter.html new file mode 100644 index 0000000..00ca16a --- /dev/null +++ b/rte/_samples/jqueryadapter.html @@ -0,0 +1,73 @@ + + + + + jQuery adapter - CKEditor Sample + + + + + + + + + +

    + CKEditor Sample +

    + +
    + +
    + +
    +

    +
    + +

    +

    + +

    +
    + + + diff --git a/rte/_samples/output_html.html b/rte/_samples/output_html.html new file mode 100644 index 0000000..d6a7ca4 --- /dev/null +++ b/rte/_samples/output_html.html @@ -0,0 +1,259 @@ + + + + + HTML compliant output - CKEditor Sample + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +
    +

    + This sample shows CKEditor configured to produce a legacy HTML4 document. Traditional + HTML elements like <b>, <i>, and <font> are used in place of + <strong>, <em> and CSS styles.

    +

    +
    + + +

    +

    + +

    +
    + + + diff --git a/rte/_samples/output_xhtml.html b/rte/_samples/output_xhtml.html new file mode 100644 index 0000000..7752420 --- /dev/null +++ b/rte/_samples/output_xhtml.html @@ -0,0 +1,159 @@ + + + + + XHTML compliant output - CKEditor Sample + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +
    +

    + This sample shows CKEditor configured to produce XHTML 1.1 compliant + HTML. Deprecated elements or attributes, like the <font> and <u> elements + or the "style" attribute, are avoided.

    +

    +
    + + +

    +

    + +

    +
    + + + diff --git a/rte/_samples/php/advanced.php b/rte/_samples/php/advanced.php new file mode 100644 index 0000000..b7500ef --- /dev/null +++ b/rte/_samples/php/advanced.php @@ -0,0 +1,93 @@ + + + + + Sample - CKEditor + + + + +

    + CKEditor Sample +

    + +
    + +
    + +
    + Output +
    +

    +
    +

    +returnOutput = true; + +// Path to CKEditor directory, ideally instead of relative dir, use an absolute path: +// $CKEditor->basePath = '/ckeditor/' +// If not set, CKEditor will try to detect the correct path. +$CKEditor->basePath = '../../'; + +// Set global configuration (will be used by all instances of CKEditor). +$CKEditor->config['width'] = 600; + +// Change default textarea attributes +$CKEditor->textareaAttributes = array("cols" => 80, "rows" => 10); + +// The initial value to be displayed in the editor. +$initialValue = '

    This is some sample text. You are using CKEditor.

    '; + +// Create first instance. +$code = $CKEditor->editor("editor1", $initialValue); + +echo $code; +?> +

    +
    +

    +editor("editor2", $initialValue, $config); +?> +

    + +

    +
    +
    + + + diff --git a/rte/_samples/php/events.php b/rte/_samples/php/events.php new file mode 100644 index 0000000..6de1d53 --- /dev/null +++ b/rte/_samples/php/events.php @@ -0,0 +1,130 @@ + + + + + Sample - CKEditor + + + + +

    + CKEditor Sample +

    + +
    + +
    + +
    + Output +
    +

    +
    +

    +addGlobalEventHandler('dialogDefinition', $function); +} + +/** + * Adds global event, will notify about opened dialog. + */ +function CKEditorNotifyAboutOpenedDialog(&$CKEditor) { + $function = 'function (evt) { + alert("Loading dialog: " + evt.data.name); + }'; + + $CKEditor->addGlobalEventHandler('dialogDefinition', $function); +} + +// Include CKEditor class. +include("../../ckeditor.php"); + +// Create class instance. +$CKEditor = new CKEditor(); + +// Set configuration option for all editors. +$CKEditor->config['width'] = 750; + +// Path to CKEditor directory, ideally instead of relative dir, use an absolute path: +// $CKEditor->basePath = '/ckeditor/' +// If not set, CKEditor will try to detect the correct path. +$CKEditor->basePath = '../../'; + +// The initial value to be displayed in the editor. +$initialValue = '

    This is some sample text. You are using CKEditor.

    '; + +// Event that will be handled only by the first editor. +$CKEditor->addEventHandler('instanceReady', 'function (evt) { + alert("Loaded editor: " + evt.editor.name); +}'); + +// Create first instance. +$CKEditor->editor("editor1", $initialValue); + +// Clear event handlers, instances that will be created later will not have +// the 'instanceReady' listener defined a couple of lines above. +$CKEditor->clearEventHandlers(); +?> +

    +
    +

    +editor("editor2", $initialValue, $config, $events); +?> +

    + +

    +
    +
    + + + diff --git a/rte/_samples/php/replace.php b/rte/_samples/php/replace.php new file mode 100644 index 0000000..216f903 --- /dev/null +++ b/rte/_samples/php/replace.php @@ -0,0 +1,63 @@ + + + + + Sample - CKEditor + + + + +

    + CKEditor Sample +

    + +
    + +
    + +
    + Output +
    +

    +
    + +

    +

    + +

    +
    +
    + + basePath = '/ckeditor/' + // If not set, CKEditor will try to detect the correct path. + $CKEditor->basePath = '../../'; + // Replace textarea with id (or name) "editor1". + $CKEditor->replace("editor1"); + ?> + + diff --git a/rte/_samples/php/replaceall.php b/rte/_samples/php/replaceall.php new file mode 100644 index 0000000..704deae --- /dev/null +++ b/rte/_samples/php/replaceall.php @@ -0,0 +1,68 @@ + + + + + Sample - CKEditor + + + + +

    + CKEditor Sample +

    + +
    + +
    + +
    + Output +
    +

    +
    + +

    +

    +
    + +

    +

    + +

    +
    +
    + + basePath = '/ckeditor/' + // If not set, CKEditor will try to detect the correct path. + $CKEditor->basePath = '../../'; + // Replace all textareas with CKEditor. + $CKEditor->replaceAll(); + ?> + + diff --git a/rte/_samples/php/standalone.php b/rte/_samples/php/standalone.php new file mode 100644 index 0000000..c070f30 --- /dev/null +++ b/rte/_samples/php/standalone.php @@ -0,0 +1,64 @@ + + + + + Sample - CKEditor + + + + +

    + CKEditor Sample +

    + +
    + +
    + +
    + Output +
    +

    +
    +

    +

    + This is some sample text.

    '; + // Create class instance. + $CKEditor = new CKEditor(); + // Path to CKEditor directory, ideally instead of relative dir, use an absolute path: + // $CKEditor->basePath = '/ckeditor/' + // If not set, CKEditor will try to detect the correct path. + $CKEditor->basePath = '../../'; + // Create textarea element and attach CKEditor to it. + $CKEditor->editor("editor1", $initialValue); + ?> + +

    +
    +
    + + + diff --git a/rte/_samples/replacebyclass.html b/rte/_samples/replacebyclass.html new file mode 100644 index 0000000..6041694 --- /dev/null +++ b/rte/_samples/replacebyclass.html @@ -0,0 +1,49 @@ + + + + + Replace Textareas by Class Name - CKEditor Sample + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +
    +

    +
    + +

    +

    + +

    +
    + + + diff --git a/rte/_samples/replacebycode.html b/rte/_samples/replacebycode.html new file mode 100644 index 0000000..5aeff3a --- /dev/null +++ b/rte/_samples/replacebycode.html @@ -0,0 +1,80 @@ + + + + + Replace Textarea by Code - CKEditor Sample + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +
    +

    +
    + + +

    +

    +
    + + +

    +

    + +

    +
    + + + diff --git a/rte/_samples/sample.css b/rte/_samples/sample.css new file mode 100644 index 0000000..34fecf9 --- /dev/null +++ b/rte/_samples/sample.css @@ -0,0 +1,81 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +fieldset +{ + margin: 0; + padding: 10px; +} + +form +{ + margin: 0; + padding: 0; +} + +pre +{ + background-color: #F7F7F7; + border: 1px solid #D7D7D7; + overflow: auto; + margin: 0; + padding: 0.25em; +} + +#alerts +{ + color: Red; +} + +#footer hr +{ + margin: 10px 0 15px 0; + height: 1px; + border: solid 1px gray; + border-bottom: none; +} + +#footer p +{ + margin: 0 10px 10px 10px; + float: left; +} + +#footer #copy +{ + float: right; +} + +#outputSample +{ + width: 100%; + table-layout: fixed; +} + +#outputSample thead th +{ + color: #dddddd; + background-color: #999999; + padding: 4px; + white-space: nowrap; +} + +#outputSample tbody th +{ + vertical-align: top; + text-align: left; +} + +#outputSample pre +{ + margin: 0; + padding: 0; + white-space: pre; /* CSS2 */ + white-space: -moz-pre-wrap; /* Mozilla*/ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS 2.1 */ + white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */ + word-wrap: break-word; /* IE */ +} diff --git a/rte/_samples/sample.js b/rte/_samples/sample.js new file mode 100644 index 0000000..dda5209 --- /dev/null +++ b/rte/_samples/sample.js @@ -0,0 +1,65 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +// This file is not required by CKEditor and may be safely ignored. +// It is just a helper file that displays a red message about browser compatibility +// at the top of the samples (if incompatible browser is detected). + +if ( window.CKEDITOR ) +{ + (function() + { + var showCompatibilityMsg = function() + { + var env = CKEDITOR.env; + + var html = '

    Your browser is not compatible with CKEditor.'; + + var browsers = + { + gecko : 'Firefox 2.0', + ie : 'Internet Explorer 6.0', + opera : 'Opera 9.5', + webkit : 'Safari 3.0' + }; + + var alsoBrowsers = ''; + + for ( var key in env ) + { + if ( browsers[ key ] ) + { + if ( env[key] ) + html += ' CKEditor is compatible with ' + browsers[ key ] + ' or higher.'; + else + alsoBrowsers += browsers[ key ] + '+, '; + } + } + + alsoBrowsers = alsoBrowsers.replace( /\+,([^,]+), $/, '+ and $1' ); + + html += ' It is also compatible with ' + alsoBrowsers + '.'; + + html += '

    With non compatible browsers, you should still be able to see and edit the contents (HTML) in a plain text field.

    '; + + var alertsEl = document.getElementById( 'alerts' ); + alertsEl && ( alertsEl.innerHTML = html ); + }; + + var onload = function() + { + // Show a friendly compatibility message as soon as the page is loaded, + // for those browsers that are not compatible with CKEditor. + if ( !CKEDITOR.env.isCompatible ) + showCompatibilityMsg(); + }; + + // Register the onload listener. + if ( window.addEventListener ) + window.addEventListener( 'load', onload, false ); + else if ( window.attachEvent ) + window.attachEvent( 'onload', onload ); + })(); +} diff --git a/rte/_samples/sample_posteddata.php b/rte/_samples/sample_posteddata.php new file mode 100644 index 0000000..c50001a --- /dev/null +++ b/rte/_samples/sample_posteddata.php @@ -0,0 +1,59 @@ + + + + + Sample - CKEditor + + + + +

    + CKEditor - Posted Data +

    + + + + + + + + + $value ) +{ + if ( get_magic_quotes_gpc() ) + $postedValue = htmlspecialchars( stripslashes( $value ) ) ; + else + $postedValue = htmlspecialchars( $value ) ; + +?> + + + + + +
    Field NameValue
    + + + diff --git a/rte/_samples/sharedspaces.html b/rte/_samples/sharedspaces.html new file mode 100644 index 0000000..51619ab --- /dev/null +++ b/rte/_samples/sharedspaces.html @@ -0,0 +1,131 @@ + + + + + Shared toolbars - CKEditor Sample + + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +
    +
    +
    +

    +
    + +

    +

    +
    + +

    +

    +
    + +

    +

    +
    + +

    +

    + +

    +
    +
    +
    + + + + diff --git a/rte/_samples/skins.html b/rte/_samples/skins.html new file mode 100644 index 0000000..d44e06f --- /dev/null +++ b/rte/_samples/skins.html @@ -0,0 +1,83 @@ + + + + + Skins - CKEditor Sample + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +
    +

    + "Kama" skin:
    + + +

    +

    + "Office 2003" skin:
    + + +

    +

    + "V2" skin:
    + + +

    +
    + + + diff --git a/rte/_samples/ui_color.html b/rte/_samples/ui_color.html new file mode 100644 index 0000000..ffd9ebb --- /dev/null +++ b/rte/_samples/ui_color.html @@ -0,0 +1,87 @@ + + + + + UI Color Setting Tool - CKEditor Sample + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +

    + Click the UI Color Picker button to test your color preferences at runtime.

    +
    +

    + + +

    +

    + + +

    +

    + +

    +
    + + + diff --git a/rte/_samples/ui_languages.html b/rte/_samples/ui_languages.html new file mode 100644 index 0000000..934cf7e --- /dev/null +++ b/rte/_samples/ui_languages.html @@ -0,0 +1,103 @@ + + + + + User Interface Globalization - CKEditor Sample + + + + + + + +

    + CKEditor Sample +

    + +
    + +
    +
    +

    + Available languages ( languages!):
    + +
    + (You may see strange characters if your system doesn't + support the selected language) +

    +

    + + +

    +
    + + + diff --git a/rte/_source/adapters/jquery.js b/rte/_source/adapters/jquery.js new file mode 100644 index 0000000..e2b9214 --- /dev/null +++ b/rte/_source/adapters/jquery.js @@ -0,0 +1,297 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview jQuery adapter provides easy use of basic CKEditor functions + * and access to internal API. It also integrates some aspects of CKEditor with + * jQuery framework. + * + * Every TEXTAREA, DIV and P elements can be converted to working editor. + * + * Plugin exposes some of editor's event to jQuery event system. All of those are namespaces inside + * ".ckeditor" namespace and can be binded/listened on supported textarea, div and p nodes. + * + * Available jQuery events: + * - instanceReady.ckeditor( editor, rootNode ) + * Triggered when new instance is ready. + * - destroy.ckeditor( editor ) + * Triggered when instance is destroyed. + * - getData.ckeditor( editor, eventData ) + * Triggered when getData event is fired inside editor. It can change returned data using eventData reference. + * - setData.ckeditor( editor ) + * Triggered when getData event is fired inside editor. + * + * @example + * + * + * + */ + +(function() +{ + /** + * Allow CKEditor to override jQuery.fn.val(). This results in ability to use val() + * function on textareas as usual and having those calls synchronized with CKEditor + * Rich Text Editor component. + * + * This config option is global and executed during plugin load. + * Can't be customized across editor instances. + * + * @type Boolean + * @example + * $( 'textarea' ).ckeditor(); + * // ... + * $( 'textarea' ).val( 'New content' ); + */ + CKEDITOR.config.jqueryOverrideVal = typeof CKEDITOR.config.jqueryOverrideVal == 'undefined' + ? true : CKEDITOR.config.jqueryOverrideVal; + + var jQuery = window.jQuery; + + if ( typeof jQuery == 'undefined' ) + return; + + // jQuery object methods. + jQuery.extend( jQuery.fn, + /** @lends jQuery.fn */ + { + /** + * Return existing CKEditor instance for first matched element. + * Allows to easily use internal API. Doesn't return jQuery object. + * + * Raised exception if editor doesn't exist or isn't ready yet. + * + * @name jQuery.ckeditorGet + * @return CKEDITOR.editor + * @see CKEDITOR.editor + */ + ckeditorGet: function() + { + var instance = this.eq( 0 ).data( 'ckeditorInstance' ); + if ( !instance ) + throw "CKEditor not yet initialized, use ckeditor() with callback."; + return instance; + }, + /** + * Triggers creation of CKEditor in all matched elements (reduced to DIV, P and TEXTAREAs). + * Binds callback to instanceReady event of all instances. If editor is already created, than + * callback is fired right away. + * + * Mixed parameter order allowed. + * + * @param callback Function to be run on editor instance. Passed parameters: [ textarea ]. + * Callback is fiered in "this" scope being ckeditor instance and having source textarea as first param. + * + * @param config Configuration options for new instance(s) if not already created. + * See URL + * + * @example + * $( 'textarea' ).ckeditor( function( textarea ) { + * $( textarea ).val( this.getData() ) + * } ); + * + * @name jQuery.fn.ckeditor + * @return jQuery.fn + */ + ckeditor: function( callback, config ) + { + if ( !jQuery.isFunction( callback )) + { + var tmp = config; + config = callback; + callback = tmp; + } + config = config || {}; + + this.filter( 'textarea, div, p' ).each( function() + { + var $element = jQuery( this ), + editor = $element.data( 'ckeditorInstance' ), + instanceLock = $element.data( '_ckeditorInstanceLock' ), + element = this; + + if ( editor && !instanceLock ) + { + if ( callback ) + callback.apply( editor, [ this ] ); + } + else if ( !instanceLock ) + { + // CREATE NEW INSTANCE + + // Handle config.autoUpdateElement inside this plugin if desired. + if ( config.autoUpdateElement + || ( typeof config.autoUpdateElement == 'undefined' && CKEDITOR.config.autoUpdateElement ) ) + { + config.autoUpdateElementJquery = true; + } + + // Always disable config.autoUpdateElement. + config.autoUpdateElement = false; + $element.data( '_ckeditorInstanceLock', true ); + + // Set instance reference in element's data. + editor = CKEDITOR.replace( element, config ); + $element.data( 'ckeditorInstance', editor ); + + // Register callback. + editor.on( 'instanceReady', function( event ) + { + var editor = event.editor; + setTimeout( function() + { + // Delay bit more if editor is still not ready. + if ( !editor.element ) + { + setTimeout( arguments.callee, 100 ); + return; + } + + // Remove this listener. + event.removeListener( 'instanceReady', this.callee ); + + // Forward setData on dataReady. + editor.on( 'dataReady', function() + { + $element.trigger( 'setData' + '.ckeditor', [ editor ] ); + }); + + // Forward getData. + editor.on( 'getData', function( event ) { + $element.trigger( 'getData' + '.ckeditor', [ editor, event.data ] ); + }, 999 ); + + // Forward destroy event. + editor.on( 'destroy', function() + { + $element.trigger( 'destroy.ckeditor', [ editor ] ); + }); + + // Integrate with form submit. + if ( editor.config.autoUpdateElementJquery && $element.is( 'textarea' ) && $element.parents( 'form' ).length ) + { + var onSubmit = function() + { + $element.ckeditor( function() + { + editor.updateElement(); + }); + }; + + // Bind to submit event. + $element.parents( 'form' ).submit( onSubmit ); + + // Bind to form-pre-serialize from jQuery Forms plugin. + $element.parents( 'form' ).bind( 'form-pre-serialize', onSubmit ); + + // Unbind when editor destroyed. + $element.bind( 'destroy.ckeditor', function() + { + $element.parents( 'form' ).unbind( 'submit', onSubmit ); + $element.parents( 'form' ).unbind( 'form-pre-serialize', onSubmit ); + }); + } + + // Garbage collect on destroy. + editor.on( 'destroy', function() + { + $element.data( 'ckeditorInstance', null ); + }); + + // Remove lock. + $element.data( '_ckeditorInstanceLock', null ); + + // Fire instanceReady event. + $element.trigger( 'instanceReady.ckeditor', [ editor ] ); + + // Run given (first) code. + if ( callback ) + callback.apply( editor, [ element ] ); + }, 0 ); + }, null, null, 9999); + } + else + { + // Editor is already during creation process, bind our code to the event. + CKEDITOR.on( 'instanceReady', function( event ) + { + var editor = event.editor; + setTimeout( function() + { + // Delay bit more if editor is still not ready. + if ( !editor.element ) + { + setTimeout( arguments.callee, 100 ); + return; + } + + if ( editor.element.$ == element ) + { + // Run given code. + if ( callback ) + callback.apply( editor, [ element ] ); + } + }, 0 ); + }, null, null, 9999); + } + }); + return this; + } + }); + + // New val() method for objects. + if ( CKEDITOR.config.jqueryOverrideVal ) + { + jQuery.fn.val = CKEDITOR.tools.override( jQuery.fn.val, function( oldValMethod ) + { + /** + * CKEditor-aware val() method. + * + * Acts same as original jQuery val(), but for textareas which have CKEditor instances binded to them, method + * returns editor's content. It also works for settings values. + * + * @param oldValMethod + * @name jQuery.fn.val + */ + return function( newValue, forceNative ) + { + var isSetter = typeof newValue != 'undefined', + result; + + this.each( function() + { + var $this = jQuery( this ), + editor = $this.data( 'ckeditorInstance' ); + + if ( !forceNative && $this.is( 'textarea' ) && editor ) + { + if ( isSetter ) + editor.setData( newValue ); + else + { + result = editor.getData(); + // break; + return null; + } + } + else + { + if ( isSetter ) + oldValMethod.call( $this, newValue ); + else + { + result = oldValMethod.call( $this ); + // break; + return null; + } + } + + return true; + }); + return isSetter ? this : result; + }; + }); + } +})(); diff --git a/rte/_source/core/_bootstrap.js b/rte/_source/core/_bootstrap.js new file mode 100644 index 0000000..3904b75 --- /dev/null +++ b/rte/_source/core/_bootstrap.js @@ -0,0 +1,91 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview API initialization code. + */ + +(function() +{ + // Disable HC detaction in WebKit. (#5429) + if ( CKEDITOR.env.webkit ) + { + CKEDITOR.env.hc = false; + return; + } + + // Check is High Contrast is active by creating a temporary element with a + // background image. + + var useSpacer = CKEDITOR.env.ie && CKEDITOR.env.version < 7, + useBlank = CKEDITOR.env.ie && CKEDITOR.env.version == 7; + + var backgroundImageUrl = useSpacer ? ( CKEDITOR.basePath + 'images/spacer.gif' ) : + useBlank ? 'about:blank' : 'data:image/png;base64,'; + + var hcDetect = CKEDITOR.dom.element.createFromHtml( + '
    ', CKEDITOR.document ); + + hcDetect.appendTo( CKEDITOR.document.getHead() ); + + // Update CKEDITOR.env. + // Catch exception needed sometimes for FF. (#4230) + try + { + CKEDITOR.env.hc = ( hcDetect.getComputedStyle( 'background-image' ) == 'none' ); + } + catch (e) + { + CKEDITOR.env.hc = false; + } + + if ( CKEDITOR.env.hc ) + CKEDITOR.env.cssClass += ' cke_hc'; + + hcDetect.remove(); +})(); + +// Load core plugins. +CKEDITOR.plugins.load( CKEDITOR.config.corePlugins.split( ',' ), function() + { + CKEDITOR.status = 'loaded'; + CKEDITOR.fire( 'loaded' ); + + // Process all instances created by the "basic" implementation. + var pending = CKEDITOR._.pending; + if ( pending ) + { + delete CKEDITOR._.pending; + + for ( var i = 0 ; i < pending.length ; i++ ) + CKEDITOR.add( pending[ i ] ); + } + }); + +/* +TODO: Enable the following and check if effective. + +if ( CKEDITOR.env.ie ) +{ + // Remove IE mouse flickering on IE6 because of background images. + try + { + document.execCommand( 'BackgroundImageCache', false, true ); + } + catch (e) + { + // We have been reported about loading problems caused by the above + // line. For safety, let's just ignore errors. + } +} +*/ + +/** + * Fired when a CKEDITOR core object is fully loaded and ready for interaction. + * @name CKEDITOR#loaded + * @event + */ diff --git a/rte/_source/core/ajax.js b/rte/_source/core/ajax.js new file mode 100644 index 0000000..176d1a2 --- /dev/null +++ b/rte/_source/core/ajax.js @@ -0,0 +1,143 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.ajax} object, which holds ajax methods for + * data loading. + */ + +/** + * Ajax methods for data loading. + * @namespace + * @example + */ +CKEDITOR.ajax = (function() +{ + var createXMLHttpRequest = function() + { + // In IE, using the native XMLHttpRequest for local files may throw + // "Access is Denied" errors. + if ( !CKEDITOR.env.ie || location.protocol != 'file:' ) + try { return new XMLHttpRequest(); } catch(e) {} + + try { return new ActiveXObject( 'Msxml2.XMLHTTP' ); } catch (e) {} + try { return new ActiveXObject( 'Microsoft.XMLHTTP' ); } catch (e) {} + + return null; + }; + + var checkStatus = function( xhr ) + { + // HTTP Status Codes: + // 2xx : Success + // 304 : Not Modified + // 0 : Returned when running locally (file://) + // 1223 : IE may change 204 to 1223 (see http://dev.jquery.com/ticket/1450) + + return ( xhr.readyState == 4 && + ( ( xhr.status >= 200 && xhr.status < 300 ) || + xhr.status == 304 || + xhr.status === 0 || + xhr.status == 1223 ) ); + }; + + var getResponseText = function( xhr ) + { + if ( checkStatus( xhr ) ) + return xhr.responseText; + return null; + }; + + var getResponseXml = function( xhr ) + { + if ( checkStatus( xhr ) ) + { + var xml = xhr.responseXML; + return new CKEDITOR.xml( xml && xml.firstChild ? xml : xhr.responseText ); + } + return null; + }; + + var load = function( url, callback, getResponseFn ) + { + var async = !!callback; + + var xhr = createXMLHttpRequest(); + + if ( !xhr ) + return null; + + xhr.open( 'GET', url, async ); + + if ( async ) + { + // TODO: perform leak checks on this closure. + /** @ignore */ + xhr.onreadystatechange = function() + { + if ( xhr.readyState == 4 ) + { + callback( getResponseFn( xhr ) ); + xhr = null; + } + }; + } + + xhr.send(null); + + return async ? '' : getResponseFn( xhr ); + }; + + return /** @lends CKEDITOR.ajax */ { + + /** + * Loads data from an URL as plain text. + * @param {String} url The URL from which load data. + * @param {Function} [callback] A callback function to be called on + * data load. If not provided, the data will be loaded + * asynchronously, passing the data value the function on load. + * @returns {String} The loaded data. For asynchronous requests, an + * empty string. For invalid requests, null. + * @example + * // Load data synchronously. + * var data = CKEDITOR.ajax.load( 'somedata.txt' ); + * alert( data ); + * @example + * // Load data asynchronously. + * var data = CKEDITOR.ajax.load( 'somedata.txt', function( data ) + * { + * alert( data ); + * } ); + */ + load : function( url, callback ) + { + return load( url, callback, getResponseText ); + }, + + /** + * Loads data from an URL as XML. + * @param {String} url The URL from which load data. + * @param {Function} [callback] A callback function to be called on + * data load. If not provided, the data will be loaded + * asynchronously, passing the data value the function on load. + * @returns {CKEDITOR.xml} An XML object holding the loaded data. For asynchronous requests, an + * empty string. For invalid requests, null. + * @example + * // Load XML synchronously. + * var xml = CKEDITOR.ajax.loadXml( 'somedata.xml' ); + * alert( xml.getInnerXml( '//' ) ); + * @example + * // Load XML asynchronously. + * var data = CKEDITOR.ajax.loadXml( 'somedata.xml', function( xml ) + * { + * alert( xml.getInnerXml( '//' ) ); + * } ); + */ + loadXml : function( url, callback ) + { + return load( url, callback, getResponseXml ); + } + }; +})(); diff --git a/rte/_source/core/ckeditor.js b/rte/_source/core/ckeditor.js new file mode 100644 index 0000000..5a189b0 --- /dev/null +++ b/rte/_source/core/ckeditor.js @@ -0,0 +1,113 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Contains the third and last part of the {@link CKEDITOR} object + * definition. + */ + +// Remove the CKEDITOR.loadFullCore reference defined on ckeditor_basic. +delete CKEDITOR.loadFullCore; + +/** + * Holds references to all editor instances created. The name of the properties + * in this object correspond to instance names, and their values contains the + * {@link CKEDITOR.editor} object representing them. + * @type {Object} + * @example + * alert( CKEDITOR.instances.editor1.name ); // "editor1" + */ +CKEDITOR.instances = {}; + +/** + * The document of the window holding the CKEDITOR object. + * @type {CKEDITOR.dom.document} + * @example + * alert( CKEDITOR.document.getBody().getName() ); // "body" + */ +CKEDITOR.document = new CKEDITOR.dom.document( document ); + +/** + * Adds an editor instance to the global {@link CKEDITOR} object. This function + * is available for internal use mainly. + * @param {CKEDITOR.editor} editor The editor instance to be added. + * @example + */ +CKEDITOR.add = function( editor ) +{ + CKEDITOR.instances[ editor.name ] = editor; + + editor.on( 'focus', function() + { + if ( CKEDITOR.currentInstance != editor ) + { + CKEDITOR.currentInstance = editor; + CKEDITOR.fire( 'currentInstance' ); + } + }); + + editor.on( 'blur', function() + { + if ( CKEDITOR.currentInstance == editor ) + { + CKEDITOR.currentInstance = null; + CKEDITOR.fire( 'currentInstance' ); + } + }); +}; + +/** + * Removes and editor instance from the global {@link CKEDITOR} object. his function + * is available for internal use mainly. + * @param {CKEDITOR.editor} editor The editor instance to be added. + * @example + */ +CKEDITOR.remove = function( editor ) +{ + delete CKEDITOR.instances[ editor.name ]; +}; + +/** + * Perform global clean up to free as much memory as possible + * when there are no instances left + */ +CKEDITOR.on( 'instanceDestroyed', function () + { + if ( CKEDITOR.tools.isEmpty( this.instances ) ) + CKEDITOR.fire( 'reset' ); + }); + +// Load the bootstrap script. +CKEDITOR.loader.load( 'core/_bootstrap' ); // @Packager.RemoveLine + +// Tri-state constants. + +/** + * Used to indicate the ON or ACTIVE state. + * @constant + * @example + */ +CKEDITOR.TRISTATE_ON = 1; + +/** + * Used to indicate the OFF or NON ACTIVE state. + * @constant + * @example + */ +CKEDITOR.TRISTATE_OFF = 2; + +/** + * Used to indicate DISABLED state. + * @constant + * @example + */ +CKEDITOR.TRISTATE_DISABLED = 0; + +/** + * Fired when the CKEDITOR.currentInstance object reference changes. This may + * happen when setting the focus on different editor instances in the page. + * @name CKEDITOR#currentInstance + * @event + */ diff --git a/rte/_source/core/ckeditor_base.js b/rte/_source/core/ckeditor_base.js new file mode 100644 index 0000000..4bdc776 --- /dev/null +++ b/rte/_source/core/ckeditor_base.js @@ -0,0 +1,193 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Contains the first and essential part of the {@link CKEDITOR} + * object definition. + */ + +// #### Compressed Code +// Must be updated on changes in the script, as well as updated in the +// ckeditor_source.js and ckeditor_basic_source.js files. + +// if(!window.CKEDITOR)window.CKEDITOR=(function(){var a={timestamp:'',version:'3.3.1',rev:'5586',_:{},status:'unloaded',basePath:(function(){var d=window.CKEDITOR_BASEPATH||'';if(!d){var e=document.getElementsByTagName('script');for(var f=0;f=0?'&':'?')+('t=')+this.timestamp;return d;}},b=window.CKEDITOR_GETURL;if(b){var c=a.getUrl;a.getUrl=function(d){return b.call(a,d)||c.call(a,d);};}return a;})(); + +// #### Raw code +// ATTENTION: read the above "Compressed Code" notes when changing this code. + +if ( !window.CKEDITOR ) +{ + /** + * This is the API entry point. The entire CKEditor code runs under this object. + * @name CKEDITOR + * @namespace + * @example + */ + window.CKEDITOR = (function() + { + var CKEDITOR = + /** @lends CKEDITOR */ + { + + /** + * A constant string unique for each release of CKEditor. Its value + * is used, by default, to build the URL for all resources loaded + * by the editor code, guaranteing clean cache results when + * upgrading. + * @type String + * @example + * alert( CKEDITOR.timestamp ); // e.g. '87dm' + */ + // The production implementation contains a fixed timestamp, unique + // for each release, generated by the releaser. + // (Base 36 value of each component of YYMMDDHH - 4 chars total - e.g. 87bm == 08071122) + timestamp : 'A5AB4B6', + + /** + * Contains the CKEditor version number. + * @type String + * @example + * alert( CKEDITOR.version ); // e.g. 'CKEditor 3.0 Beta' + */ + version : '3.3.1', + + /** + * Contains the CKEditor revision number. + * Revision number is incremented automatically after each modification of CKEditor source code. + * @type String + * @example + * alert( CKEDITOR.revision ); // e.g. '3975' + */ + revision : '5586', + + /** + * Private object used to hold core stuff. It should not be used out of + * the API code as properties defined here may change at any time + * without notice. + * @private + */ + _ : {}, + + /** + * Indicates the API loading status. The following status are available: + *
      + *
    • unloaded: the API is not yet loaded.
    • + *
    • basic_loaded: the basic API features are available.
    • + *
    • basic_ready: the basic API is ready to load the full core code.
    • + *
    • loading: the full API is being loaded.
    • + *
    • ready: the API can be fully used.
    • + *
    + * @type String + * @example + * if ( CKEDITOR.status == 'ready' ) + * { + * // The API can now be fully used. + * } + */ + status : 'unloaded', + + /** + * Contains the full URL for the CKEditor installation directory. + * It's possible to manually provide the base path by setting a + * global variable named CKEDITOR_BASEPATH. This global variable + * must be set "before" the editor script loading. + * @type String + * @example + * alert( CKEDITOR.basePath ); // "http://www.example.com/ckeditor/" (e.g.) + */ + basePath : (function() + { + // ATTENTION: fixes on this code must be ported to + // var basePath in "core/loader.js". + + // Find out the editor directory path, based on its ")' ); + } + } + + return $ && new CKEDITOR.dom.document( $.contentWindow.document ); + }, + + /** + * Copy all the attributes from one node to the other, kinda like a clone + * skipAttributes is an object with the attributes that must NOT be copied. + * @param {CKEDITOR.dom.element} dest The destination element. + * @param {Object} skipAttributes A dictionary of attributes to skip. + * @example + */ + copyAttributes : function( dest, skipAttributes ) + { + var attributes = this.$.attributes; + skipAttributes = skipAttributes || {}; + + for ( var n = 0 ; n < attributes.length ; n++ ) + { + var attribute = attributes[n]; + + // Lowercase attribute name hard rule is broken for + // some attribute on IE, e.g. CHECKED. + var attrName = attribute.nodeName.toLowerCase(), + attrValue; + + // We can set the type only once, so do it with the proper value, not copying it. + if ( attrName in skipAttributes ) + continue; + + if ( attrName == 'checked' && ( attrValue = this.getAttribute( attrName ) ) ) + dest.setAttribute( attrName, attrValue ); + // IE BUG: value attribute is never specified even if it exists. + else if ( attribute.specified || + ( CKEDITOR.env.ie && attribute.nodeValue && attrName == 'value' ) ) + { + attrValue = this.getAttribute( attrName ); + if ( attrValue === null ) + attrValue = attribute.nodeValue; + + dest.setAttribute( attrName, attrValue ); + } + } + + // The style: + if ( this.$.style.cssText !== '' ) + dest.$.style.cssText = this.$.style.cssText; + }, + + /** + * Changes the tag name of the current element. + * @param {String} newTag The new tag for the element. + */ + renameNode : function( newTag ) + { + // If it's already correct exit here. + if ( this.getName() == newTag ) + return; + + var doc = this.getDocument(); + + // Create the new node. + var newNode = new CKEDITOR.dom.element( newTag, doc ); + + // Copy all attributes. + this.copyAttributes( newNode ); + + // Move children to the new node. + this.moveChildren( newNode ); + + // Replace the node. + this.getParent() && this.$.parentNode.replaceChild( newNode.$, this.$ ); + newNode.$._cke_expando = this.$._cke_expando; + this.$ = newNode.$; + }, + + /** + * Gets a DOM tree descendant under the current node. + * @param {Array|Number} indices The child index or array of child indices under the node. + * @returns {CKEDITOR.dom.node} The specified DOM child under the current node. Null if child does not exist. + * @example + * var strong = p.getChild(0); + */ + getChild : function( indices ) + { + var rawNode = this.$; + + if ( !indices.slice ) + rawNode = rawNode.childNodes[ indices ]; + else + { + while ( indices.length > 0 && rawNode ) + rawNode = rawNode.childNodes[ indices.shift() ]; + } + + return rawNode ? new CKEDITOR.dom.node( rawNode ) : null; + }, + + getChildCount : function() + { + return this.$.childNodes.length; + }, + + disableContextMenu : function() + { + this.on( 'contextmenu', function( event ) + { + // Cancel the browser context menu. + if ( !event.data.getTarget().hasClass( 'cke_enable_context_menu' ) ) + event.data.preventDefault(); + } ); + } + }); diff --git a/rte/_source/core/dom/elementpath.js b/rte/_source/core/dom/elementpath.js new file mode 100644 index 0000000..dc417d6 --- /dev/null +++ b/rte/_source/core/dom/elementpath.js @@ -0,0 +1,116 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + // Elements that may be considered the "Block boundary" in an element path. + var pathBlockElements = { address:1,blockquote:1,dl:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,p:1,pre:1,li:1,dt:1,dd:1 }; + + // Elements that may be considered the "Block limit" in an element path. + var pathBlockLimitElements = { body:1,div:1,table:1,tbody:1,tr:1,td:1,th:1,caption:1,form:1 }; + + // Check if an element contains any block element. + var checkHasBlock = function( element ) + { + var childNodes = element.getChildren(); + + for ( var i = 0, count = childNodes.count() ; i < count ; i++ ) + { + var child = childNodes.getItem( i ); + + if ( child.type == CKEDITOR.NODE_ELEMENT && CKEDITOR.dtd.$block[ child.getName() ] ) + return true; + } + + return false; + }; + + CKEDITOR.dom.elementPath = function( lastNode ) + { + var block = null; + var blockLimit = null; + var elements = []; + + var e = lastNode; + + while ( e ) + { + if ( e.type == CKEDITOR.NODE_ELEMENT ) + { + if ( !this.lastElement ) + this.lastElement = e; + + var elementName = e.getName(); + if ( CKEDITOR.env.ie && e.$.scopeName != 'HTML' ) + elementName = e.$.scopeName.toLowerCase() + ':' + elementName; + + if ( !blockLimit ) + { + if ( !block && pathBlockElements[ elementName ] ) + block = e; + + if ( pathBlockLimitElements[ elementName ] ) + { + // DIV is considered the Block, if no block is available (#525) + // and if it doesn't contain other blocks. + if ( !block && elementName == 'div' && !checkHasBlock( e ) ) + block = e; + else + blockLimit = e; + } + } + + elements.push( e ); + + if ( elementName == 'body' ) + break; + } + e = e.getParent(); + } + + this.block = block; + this.blockLimit = blockLimit; + this.elements = elements; + }; +})(); + +CKEDITOR.dom.elementPath.prototype = +{ + /** + * Compares this element path with another one. + * @param {CKEDITOR.dom.elementPath} otherPath The elementPath object to be + * compared with this one. + * @returns {Boolean} "true" if the paths are equal, containing the same + * number of elements and the same elements in the same order. + */ + compare : function( otherPath ) + { + var thisElements = this.elements; + var otherElements = otherPath && otherPath.elements; + + if ( !otherElements || thisElements.length != otherElements.length ) + return false; + + for ( var i = 0 ; i < thisElements.length ; i++ ) + { + if ( !thisElements[ i ].equals( otherElements[ i ] ) ) + return false; + } + + return true; + }, + + contains : function( tagNames ) + { + var elements = this.elements; + for ( var i = 0 ; i < elements.length ; i++ ) + { + if ( elements[ i ].getName() in tagNames ) + return elements[ i ]; + } + + return null; + } +}; diff --git a/rte/_source/core/dom/event.js b/rte/_source/core/dom/event.js new file mode 100644 index 0000000..24b75d0 --- /dev/null +++ b/rte/_source/core/dom/event.js @@ -0,0 +1,142 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.dom.event} class, which + * represents the a native DOM event object. + */ + +/** + * Represents a native DOM event object. + * @constructor + * @param {Object} domEvent A native DOM event object. + * @example + */ +CKEDITOR.dom.event = function( domEvent ) +{ + /** + * The native DOM event object represented by this class instance. + * @type Object + * @example + */ + this.$ = domEvent; +}; + +CKEDITOR.dom.event.prototype = +{ + /** + * Gets the key code associated to the event. + * @returns {Number} The key code. + * @example + * alert( event.getKey() ); "65" is "a" has been pressed + */ + getKey : function() + { + return this.$.keyCode || this.$.which; + }, + + /** + * Gets a number represeting the combination of the keys pressed during the + * event. It is the sum with the current key code and the {@link CKEDITOR.CTRL}, + * {@link CKEDITOR.SHIFT} and {@link CKEDITOR.ALT} constants. + * @returns {Number} The number representing the keys combination. + * @example + * alert( event.getKeystroke() == 65 ); // "a" key + * alert( event.getKeystroke() == CKEDITOR.CTRL + 65 ); // CTRL + "a" key + * alert( event.getKeystroke() == CKEDITOR.CTRL + CKEDITOR.SHIFT + 65 ); // CTRL + SHIFT + "a" key + */ + getKeystroke : function() + { + var keystroke = this.getKey(); + + if ( this.$.ctrlKey || this.$.metaKey ) + keystroke += CKEDITOR.CTRL; + + if ( this.$.shiftKey ) + keystroke += CKEDITOR.SHIFT; + + if ( this.$.altKey ) + keystroke += CKEDITOR.ALT; + + return keystroke; + }, + + /** + * Prevents the original behavior of the event to happen. It can optionally + * stop propagating the event in the event chain. + * @param {Boolean} [stopPropagation] Stop propagating this event in the + * event chain. + * @example + * var element = CKEDITOR.document.getById( 'myElement' ); + * element.on( 'click', function( ev ) + * { + * // The DOM event object is passed by the "data" property. + * var domEvent = ev.data; + * // Prevent the click to chave any effect in the element. + * domEvent.preventDefault(); + * }); + */ + preventDefault : function( stopPropagation ) + { + var $ = this.$; + if ( $.preventDefault ) + $.preventDefault(); + else + $.returnValue = false; + + if ( stopPropagation ) + this.stopPropagation(); + }, + + stopPropagation : function() + { + var $ = this.$; + if ( $.stopPropagation ) + $.stopPropagation(); + else + $.cancelBubble = true; + }, + + /** + * Returns the DOM node where the event was targeted to. + * @returns {CKEDITOR.dom.node} The target DOM node. + * @example + * var element = CKEDITOR.document.getById( 'myElement' ); + * element.on( 'click', function( ev ) + * { + * // The DOM event object is passed by the "data" property. + * var domEvent = ev.data; + * // Add a CSS class to the event target. + * domEvent.getTarget().addClass( 'clicked' ); + * }); + */ + + getTarget : function() + { + var rawNode = this.$.target || this.$.srcElement; + return rawNode ? new CKEDITOR.dom.node( rawNode ) : null; + } +}; + +/** + * CTRL key (1000). + * @constant + * @example + */ +CKEDITOR.CTRL = 1000; + +/** + * SHIFT key (2000). + * @constant + * @example + */ +CKEDITOR.SHIFT = 2000; + +/** + * ALT key (4000). + * @constant + * @example + */ +CKEDITOR.ALT = 4000; diff --git a/rte/_source/core/dom/node.js b/rte/_source/core/dom/node.js new file mode 100644 index 0000000..d9a76ee --- /dev/null +++ b/rte/_source/core/dom/node.js @@ -0,0 +1,662 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.dom.node} class, which is the base + * class for classes that represent DOM nodes. + */ + +/** + * Base class for classes representing DOM nodes. This constructor may return + * and instance of classes that inherits this class, like + * {@link CKEDITOR.dom.element} or {@link CKEDITOR.dom.text}. + * @augments CKEDITOR.dom.domObject + * @param {Object} domNode A native DOM node. + * @constructor + * @see CKEDITOR.dom.element + * @see CKEDITOR.dom.text + * @example + */ +CKEDITOR.dom.node = function( domNode ) +{ + if ( domNode ) + { + switch ( domNode.nodeType ) + { + // Safari don't consider document as element node type. (#3389) + case CKEDITOR.NODE_DOCUMENT : + return new CKEDITOR.dom.document( domNode ); + + case CKEDITOR.NODE_ELEMENT : + return new CKEDITOR.dom.element( domNode ); + + case CKEDITOR.NODE_TEXT : + return new CKEDITOR.dom.text( domNode ); + } + + // Call the base constructor. + CKEDITOR.dom.domObject.call( this, domNode ); + } + + return this; +}; + +CKEDITOR.dom.node.prototype = new CKEDITOR.dom.domObject(); + +/** + * Element node type. + * @constant + * @example + */ +CKEDITOR.NODE_ELEMENT = 1; + +/** + * Document node type. + * @constant + * @example + */ +CKEDITOR.NODE_DOCUMENT = 9; + +/** + * Text node type. + * @constant + * @example + */ +CKEDITOR.NODE_TEXT = 3; + +/** + * Comment node type. + * @constant + * @example + */ +CKEDITOR.NODE_COMMENT = 8; + +CKEDITOR.NODE_DOCUMENT_FRAGMENT = 11; + +CKEDITOR.POSITION_IDENTICAL = 0; +CKEDITOR.POSITION_DISCONNECTED = 1; +CKEDITOR.POSITION_FOLLOWING = 2; +CKEDITOR.POSITION_PRECEDING = 4; +CKEDITOR.POSITION_IS_CONTAINED = 8; +CKEDITOR.POSITION_CONTAINS = 16; + +CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, + /** @lends CKEDITOR.dom.node.prototype */ + { + /** + * Makes this node child of another element. + * @param {CKEDITOR.dom.element} element The target element to which append + * this node. + * @returns {CKEDITOR.dom.element} The target element. + * @example + * var p = new CKEDITOR.dom.element( 'p' ); + * var strong = new CKEDITOR.dom.element( 'strong' ); + * strong.appendTo( p ); + * + * // result: "<p><strong></strong></p>" + */ + appendTo : function( element, toStart ) + { + element.append( this, toStart ); + return element; + }, + + clone : function( includeChildren, cloneId ) + { + var $clone = this.$.cloneNode( includeChildren ); + + if ( !cloneId ) + { + var removeIds = function( node ) + { + if ( node.nodeType != CKEDITOR.NODE_ELEMENT ) + return; + + node.removeAttribute( 'id', false ) ; + node.removeAttribute( '_cke_expando', false ) ; + + var childs = node.childNodes; + for ( var i=0 ; i < childs.length ; i++ ) + removeIds( childs[ i ] ); + }; + + // The "id" attribute should never be cloned to avoid duplication. + removeIds( $clone ); + } + + return new CKEDITOR.dom.node( $clone ); + }, + + hasPrevious : function() + { + return !!this.$.previousSibling; + }, + + hasNext : function() + { + return !!this.$.nextSibling; + }, + + /** + * Inserts this element after a node. + * @param {CKEDITOR.dom.node} node The that will preceed this element. + * @returns {CKEDITOR.dom.node} The node preceeding this one after + * insertion. + * @example + * var em = new CKEDITOR.dom.element( 'em' ); + * var strong = new CKEDITOR.dom.element( 'strong' ); + * strong.insertAfter( em ); + * + * // result: "<em></em><strong></strong>" + */ + insertAfter : function( node ) + { + node.$.parentNode.insertBefore( this.$, node.$.nextSibling ); + return node; + }, + + /** + * Inserts this element before a node. + * @param {CKEDITOR.dom.node} node The that will be after this element. + * @returns {CKEDITOR.dom.node} The node being inserted. + * @example + * var em = new CKEDITOR.dom.element( 'em' ); + * var strong = new CKEDITOR.dom.element( 'strong' ); + * strong.insertBefore( em ); + * + * // result: "<strong></strong><em></em>" + */ + insertBefore : function( node ) + { + node.$.parentNode.insertBefore( this.$, node.$ ); + return node; + }, + + insertBeforeMe : function( node ) + { + this.$.parentNode.insertBefore( node.$, this.$ ); + return node; + }, + + /** + * Retrieves a uniquely identifiable tree address for this node. + * The tree address returns is an array of integers, with each integer + * indicating a child index of a DOM node, starting from + * document.documentElement. + * + * For example, assuming is the second child from ( + * being the first), and we'd like to address the third child under the + * fourth child of body, the tree address returned would be: + * [1, 3, 2] + * + * The tree address cannot be used for finding back the DOM tree node once + * the DOM tree structure has been modified. + */ + getAddress : function( normalized ) + { + var address = []; + var $documentElement = this.getDocument().$.documentElement; + var node = this.$; + + while ( node && node != $documentElement ) + { + var parentNode = node.parentNode; + var currentIndex = -1; + + if ( parentNode ) + { + for ( var i = 0 ; i < parentNode.childNodes.length ; i++ ) + { + var candidate = parentNode.childNodes[i]; + + if ( normalized && + candidate.nodeType == 3 && + candidate.previousSibling && + candidate.previousSibling.nodeType == 3 ) + { + continue; + } + + currentIndex++; + + if ( candidate == node ) + break; + } + + address.unshift( currentIndex ); + } + + node = parentNode; + } + + return address; + }, + + /** + * Gets the document containing this element. + * @returns {CKEDITOR.dom.document} The document. + * @example + * var element = CKEDITOR.document.getById( 'example' ); + * alert( element.getDocument().equals( CKEDITOR.document ) ); // "true" + */ + getDocument : function() + { + var document = new CKEDITOR.dom.document( this.$.ownerDocument || this.$.parentNode.ownerDocument ); + + return ( + this.getDocument = function() + { + return document; + })(); + }, + + getIndex : function() + { + var $ = this.$; + + var currentNode = $.parentNode && $.parentNode.firstChild; + var currentIndex = -1; + + while ( currentNode ) + { + currentIndex++; + + if ( currentNode == $ ) + return currentIndex; + + currentNode = currentNode.nextSibling; + } + + return -1; + }, + + getNextSourceNode : function( startFromSibling, nodeType, guard ) + { + // If "guard" is a node, transform it in a function. + if ( guard && !guard.call ) + { + var guardNode = guard; + guard = function( node ) + { + return !node.equals( guardNode ); + }; + } + + var node = ( !startFromSibling && this.getFirst && this.getFirst() ), + parent; + + // Guarding when we're skipping the current element( no children or 'startFromSibling' ). + // send the 'moving out' signal even we don't actually dive into. + if ( !node ) + { + if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false ) + return null; + node = this.getNext(); + } + + while ( !node && ( parent = ( parent || this ).getParent() ) ) + { + // The guard check sends the "true" paramenter to indicate that + // we are moving "out" of the element. + if ( guard && guard( parent, true ) === false ) + return null; + + node = parent.getNext(); + } + + if ( !node ) + return null; + + if ( guard && guard( node ) === false ) + return null; + + if ( nodeType && nodeType != node.type ) + return node.getNextSourceNode( false, nodeType, guard ); + + return node; + }, + + getPreviousSourceNode : function( startFromSibling, nodeType, guard ) + { + if ( guard && !guard.call ) + { + var guardNode = guard; + guard = function( node ) + { + return !node.equals( guardNode ); + }; + } + + var node = ( !startFromSibling && this.getLast && this.getLast() ), + parent; + + // Guarding when we're skipping the current element( no children or 'startFromSibling' ). + // send the 'moving out' signal even we don't actually dive into. + if ( !node ) + { + if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false ) + return null; + node = this.getPrevious(); + } + + while ( !node && ( parent = ( parent || this ).getParent() ) ) + { + // The guard check sends the "true" paramenter to indicate that + // we are moving "out" of the element. + if ( guard && guard( parent, true ) === false ) + return null; + + node = parent.getPrevious(); + } + + if ( !node ) + return null; + + if ( guard && guard( node ) === false ) + return null; + + if ( nodeType && node.type != nodeType ) + return node.getPreviousSourceNode( false, nodeType, guard ); + + return node; + }, + + getPrevious : function( evaluator ) + { + var previous = this.$, retval; + do + { + previous = previous.previousSibling; + retval = previous && new CKEDITOR.dom.node( previous ); + } + while ( retval && evaluator && !evaluator( retval ) ) + return retval; + }, + + /** + * Gets the node that follows this element in its parent's child list. + * @param {Function} evaluator Filtering the result node. + * @returns {CKEDITOR.dom.node} The next node or null if not available. + * @example + * var element = CKEDITOR.dom.element.createFromHtml( '<div><b>Example</b> <i>next</i></div>' ); + * var first = element.getFirst().getNext(); + * alert( first.getName() ); // "i" + */ + getNext : function( evaluator ) + { + var next = this.$, retval; + do + { + next = next.nextSibling; + retval = next && new CKEDITOR.dom.node( next ); + } + while ( retval && evaluator && !evaluator( retval ) ) + return retval; + }, + + /** + * Gets the parent element for this node. + * @returns {CKEDITOR.dom.element} The parent element. + * @example + * var node = editor.document.getBody().getFirst(); + * var parent = node.getParent(); + * alert( node.getName() ); // "body" + */ + getParent : function() + { + var parent = this.$.parentNode; + return ( parent && parent.nodeType == 1 ) ? new CKEDITOR.dom.node( parent ) : null; + }, + + getParents : function( closerFirst ) + { + var node = this; + var parents = []; + + do + { + parents[ closerFirst ? 'push' : 'unshift' ]( node ); + } + while ( ( node = node.getParent() ) ) + + return parents; + }, + + getCommonAncestor : function( node ) + { + if ( node.equals( this ) ) + return this; + + if ( node.contains && node.contains( this ) ) + return node; + + var start = this.contains ? this : this.getParent(); + + do + { + if ( start.contains( node ) ) + return start; + } + while ( ( start = start.getParent() ) ); + + return null; + }, + + getPosition : function( otherNode ) + { + var $ = this.$; + var $other = otherNode.$; + + if ( $.compareDocumentPosition ) + return $.compareDocumentPosition( $other ); + + // IE and Safari have no support for compareDocumentPosition. + + if ( $ == $other ) + return CKEDITOR.POSITION_IDENTICAL; + + // Only element nodes support contains and sourceIndex. + if ( this.type == CKEDITOR.NODE_ELEMENT && otherNode.type == CKEDITOR.NODE_ELEMENT ) + { + if ( $.contains ) + { + if ( $.contains( $other ) ) + return CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING; + + if ( $other.contains( $ ) ) + return CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING; + } + + if ( 'sourceIndex' in $ ) + { + return ( $.sourceIndex < 0 || $other.sourceIndex < 0 ) ? CKEDITOR.POSITION_DISCONNECTED : + ( $.sourceIndex < $other.sourceIndex ) ? CKEDITOR.POSITION_PRECEDING : + CKEDITOR.POSITION_FOLLOWING; + } + } + + // For nodes that don't support compareDocumentPosition, contains + // or sourceIndex, their "address" is compared. + + var addressOfThis = this.getAddress(), + addressOfOther = otherNode.getAddress(), + minLevel = Math.min( addressOfThis.length, addressOfOther.length ); + + // Determinate preceed/follow relationship. + for ( var i = 0 ; i <= minLevel - 1 ; i++ ) + { + if ( addressOfThis[ i ] != addressOfOther[ i ] ) + { + if ( i < minLevel ) + { + return addressOfThis[ i ] < addressOfOther[ i ] ? + CKEDITOR.POSITION_PRECEDING : CKEDITOR.POSITION_FOLLOWING; + } + break; + } + } + + // Determinate contains/contained relationship. + return ( addressOfThis.length < addressOfOther.length ) ? + CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING : + CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING; + }, + + /** + * Gets the closes ancestor node of a specified node name. + * @param {String} name Node name of ancestor node. + * @param {Boolean} includeSelf (Optional) Whether to include the current + * node in the calculation or not. + * @returns {CKEDITOR.dom.node} Ancestor node. + */ + getAscendant : function( name, includeSelf ) + { + var $ = this.$; + + if ( !includeSelf ) + $ = $.parentNode; + + while ( $ ) + { + if ( $.nodeName && $.nodeName.toLowerCase() == name ) + return new CKEDITOR.dom.node( $ ); + + $ = $.parentNode; + } + return null; + }, + + hasAscendant : function( name, includeSelf ) + { + var $ = this.$; + + if ( !includeSelf ) + $ = $.parentNode; + + while ( $ ) + { + if ( $.nodeName && $.nodeName.toLowerCase() == name ) + return true; + + $ = $.parentNode; + } + return false; + }, + + move : function( target, toStart ) + { + target.append( this.remove(), toStart ); + }, + + /** + * Removes this node from the document DOM. + * @param {Boolean} [preserveChildren] Indicates that the children + * elements must remain in the document, removing only the outer + * tags. + * @example + * var element = CKEDITOR.dom.element.getById( 'MyElement' ); + * element.remove(); + */ + remove : function( preserveChildren ) + { + var $ = this.$; + var parent = $.parentNode; + + if ( parent ) + { + if ( preserveChildren ) + { + // Move all children before the node. + for ( var child ; ( child = $.firstChild ) ; ) + { + parent.insertBefore( $.removeChild( child ), $ ); + } + } + + parent.removeChild( $ ); + } + + return this; + }, + + replace : function( nodeToReplace ) + { + this.insertBefore( nodeToReplace ); + nodeToReplace.remove(); + }, + + trim : function() + { + this.ltrim(); + this.rtrim(); + }, + + ltrim : function() + { + var child; + while ( this.getFirst && ( child = this.getFirst() ) ) + { + if ( child.type == CKEDITOR.NODE_TEXT ) + { + var trimmed = CKEDITOR.tools.ltrim( child.getText() ), + originalLength = child.getLength(); + + if ( !trimmed ) + { + child.remove(); + continue; + } + else if ( trimmed.length < originalLength ) + { + child.split( originalLength - trimmed.length ); + + // IE BUG: child.remove() may raise JavaScript errors here. (#81) + this.$.removeChild( this.$.firstChild ); + } + } + break; + } + }, + + rtrim : function() + { + var child; + while ( this.getLast && ( child = this.getLast() ) ) + { + if ( child.type == CKEDITOR.NODE_TEXT ) + { + var trimmed = CKEDITOR.tools.rtrim( child.getText() ), + originalLength = child.getLength(); + + if ( !trimmed ) + { + child.remove(); + continue; + } + else if ( trimmed.length < originalLength ) + { + child.split( trimmed.length ); + + // IE BUG: child.getNext().remove() may raise JavaScript errors here. + // (#81) + this.$.lastChild.parentNode.removeChild( this.$.lastChild ); + } + } + break; + } + + if ( !CKEDITOR.env.ie && !CKEDITOR.env.opera ) + { + child = this.$.lastChild; + + if ( child && child.type == 1 && child.nodeName.toLowerCase() == 'br' ) + { + // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#324). + child.parentNode.removeChild( child ) ; + } + } + } + } +); diff --git a/rte/_source/core/dom/nodelist.js b/rte/_source/core/dom/nodelist.js new file mode 100644 index 0000000..155f0ad --- /dev/null +++ b/rte/_source/core/dom/nodelist.js @@ -0,0 +1,23 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dom.nodeList = function( nativeList ) +{ + this.$ = nativeList; +}; + +CKEDITOR.dom.nodeList.prototype = +{ + count : function() + { + return this.$.length; + }, + + getItem : function( index ) + { + var $node = this.$[ index ]; + return $node ? new CKEDITOR.dom.node( $node ) : null; + } +}; diff --git a/rte/_source/core/dom/range.js b/rte/_source/core/dom/range.js new file mode 100644 index 0000000..80d247a --- /dev/null +++ b/rte/_source/core/dom/range.js @@ -0,0 +1,1853 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dom.range = function( document ) +{ + this.startContainer = null; + this.startOffset = null; + this.endContainer = null; + this.endOffset = null; + this.collapsed = true; + + this.document = document; +}; + +(function() +{ + // Updates the "collapsed" property for the given range object. + var updateCollapsed = function( range ) + { + range.collapsed = ( + range.startContainer && + range.endContainer && + range.startContainer.equals( range.endContainer ) && + range.startOffset == range.endOffset ); + }; + + // This is a shared function used to delete, extract and clone the range + // contents. + // V2 + var execContentsAction = function( range, action, docFrag ) + { + range.optimizeBookmark(); + + var startNode = range.startContainer; + var endNode = range.endContainer; + + var startOffset = range.startOffset; + var endOffset = range.endOffset; + + var removeStartNode; + var removeEndNode; + + // For text containers, we must simply split the node and point to the + // second part. The removal will be handled by the rest of the code . + if ( endNode.type == CKEDITOR.NODE_TEXT ) + endNode = endNode.split( endOffset ); + else + { + // If the end container has children and the offset is pointing + // to a child, then we should start from it. + if ( endNode.getChildCount() > 0 ) + { + // If the offset points after the last node. + if ( endOffset >= endNode.getChildCount() ) + { + // Let's create a temporary node and mark it for removal. + endNode = endNode.append( range.document.createText( '' ) ); + removeEndNode = true; + } + else + endNode = endNode.getChild( endOffset ); + } + } + + // For text containers, we must simply split the node. The removal will + // be handled by the rest of the code . + if ( startNode.type == CKEDITOR.NODE_TEXT ) + { + startNode.split( startOffset ); + + // In cases the end node is the same as the start node, the above + // splitting will also split the end, so me must move the end to + // the second part of the split. + if ( startNode.equals( endNode ) ) + endNode = startNode.getNext(); + } + else + { + // If the start container has children and the offset is pointing + // to a child, then we should start from its previous sibling. + + // If the offset points to the first node, we don't have a + // sibling, so let's use the first one, but mark it for removal. + if ( !startOffset ) + { + // Let's create a temporary node and mark it for removal. + startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) ); + removeStartNode = true; + } + else if ( startOffset >= startNode.getChildCount() ) + { + // Let's create a temporary node and mark it for removal. + startNode = startNode.append( range.document.createText( '' ) ); + removeStartNode = true; + } + else + startNode = startNode.getChild( startOffset ).getPrevious(); + } + + // Get the parent nodes tree for the start and end boundaries. + var startParents = startNode.getParents(); + var endParents = endNode.getParents(); + + // Compare them, to find the top most siblings. + var i, topStart, topEnd; + + for ( i = 0 ; i < startParents.length ; i++ ) + { + topStart = startParents[ i ]; + topEnd = endParents[ i ]; + + // The compared nodes will match until we find the top most + // siblings (different nodes that have the same parent). + // "i" will hold the index in the parents array for the top + // most element. + if ( !topStart.equals( topEnd ) ) + break; + } + + var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling; + + // Remove all successive sibling nodes for every node in the + // startParents tree. + for ( var j = i ; j < startParents.length ; j++ ) + { + levelStartNode = startParents[j]; + + // For Extract and Clone, we must clone this level. + if ( clone && !levelStartNode.equals( startNode ) ) // action = 0 = Delete + levelClone = clone.append( levelStartNode.clone() ); + + currentNode = levelStartNode.getNext(); + + while ( currentNode ) + { + // Stop processing when the current node matches a node in the + // endParents tree or if it is the endNode. + if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) ) + break; + + // Cache the next sibling. + currentSibling = currentNode.getNext(); + + // If cloning, just clone it. + if ( action == 2 ) // 2 = Clone + clone.append( currentNode.clone( true ) ); + else + { + // Both Delete and Extract will remove the node. + currentNode.remove(); + + // When Extracting, move the removed node to the docFrag. + if ( action == 1 ) // 1 = Extract + clone.append( currentNode ); + } + + currentNode = currentSibling; + } + + if ( clone ) + clone = levelClone; + } + + clone = docFrag; + + // Remove all previous sibling nodes for every node in the + // endParents tree. + for ( var k = i ; k < endParents.length ; k++ ) + { + levelStartNode = endParents[ k ]; + + // For Extract and Clone, we must clone this level. + if ( action > 0 && !levelStartNode.equals( endNode ) ) // action = 0 = Delete + levelClone = clone.append( levelStartNode.clone() ); + + // The processing of siblings may have already been done by the parent. + if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode ) + { + currentNode = levelStartNode.getPrevious(); + + while ( currentNode ) + { + // Stop processing when the current node matches a node in the + // startParents tree or if it is the startNode. + if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) ) + break; + + // Cache the next sibling. + currentSibling = currentNode.getPrevious(); + + // If cloning, just clone it. + if ( action == 2 ) // 2 = Clone + clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ; + else + { + // Both Delete and Extract will remove the node. + currentNode.remove(); + + // When Extracting, mode the removed node to the docFrag. + if ( action == 1 ) // 1 = Extract + clone.$.insertBefore( currentNode.$, clone.$.firstChild ); + } + + currentNode = currentSibling; + } + } + + if ( clone ) + clone = levelClone; + } + + if ( action == 2 ) // 2 = Clone. + { + // No changes in the DOM should be done, so fix the split text (if any). + + var startTextNode = range.startContainer; + if ( startTextNode.type == CKEDITOR.NODE_TEXT ) + { + startTextNode.$.data += startTextNode.$.nextSibling.data; + startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling ); + } + + var endTextNode = range.endContainer; + if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling ) + { + endTextNode.$.data += endTextNode.$.nextSibling.data; + endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling ); + } + } + else + { + // Collapse the range. + + // If a node has been partially selected, collapse the range between + // topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs). + if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) ) + { + var endIndex = topEnd.getIndex(); + + // If the start node is to be removed, we must correct the + // index to reflect the removal. + if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode ) + endIndex--; + + range.setStart( topEnd.getParent(), endIndex ); + } + + // Collapse it to the start. + range.collapse( true ); + } + + // Cleanup any marked node. + if ( removeStartNode ) + startNode.remove(); + + if ( removeEndNode && endNode.$.parentNode ) + endNode.remove(); + }; + + var inlineChildReqElements = { abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 }; + + // Creates the appropriate node evaluator for the dom walker used inside + // check(Start|End)OfBlock. + function getCheckStartEndBlockEvalFunction( isStart ) + { + var hadBr = false, bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true ); + return function( node ) + { + // First ignore bookmark nodes. + if ( bookmarkEvaluator( node ) ) + return true; + + if ( node.type == CKEDITOR.NODE_TEXT ) + { + // If there's any visible text, then we're not at the start. + if ( CKEDITOR.tools.trim( node.getText() ).length ) + return false; + } + else if ( node.type == CKEDITOR.NODE_ELEMENT ) + { + // If there are non-empty inline elements (e.g. ), then we're not + // at the start. + if ( !inlineChildReqElements[ node.getName() ] ) + { + // If we're working at the end-of-block, forgive the first
    in non-IE + // browsers. + if ( !isStart && !CKEDITOR.env.ie && node.getName() == 'br' && !hadBr ) + hadBr = true; + else + return false; + } + } + return true; + }; + } + + // Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any + // text node and non-empty elements unless it's being bookmark text. + function elementBoundaryEval( node ) + { + // Reject any text node unless it's being bookmark + // OR it's spaces. (#3883) + return node.type != CKEDITOR.NODE_TEXT + && node.getName() in CKEDITOR.dtd.$removeEmpty + || !CKEDITOR.tools.trim( node.getText() ) + || node.getParent().hasAttribute( '_fck_bookmark' ); + } + + var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(), + bookmarkEval = new CKEDITOR.dom.walker.bookmark(); + + function nonWhitespaceOrBookmarkEval( node ) + { + // Whitespaces and bookmark nodes are to be ignored. + return !whitespaceEval( node ) && !bookmarkEval( node ); + } + + CKEDITOR.dom.range.prototype = + { + clone : function() + { + var clone = new CKEDITOR.dom.range( this.document ); + + clone.startContainer = this.startContainer; + clone.startOffset = this.startOffset; + clone.endContainer = this.endContainer; + clone.endOffset = this.endOffset; + clone.collapsed = this.collapsed; + + return clone; + }, + + collapse : function( toStart ) + { + if ( toStart ) + { + this.endContainer = this.startContainer; + this.endOffset = this.startOffset; + } + else + { + this.startContainer = this.endContainer; + this.startOffset = this.endOffset; + } + + this.collapsed = true; + }, + + // The selection may be lost when cloning (due to the splitText() call). + cloneContents : function() + { + var docFrag = new CKEDITOR.dom.documentFragment( this.document ); + + if ( !this.collapsed ) + execContentsAction( this, 2, docFrag ); + + return docFrag; + }, + + deleteContents : function() + { + if ( this.collapsed ) + return; + + execContentsAction( this, 0 ); + }, + + extractContents : function() + { + var docFrag = new CKEDITOR.dom.documentFragment( this.document ); + + if ( !this.collapsed ) + execContentsAction( this, 1, docFrag ); + + return docFrag; + }, + + /** + * Creates a bookmark object, which can be later used to restore the + * range by using the moveToBookmark function. + * This is an "intrusive" way to create a bookmark. It includes tags + * in the range boundaries. The advantage of it is that it is possible to + * handle DOM mutations when moving back to the bookmark. + * Attention: the inclusion of nodes in the DOM is a design choice and + * should not be changed as there are other points in the code that may be + * using those nodes to perform operations. See GetBookmarkNode. + * @param {Boolean} [serializable] Indicates that the bookmark nodes + * must contain ids, which can be used to restore the range even + * when these nodes suffer mutations (like a clonation or innerHTML + * change). + * @returns {Object} And object representing a bookmark. + */ + createBookmark : function( serializable ) + { + var startNode, endNode; + var baseId; + var clone; + + startNode = this.document.createElement( 'span' ); + startNode.setAttribute( '_fck_bookmark', 1 ); + startNode.setStyle( 'display', 'none' ); + + // For IE, it must have something inside, otherwise it may be + // removed during DOM operations. + startNode.setHtml( ' ' ); + + if ( serializable ) + { + baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber(); + startNode.setAttribute( 'id', baseId + 'S' ); + } + + // If collapsed, the endNode will not be created. + if ( !this.collapsed ) + { + endNode = startNode.clone(); + endNode.setHtml( ' ' ); + + if ( serializable ) + endNode.setAttribute( 'id', baseId + 'E' ); + + clone = this.clone(); + clone.collapse(); + clone.insertNode( endNode ); + } + + clone = this.clone(); + clone.collapse( true ); + clone.insertNode( startNode ); + + // Update the range position. + if ( endNode ) + { + this.setStartAfter( startNode ); + this.setEndBefore( endNode ); + } + else + this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END ); + + return { + startNode : serializable ? baseId + 'S' : startNode, + endNode : serializable ? baseId + 'E' : endNode, + serializable : serializable + }; + }, + + /** + * Creates a "non intrusive" and "mutation sensible" bookmark. This + * kind of bookmark should be used only when the DOM is supposed to + * remain stable after its creation. + * @param {Boolean} [normalized] Indicates that the bookmark must + * normalized. When normalized, the successive text nodes are + * considered a single node. To sucessful load a normalized + * bookmark, the DOM tree must be also normalized before calling + * moveToBookmark. + * @returns {Object} An object representing the bookmark. + */ + createBookmark2 : function( normalized ) + { + var startContainer = this.startContainer, + endContainer = this.endContainer; + + var startOffset = this.startOffset, + endOffset = this.endOffset; + + var child, previous; + + // If there is no range then get out of here. + // It happens on initial load in Safari #962 and if the editor it's + // hidden also in Firefox + if ( !startContainer || !endContainer ) + return { start : 0, end : 0 }; + + if ( normalized ) + { + // Find out if the start is pointing to a text node that will + // be normalized. + if ( startContainer.type == CKEDITOR.NODE_ELEMENT ) + { + child = startContainer.getChild( startOffset ); + + // In this case, move the start information to that text + // node. + if ( child && child.type == CKEDITOR.NODE_TEXT + && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT ) + { + startContainer = child; + startOffset = 0; + } + } + + // Normalize the start. + while ( startContainer.type == CKEDITOR.NODE_TEXT + && ( previous = startContainer.getPrevious() ) + && previous.type == CKEDITOR.NODE_TEXT ) + { + startContainer = previous; + startOffset += previous.getLength(); + } + + // Process the end only if not normalized. + if ( !this.isCollapsed ) + { + // Find out if the start is pointing to a text node that + // will be normalized. + if ( endContainer.type == CKEDITOR.NODE_ELEMENT ) + { + child = endContainer.getChild( endOffset ); + + // In this case, move the start information to that + // text node. + if ( child && child.type == CKEDITOR.NODE_TEXT + && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT ) + { + endContainer = child; + endOffset = 0; + } + } + + // Normalize the end. + while ( endContainer.type == CKEDITOR.NODE_TEXT + && ( previous = endContainer.getPrevious() ) + && previous.type == CKEDITOR.NODE_TEXT ) + { + endContainer = previous; + endOffset += previous.getLength(); + } + } + } + + return { + start : startContainer.getAddress( normalized ), + end : this.isCollapsed ? null : endContainer.getAddress( normalized ), + startOffset : startOffset, + endOffset : endOffset, + normalized : normalized, + is2 : true // It's a createBookmark2 bookmark. + }; + }, + + moveToBookmark : function( bookmark ) + { + if ( bookmark.is2 ) // Created with createBookmark2(). + { + // Get the start information. + var startContainer = this.document.getByAddress( bookmark.start, bookmark.normalized ), + startOffset = bookmark.startOffset; + + // Get the end information. + var endContainer = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ), + endOffset = bookmark.endOffset; + + // Set the start boundary. + this.setStart( startContainer, startOffset ); + + // Set the end boundary. If not available, collapse it. + if ( endContainer ) + this.setEnd( endContainer, endOffset ); + else + this.collapse( true ); + } + else // Created with createBookmark(). + { + var serializable = bookmark.serializable, + startNode = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode, + endNode = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode; + + // Set the range start at the bookmark start node position. + this.setStartBefore( startNode ); + + // Remove it, because it may interfere in the setEndBefore call. + startNode.remove(); + + // Set the range end at the bookmark end node position, or simply + // collapse it if it is not available. + if ( endNode ) + { + this.setEndBefore( endNode ); + endNode.remove(); + } + else + this.collapse( true ); + } + }, + + getBoundaryNodes : function() + { + var startNode = this.startContainer, + endNode = this.endContainer, + startOffset = this.startOffset, + endOffset = this.endOffset, + childCount; + + if ( startNode.type == CKEDITOR.NODE_ELEMENT ) + { + childCount = startNode.getChildCount(); + if ( childCount > startOffset ) + startNode = startNode.getChild( startOffset ); + else if ( childCount < 1 ) + startNode = startNode.getPreviousSourceNode(); + else // startOffset > childCount but childCount is not 0 + { + // Try to take the node just after the current position. + startNode = startNode.$; + while ( startNode.lastChild ) + startNode = startNode.lastChild; + startNode = new CKEDITOR.dom.node( startNode ); + + // Normally we should take the next node in DFS order. But it + // is also possible that we've already reached the end of + // document. + startNode = startNode.getNextSourceNode() || startNode; + } + } + if ( endNode.type == CKEDITOR.NODE_ELEMENT ) + { + childCount = endNode.getChildCount(); + if ( childCount > endOffset ) + endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true ); + else if ( childCount < 1 ) + endNode = endNode.getPreviousSourceNode(); + else // endOffset > childCount but childCount is not 0 + { + // Try to take the node just before the current position. + endNode = endNode.$; + while ( endNode.lastChild ) + endNode = endNode.lastChild; + endNode = new CKEDITOR.dom.node( endNode ); + } + } + + // Sometimes the endNode will come right before startNode for collapsed + // ranges. Fix it. (#3780) + if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING ) + startNode = endNode; + + return { startNode : startNode, endNode : endNode }; + }, + + /** + * Find the node which fully contains the range. + * @param includeSelf + * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type. + */ + getCommonAncestor : function( includeSelf , ignoreTextNode ) + { + var start = this.startContainer, + end = this.endContainer, + ancestor; + + if ( start.equals( end ) ) + { + if ( includeSelf + && start.type == CKEDITOR.NODE_ELEMENT + && this.startOffset == this.endOffset - 1 ) + ancestor = start.getChild( this.startOffset ); + else + ancestor = start; + } + else + ancestor = start.getCommonAncestor( end ); + + return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor; + }, + + /** + * Transforms the startContainer and endContainer properties from text + * nodes to element nodes, whenever possible. This is actually possible + * if either of the boundary containers point to a text node, and its + * offset is set to zero, or after the last char in the node. + */ + optimize : function() + { + var container = this.startContainer; + var offset = this.startOffset; + + if ( container.type != CKEDITOR.NODE_ELEMENT ) + { + if ( !offset ) + this.setStartBefore( container ); + else if ( offset >= container.getLength() ) + this.setStartAfter( container ); + } + + container = this.endContainer; + offset = this.endOffset; + + if ( container.type != CKEDITOR.NODE_ELEMENT ) + { + if ( !offset ) + this.setEndBefore( container ); + else if ( offset >= container.getLength() ) + this.setEndAfter( container ); + } + }, + + /** + * Move the range out of bookmark nodes if they're been the container. + */ + optimizeBookmark: function() + { + var startNode = this.startContainer, + endNode = this.endContainer; + + if ( startNode.is && startNode.is( 'span' ) + && startNode.hasAttribute( '_fck_bookmark' ) ) + this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START ); + if ( endNode && endNode.is && endNode.is( 'span' ) + && endNode.hasAttribute( '_fck_bookmark' ) ) + this.setEndAt( endNode, CKEDITOR.POSITION_AFTER_END ); + }, + + trim : function( ignoreStart, ignoreEnd ) + { + var startContainer = this.startContainer, + startOffset = this.startOffset, + collapsed = this.collapsed; + if ( ( !ignoreStart || collapsed ) + && startContainer && startContainer.type == CKEDITOR.NODE_TEXT ) + { + // If the offset is zero, we just insert the new node before + // the start. + if ( !startOffset ) + { + startOffset = startContainer.getIndex(); + startContainer = startContainer.getParent(); + } + // If the offset is at the end, we'll insert it after the text + // node. + else if ( startOffset >= startContainer.getLength() ) + { + startOffset = startContainer.getIndex() + 1; + startContainer = startContainer.getParent(); + } + // In other case, we split the text node and insert the new + // node at the split point. + else + { + var nextText = startContainer.split( startOffset ); + + startOffset = startContainer.getIndex() + 1; + startContainer = startContainer.getParent(); + + // Check all necessity of updating the end boundary. + if ( this.startContainer.equals( this.endContainer ) ) + this.setEnd( nextText, this.endOffset - this.startOffset ); + else if ( startContainer.equals( this.endContainer ) ) + this.endOffset += 1; + } + + this.setStart( startContainer, startOffset ); + + if ( collapsed ) + { + this.collapse( true ); + return; + } + } + + var endContainer = this.endContainer; + var endOffset = this.endOffset; + + if ( !( ignoreEnd || collapsed ) + && endContainer && endContainer.type == CKEDITOR.NODE_TEXT ) + { + // If the offset is zero, we just insert the new node before + // the start. + if ( !endOffset ) + { + endOffset = endContainer.getIndex(); + endContainer = endContainer.getParent(); + } + // If the offset is at the end, we'll insert it after the text + // node. + else if ( endOffset >= endContainer.getLength() ) + { + endOffset = endContainer.getIndex() + 1; + endContainer = endContainer.getParent(); + } + // In other case, we split the text node and insert the new + // node at the split point. + else + { + endContainer.split( endOffset ); + + endOffset = endContainer.getIndex() + 1; + endContainer = endContainer.getParent(); + } + + this.setEnd( endContainer, endOffset ); + } + }, + + enlarge : function( unit ) + { + switch ( unit ) + { + case CKEDITOR.ENLARGE_ELEMENT : + + if ( this.collapsed ) + return; + + // Get the common ancestor. + var commonAncestor = this.getCommonAncestor(); + + var body = this.document.getBody(); + + // For each boundary + // a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge. + // b. Go ahead checking siblings and enlarging the boundary as much as possible until the common ancestor is not reached. After reaching the common ancestor, just save the enlargeable node to be used later. + + var startTop, endTop; + + var enlargeable, sibling, commonReached; + + // Indicates that the node can be added only if whitespace + // is available before it. + var needsWhiteSpace = false; + var isWhiteSpace; + var siblingText; + + // Process the start boundary. + + var container = this.startContainer; + var offset = this.startOffset; + + if ( container.type == CKEDITOR.NODE_TEXT ) + { + if ( offset ) + { + // Check if there is any non-space text before the + // offset. Otherwise, container is null. + container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container; + + // If we found only whitespace in the node, it + // means that we'll need more whitespace to be able + // to expand. For example, can be expanded in + // "A [B]", but not in "A [B]". + needsWhiteSpace = !!container; + } + + if ( container ) + { + if ( !( sibling = container.getPrevious() ) ) + enlargeable = container.getParent(); + } + } + else + { + // If we have offset, get the node preceeding it as the + // first sibling to be checked. + if ( offset ) + sibling = container.getChild( offset - 1 ) || container.getLast(); + + // If there is no sibling, mark the container to be + // enlarged. + if ( !sibling ) + enlargeable = container; + } + + while ( enlargeable || sibling ) + { + if ( enlargeable && !sibling ) + { + // If we reached the common ancestor, mark the flag + // for it. + if ( !commonReached && enlargeable.equals( commonAncestor ) ) + commonReached = true; + + if ( !body.contains( enlargeable ) ) + break; + + // If we don't need space or this element breaks + // the line, then enlarge it. + if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' ) + { + needsWhiteSpace = false; + + // If the common ancestor has been reached, + // we'll not enlarge it immediately, but just + // mark it to be enlarged later if the end + // boundary also enlarges it. + if ( commonReached ) + startTop = enlargeable; + else + this.setStartBefore( enlargeable ); + } + + sibling = enlargeable.getPrevious(); + } + + // Check all sibling nodes preceeding the enlargeable + // node. The node wil lbe enlarged only if none of them + // blocks it. + while ( sibling ) + { + // This flag indicates that this node has + // whitespaces at the end. + isWhiteSpace = false; + + if ( sibling.type == CKEDITOR.NODE_TEXT ) + { + siblingText = sibling.getText(); + + if ( /[^\s\ufeff]/.test( siblingText ) ) + sibling = null; + + isWhiteSpace = /[\s\ufeff]$/.test( siblingText ); + } + else + { + // If this is a visible element. + // We need to check for the bookmark attribute because IE insists on + // rendering the display:none nodes we use for bookmarks. (#3363) + if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) ) + { + // We'll accept it only if we need + // whitespace, and this is an inline + // element with whitespace only. + if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] ) + { + // It must contains spaces and inline elements only. + + siblingText = sibling.getText(); + + if ( (/[^\s\ufeff]/).test( siblingText ) ) // Spaces + Zero Width No-Break Space (U+FEFF) + sibling = null; + else + { + var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' ); + for ( var i = 0, child ; child = allChildren[ i++ ] ; ) + { + if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] ) + { + sibling = null; + break; + } + } + } + + if ( sibling ) + isWhiteSpace = !!siblingText.length; + } + else + sibling = null; + } + } + + // A node with whitespaces has been found. + if ( isWhiteSpace ) + { + // Enlarge the last enlargeable node, if we + // were waiting for spaces. + if ( needsWhiteSpace ) + { + if ( commonReached ) + startTop = enlargeable; + else if ( enlargeable ) + this.setStartBefore( enlargeable ); + } + else + needsWhiteSpace = true; + } + + if ( sibling ) + { + var next = sibling.getPrevious(); + + if ( !enlargeable && !next ) + { + // Set the sibling as enlargeable, so it's + // parent will be get later outside this while. + enlargeable = sibling; + sibling = null; + break; + } + + sibling = next; + } + else + { + // If sibling has been set to null, then we + // need to stop enlarging. + enlargeable = null; + } + } + + if ( enlargeable ) + enlargeable = enlargeable.getParent(); + } + + // Process the end boundary. This is basically the same + // code used for the start boundary, with small changes to + // make it work in the oposite side (to the right). This + // makes it difficult to reuse the code here. So, fixes to + // the above code are likely to be replicated here. + + container = this.endContainer; + offset = this.endOffset; + + // Reset the common variables. + enlargeable = sibling = null; + commonReached = needsWhiteSpace = false; + + if ( container.type == CKEDITOR.NODE_TEXT ) + { + // Check if there is any non-space text after the + // offset. Otherwise, container is null. + container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container; + + // If we found only whitespace in the node, it + // means that we'll need more whitespace to be able + // to expand. For example, can be expanded in + // "A [B]", but not in "A [B]". + needsWhiteSpace = !( container && container.getLength() ); + + if ( container ) + { + if ( !( sibling = container.getNext() ) ) + enlargeable = container.getParent(); + } + } + else + { + // Get the node right after the boudary to be checked + // first. + sibling = container.getChild( offset ); + + if ( !sibling ) + enlargeable = container; + } + + while ( enlargeable || sibling ) + { + if ( enlargeable && !sibling ) + { + if ( !commonReached && enlargeable.equals( commonAncestor ) ) + commonReached = true; + + if ( !body.contains( enlargeable ) ) + break; + + if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' ) + { + needsWhiteSpace = false; + + if ( commonReached ) + endTop = enlargeable; + else if ( enlargeable ) + this.setEndAfter( enlargeable ); + } + + sibling = enlargeable.getNext(); + } + + while ( sibling ) + { + isWhiteSpace = false; + + if ( sibling.type == CKEDITOR.NODE_TEXT ) + { + siblingText = sibling.getText(); + + if ( /[^\s\ufeff]/.test( siblingText ) ) + sibling = null; + + isWhiteSpace = /^[\s\ufeff]/.test( siblingText ); + } + else + { + // If this is a visible element. + // We need to check for the bookmark attribute because IE insists on + // rendering the display:none nodes we use for bookmarks. (#3363) + if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) ) + { + // We'll accept it only if we need + // whitespace, and this is an inline + // element with whitespace only. + if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] ) + { + // It must contains spaces and inline elements only. + + siblingText = sibling.getText(); + + if ( (/[^\s\ufeff]/).test( siblingText ) ) + sibling = null; + else + { + allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' ); + for ( i = 0 ; child = allChildren[ i++ ] ; ) + { + if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] ) + { + sibling = null; + break; + } + } + } + + if ( sibling ) + isWhiteSpace = !!siblingText.length; + } + else + sibling = null; + } + } + + if ( isWhiteSpace ) + { + if ( needsWhiteSpace ) + { + if ( commonReached ) + endTop = enlargeable; + else + this.setEndAfter( enlargeable ); + } + } + + if ( sibling ) + { + next = sibling.getNext(); + + if ( !enlargeable && !next ) + { + enlargeable = sibling; + sibling = null; + break; + } + + sibling = next; + } + else + { + // If sibling has been set to null, then we + // need to stop enlarging. + enlargeable = null; + } + } + + if ( enlargeable ) + enlargeable = enlargeable.getParent(); + } + + // If the common ancestor can be enlarged by both boundaries, then include it also. + if ( startTop && endTop ) + { + commonAncestor = startTop.contains( endTop ) ? endTop : startTop; + + this.setStartBefore( commonAncestor ); + this.setEndAfter( commonAncestor ); + } + break; + + case CKEDITOR.ENLARGE_BLOCK_CONTENTS: + case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS: + + // Enlarging the start boundary. + var walkerRange = new CKEDITOR.dom.range( this.document ); + + body = this.document.getBody(); + + walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START ); + walkerRange.setEnd( this.startContainer, this.startOffset ); + + var walker = new CKEDITOR.dom.walker( walkerRange ), + blockBoundary, // The node on which the enlarging should stop. + tailBr, // + defaultGuard = CKEDITOR.dom.walker.blockBoundary( + ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ), + // Record the encountered 'blockBoundary' for later use. + boundaryGuard = function( node ) + { + var retval = defaultGuard( node ); + if ( !retval ) + blockBoundary = node; + return retval; + }, + // Record the encounted 'tailBr' for later use. + tailBrGuard = function( node ) + { + var retval = boundaryGuard( node ); + if ( !retval && node.is && node.is( 'br' ) ) + tailBr = node; + return retval; + }; + + walker.guard = boundaryGuard; + + enlargeable = walker.lastBackward(); + + // It's the body which stop the enlarging if no block boundary found. + blockBoundary = blockBoundary || body; + + // Start the range at different position by comparing + // the document position of it with 'enlargeable' node. + this.setStartAt( + blockBoundary, + !blockBoundary.is( 'br' ) && + ( !enlargeable && this.checkStartOfBlock() + || enlargeable && blockBoundary.contains( enlargeable ) ) ? + CKEDITOR.POSITION_AFTER_START : + CKEDITOR.POSITION_AFTER_END ); + + // Enlarging the end boundary. + walkerRange = this.clone(); + walkerRange.collapse(); + walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END ); + walker = new CKEDITOR.dom.walker( walkerRange ); + + // tailBrGuard only used for on range end. + walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? + tailBrGuard : boundaryGuard; + blockBoundary = null; + // End the range right before the block boundary node. + + enlargeable = walker.lastForward(); + + // It's the body which stop the enlarging if no block boundary found. + blockBoundary = blockBoundary || body; + + // Start the range at different position by comparing + // the document position of it with 'enlargeable' node. + this.setEndAt( + blockBoundary, + ( !enlargeable && this.checkEndOfBlock() + || enlargeable && blockBoundary.contains( enlargeable ) ) ? + CKEDITOR.POSITION_BEFORE_END : + CKEDITOR.POSITION_BEFORE_START ); + // We must include the
    at the end of range if there's + // one and we're expanding list item contents + if ( tailBr ) + this.setEndAfter( tailBr ); + } + }, + + /** + * Descrease the range to make sure that boundaries + * always anchor beside text nodes or innermost element. + * @param {Number} mode ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode. + */ + shrink : function( mode, selectContents ) + { + // Unable to shrink a collapsed range. + if ( !this.collapsed ) + { + mode = mode || CKEDITOR.SHRINK_TEXT; + + var walkerRange = this.clone(); + + var startContainer = this.startContainer, + endContainer = this.endContainer, + startOffset = this.startOffset, + endOffset = this.endOffset, + collapsed = this.collapsed; + + // Whether the start/end boundary is moveable. + var moveStart = 1, + moveEnd = 1; + + if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT ) + { + if ( !startOffset ) + walkerRange.setStartBefore( startContainer ); + else if ( startOffset >= startContainer.getLength( ) ) + walkerRange.setStartAfter( startContainer ); + else + { + // Enlarge the range properly to avoid walker making + // DOM changes caused by triming the text nodes later. + walkerRange.setStartBefore( startContainer ); + moveStart = 0; + } + } + + if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT ) + { + if ( !endOffset ) + walkerRange.setEndBefore( endContainer ); + else if ( endOffset >= endContainer.getLength( ) ) + walkerRange.setEndAfter( endContainer ); + else + { + walkerRange.setEndAfter( endContainer ); + moveEnd = 0; + } + } + + var walker = new CKEDITOR.dom.walker( walkerRange ); + + walker.evaluator = function( node ) + { + return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ? + CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT ); + }; + + var currentElement; + walker.guard = function( node, movingOut ) + { + // Stop when we're shrink in element mode while encountering a text node. + if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT ) + return false; + + // Stop when we've already walked "through" an element. + if ( movingOut && node.equals( currentElement ) ) + return false; + + if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT ) + currentElement = node; + + return true; + }; + + if ( moveStart ) + { + var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next'](); + textStart && this.setStartAt( textStart, selectContents ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_START ); + } + + if ( moveEnd ) + { + walker.reset(); + var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous'](); + textEnd && this.setEndAt( textEnd, selectContents ? CKEDITOR.POSITION_BEFORE_END : CKEDITOR.POSITION_AFTER_END ); + } + + return !!( moveStart || moveEnd ); + } + }, + + /** + * Inserts a node at the start of the range. The range will be expanded + * the contain the node. + */ + insertNode : function( node ) + { + this.optimizeBookmark(); + this.trim( false, true ); + + var startContainer = this.startContainer; + var startOffset = this.startOffset; + + var nextNode = startContainer.getChild( startOffset ); + + if ( nextNode ) + node.insertBefore( nextNode ); + else + startContainer.append( node ); + + // Check if we need to update the end boundary. + if ( node.getParent().equals( this.endContainer ) ) + this.endOffset++; + + // Expand the range to embrace the new node. + this.setStartBefore( node ); + }, + + moveToPosition : function( node, position ) + { + this.setStartAt( node, position ); + this.collapse( true ); + }, + + selectNodeContents : function( node ) + { + this.setStart( node, 0 ); + this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() ); + }, + + /** + * Sets the start position of a Range. + * @param {CKEDITOR.dom.node} startNode The node to start the range. + * @param {Number} startOffset An integer greater than or equal to zero + * representing the offset for the start of the range from the start + * of startNode. + */ + setStart : function( startNode, startOffset ) + { + // W3C requires a check for the new position. If it is after the end + // boundary, the range should be collapsed to the new start. It seams + // we will not need this check for our use of this class so we can + // ignore it for now. + + // Fixing invalid range start inside dtd empty elements. + if( startNode.type == CKEDITOR.NODE_ELEMENT + && CKEDITOR.dtd.$empty[ startNode.getName() ] ) + startNode = startNode.getParent(), startOffset = startNode.getIndex(); + + this.startContainer = startNode; + this.startOffset = startOffset; + + if ( !this.endContainer ) + { + this.endContainer = startNode; + this.endOffset = startOffset; + } + + updateCollapsed( this ); + }, + + /** + * Sets the end position of a Range. + * @param {CKEDITOR.dom.node} endNode The node to end the range. + * @param {Number} endOffset An integer greater than or equal to zero + * representing the offset for the end of the range from the start + * of endNode. + */ + setEnd : function( endNode, endOffset ) + { + // W3C requires a check for the new position. If it is before the start + // boundary, the range should be collapsed to the new end. It seams we + // will not need this check for our use of this class so we can ignore + // it for now. + + // Fixing invalid range end inside dtd empty elements. + if( endNode.type == CKEDITOR.NODE_ELEMENT + && CKEDITOR.dtd.$empty[ endNode.getName() ] ) + endNode = endNode.getParent(), endOffset = endNode.getIndex() + 1; + + this.endContainer = endNode; + this.endOffset = endOffset; + + if ( !this.startContainer ) + { + this.startContainer = endNode; + this.startOffset = endOffset; + } + + updateCollapsed( this ); + }, + + setStartAfter : function( node ) + { + this.setStart( node.getParent(), node.getIndex() + 1 ); + }, + + setStartBefore : function( node ) + { + this.setStart( node.getParent(), node.getIndex() ); + }, + + setEndAfter : function( node ) + { + this.setEnd( node.getParent(), node.getIndex() + 1 ); + }, + + setEndBefore : function( node ) + { + this.setEnd( node.getParent(), node.getIndex() ); + }, + + setStartAt : function( node, position ) + { + switch( position ) + { + case CKEDITOR.POSITION_AFTER_START : + this.setStart( node, 0 ); + break; + + case CKEDITOR.POSITION_BEFORE_END : + if ( node.type == CKEDITOR.NODE_TEXT ) + this.setStart( node, node.getLength() ); + else + this.setStart( node, node.getChildCount() ); + break; + + case CKEDITOR.POSITION_BEFORE_START : + this.setStartBefore( node ); + break; + + case CKEDITOR.POSITION_AFTER_END : + this.setStartAfter( node ); + } + + updateCollapsed( this ); + }, + + setEndAt : function( node, position ) + { + switch( position ) + { + case CKEDITOR.POSITION_AFTER_START : + this.setEnd( node, 0 ); + break; + + case CKEDITOR.POSITION_BEFORE_END : + if ( node.type == CKEDITOR.NODE_TEXT ) + this.setEnd( node, node.getLength() ); + else + this.setEnd( node, node.getChildCount() ); + break; + + case CKEDITOR.POSITION_BEFORE_START : + this.setEndBefore( node ); + break; + + case CKEDITOR.POSITION_AFTER_END : + this.setEndAfter( node ); + } + + updateCollapsed( this ); + }, + + fixBlock : function( isStart, blockTag ) + { + var bookmark = this.createBookmark(), + fixedBlock = this.document.createElement( blockTag ); + + this.collapse( isStart ); + + this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS ); + + this.extractContents().appendTo( fixedBlock ); + fixedBlock.trim(); + + if ( !CKEDITOR.env.ie ) + fixedBlock.appendBogus(); + + this.insertNode( fixedBlock ); + + this.moveToBookmark( bookmark ); + + return fixedBlock; + }, + + splitBlock : function( blockTag ) + { + var startPath = new CKEDITOR.dom.elementPath( this.startContainer ), + endPath = new CKEDITOR.dom.elementPath( this.endContainer ); + + var startBlockLimit = startPath.blockLimit, + endBlockLimit = endPath.blockLimit; + + var startBlock = startPath.block, + endBlock = endPath.block; + + var elementPath = null; + // Do nothing if the boundaries are in different block limits. + if ( !startBlockLimit.equals( endBlockLimit ) ) + return null; + + // Get or fix current blocks. + if ( blockTag != 'br' ) + { + if ( !startBlock ) + { + startBlock = this.fixBlock( true, blockTag ); + endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block; + } + + if ( !endBlock ) + endBlock = this.fixBlock( false, blockTag ); + } + + // Get the range position. + var isStartOfBlock = startBlock && this.checkStartOfBlock(), + isEndOfBlock = endBlock && this.checkEndOfBlock(); + + // Delete the current contents. + // TODO: Why is 2.x doing CheckIsEmpty()? + this.deleteContents(); + + if ( startBlock && startBlock.equals( endBlock ) ) + { + if ( isEndOfBlock ) + { + elementPath = new CKEDITOR.dom.elementPath( this.startContainer ); + this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END ); + endBlock = null; + } + else if ( isStartOfBlock ) + { + elementPath = new CKEDITOR.dom.elementPath( this.startContainer ); + this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START ); + startBlock = null; + } + else + { + endBlock = this.splitElement( startBlock ); + + // In Gecko, the last child node must be a bogus
    . + // Note: bogus
    added under
      or
        would cause + // lists to be incorrectly rendered. + if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') ) + startBlock.appendBogus() ; + } + } + + return { + previousBlock : startBlock, + nextBlock : endBlock, + wasStartOfBlock : isStartOfBlock, + wasEndOfBlock : isEndOfBlock, + elementPath : elementPath + }; + }, + + /** + * Branch the specified element from the collapsed range position and + * place the caret between the two result branches. + * Note: The range must be collapsed and been enclosed by this element. + * @param {CKEDITOR.dom.element} element + * @return {CKEDITOR.dom.element} Root element of the new branch after the split. + */ + splitElement : function( toSplit ) + { + if ( !this.collapsed ) + return null; + + // Extract the contents of the block from the selection point to the end + // of its contents. + this.setEndAt( toSplit, CKEDITOR.POSITION_BEFORE_END ); + var documentFragment = this.extractContents(); + + // Duplicate the element after it. + var clone = toSplit.clone( false ); + + // Place the extracted contents into the duplicated element. + documentFragment.appendTo( clone ); + clone.insertAfter( toSplit ); + this.moveToPosition( toSplit, CKEDITOR.POSITION_AFTER_END ); + return clone; + }, + + /** + * Check whether current range is on the inner edge of the specified element. + * @param {Number} checkType ( CKEDITOR.START | CKEDITOR.END ) The checking side. + * @param {CKEDITOR.dom.element} element The target element to check. + */ + checkBoundaryOfElement : function( element, checkType ) + { + var walkerRange = this.clone(); + // Expand the range to element boundary. + walkerRange[ checkType == CKEDITOR.START ? + 'setStartAt' : 'setEndAt' ] + ( element, checkType == CKEDITOR.START ? + CKEDITOR.POSITION_AFTER_START + : CKEDITOR.POSITION_BEFORE_END ); + + var walker = new CKEDITOR.dom.walker( walkerRange ), + retval = false; + walker.evaluator = elementBoundaryEval; + return walker[ checkType == CKEDITOR.START ? + 'checkBackward' : 'checkForward' ](); + }, + // Calls to this function may produce changes to the DOM. The range may + // be updated to reflect such changes. + checkStartOfBlock : function() + { + var startContainer = this.startContainer, + startOffset = this.startOffset; + + // If the starting node is a text node, and non-empty before the offset, + // then we're surely not at the start of block. + if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT ) + { + var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) ); + if ( textBefore.length ) + return false; + } + + // Antecipate the trim() call here, so the walker will not make + // changes to the DOM, which would not get reflected into this + // range otherwise. + this.trim(); + + // We need to grab the block element holding the start boundary, so + // let's use an element path for it. + var path = new CKEDITOR.dom.elementPath( this.startContainer ); + + // Creates a range starting at the block start until the range start. + var walkerRange = this.clone(); + walkerRange.collapse( true ); + walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START ); + + var walker = new CKEDITOR.dom.walker( walkerRange ); + walker.evaluator = getCheckStartEndBlockEvalFunction( true ); + + return walker.checkBackward(); + }, + + checkEndOfBlock : function() + { + var endContainer = this.endContainer, + endOffset = this.endOffset; + + // If the ending node is a text node, and non-empty after the offset, + // then we're surely not at the end of block. + if ( endContainer.type == CKEDITOR.NODE_TEXT ) + { + var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) ); + if ( textAfter.length ) + return false; + } + + // Antecipate the trim() call here, so the walker will not make + // changes to the DOM, which would not get reflected into this + // range otherwise. + this.trim(); + + // We need to grab the block element holding the start boundary, so + // let's use an element path for it. + var path = new CKEDITOR.dom.elementPath( this.endContainer ); + + // Creates a range starting at the block start until the range start. + var walkerRange = this.clone(); + walkerRange.collapse( false ); + walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END ); + + var walker = new CKEDITOR.dom.walker( walkerRange ); + walker.evaluator = getCheckStartEndBlockEvalFunction( false ); + + return walker.checkForward(); + }, + + /** + * Moves the range boundaries to the first/end editing point inside an + * element. For example, in an element tree like + * "<p><b><i></i></b> Text</p>", the start editing point is + * "<p><b><i>^</i></b> Text</p>" (inside <i>). + * @param {CKEDITOR.dom.element} el The element into which look for the + * editing spot. + * @param {Boolean} isMoveToEnd Whether move to the end editable position. + */ + moveToElementEditablePosition : function( el, isMoveToEnd ) + { + var isEditable; + + // Empty elements are rejected. + if ( CKEDITOR.dtd.$empty[ el.getName() ] ) + return false; + + while ( el && el.type == CKEDITOR.NODE_ELEMENT ) + { + isEditable = el.isEditable(); + + // If an editable element is found, move inside it. + if ( isEditable ) + this.moveToPosition( el, isMoveToEnd ? + CKEDITOR.POSITION_BEFORE_END : + CKEDITOR.POSITION_AFTER_START ); + // Stop immediately if we've found a non editable inline element (e.g ). + else if ( CKEDITOR.dtd.$inline[ el.getName() ] ) + { + this.moveToPosition( el, isMoveToEnd ? + CKEDITOR.POSITION_AFTER_END : + CKEDITOR.POSITION_BEFORE_START ); + return true; + } + + // Non-editable non-inline elements are to be bypassed, getting the next one. + if ( CKEDITOR.dtd.$empty[ el.getName() ] ) + el = el[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval ); + else + el = el[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval ); + + // Stop immediately if we've found a text node. + if ( el && el.type == CKEDITOR.NODE_TEXT ) + { + this.moveToPosition( el, isMoveToEnd ? + CKEDITOR.POSITION_AFTER_END : + CKEDITOR.POSITION_BEFORE_START ); + return true; + } + } + + return isEditable; + }, + + /** + *@see {CKEDITOR.dom.range.moveToElementEditablePosition} + */ + moveToElementEditStart : function( target ) + { + return this.moveToElementEditablePosition( target ); + }, + + /** + *@see {CKEDITOR.dom.range.moveToElementEditablePosition} + */ + moveToElementEditEnd : function( target ) + { + return this.moveToElementEditablePosition( target, true ); + }, + + /** + * Get the single node enclosed within the range if there's one. + */ + getEnclosedNode : function() + { + var walkerRange = this.clone(); + + // Optimize and analyze the range to avoid DOM destructive nature of walker. (# + walkerRange.optimize(); + if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT + || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT ) + return null; + + var walker = new CKEDITOR.dom.walker( walkerRange ), + isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ), + isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), + evaluator = function( node ) + { + return isNotWhitespaces( node ) && isNotBookmarks( node ); + }; + walkerRange.evaluator = evaluator; + var node = walker.next(); + walker.reset(); + return node && node.equals( walker.previous() ) ? node : null; + }, + + getTouchedStartNode : function() + { + var container = this.startContainer ; + + if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT ) + return container ; + + return container.getChild( this.startOffset ) || container ; + }, + + getTouchedEndNode : function() + { + var container = this.endContainer ; + + if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT ) + return container ; + + return container.getChild( this.endOffset - 1 ) || container ; + } + }; +})(); + +CKEDITOR.POSITION_AFTER_START = 1; // ^contents "^text" +CKEDITOR.POSITION_BEFORE_END = 2; // contents^ "text^" +CKEDITOR.POSITION_BEFORE_START = 3; // ^contents ^"text" +CKEDITOR.POSITION_AFTER_END = 4; // contents^ "text" + +CKEDITOR.ENLARGE_ELEMENT = 1; +CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2; +CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3; + +/** + * Check boundary types. + * @see CKEDITOR.dom.range::checkBoundaryOfElement + */ +CKEDITOR.START = 1; +CKEDITOR.END = 2; +CKEDITOR.STARTEND = 3; + +CKEDITOR.SHRINK_ELEMENT = 1; +CKEDITOR.SHRINK_TEXT = 2; diff --git a/rte/_source/core/dom/text.js b/rte/_source/core/dom/text.js new file mode 100644 index 0000000..407c47f --- /dev/null +++ b/rte/_source/core/dom/text.js @@ -0,0 +1,123 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.dom.text} class, which represents + * a DOM text node. + */ + +/** + * Represents a DOM text node. + * @constructor + * @augments CKEDITOR.dom.node + * @param {Object|String} text A native DOM text node or a string containing + * the text to use to create a new text node. + * @param {CKEDITOR.dom.document} [ownerDocument] The document that will contain + * the node in case of new node creation. Defaults to the current document. + * @example + * var nativeNode = document.createTextNode( 'Example' ); + * var text = CKEDITOR.dom.text( nativeNode ); + * @example + * var text = CKEDITOR.dom.text( 'Example' ); + */ +CKEDITOR.dom.text = function( text, ownerDocument ) +{ + if ( typeof text == 'string' ) + text = ( ownerDocument ? ownerDocument.$ : document ).createTextNode( text ); + + // Theoretically, we should call the base constructor here + // (not CKEDITOR.dom.node though). But, IE doesn't support expando + // properties on text node, so the features provided by domObject will not + // work for text nodes (which is not a big issue for us). + // + // CKEDITOR.dom.domObject.call( this, element ); + + /** + * The native DOM text node represented by this class instance. + * @type Object + * @example + * var element = new CKEDITOR.dom.text( 'Example' ); + * alert( element.$.nodeType ); // "3" + */ + this.$ = text; +}; + +CKEDITOR.dom.text.prototype = new CKEDITOR.dom.node(); + +CKEDITOR.tools.extend( CKEDITOR.dom.text.prototype, + /** @lends CKEDITOR.dom.text.prototype */ + { + /** + * The node type. This is a constant value set to + * {@link CKEDITOR.NODE_TEXT}. + * @type Number + * @example + */ + type : CKEDITOR.NODE_TEXT, + + getLength : function() + { + return this.$.nodeValue.length; + }, + + getText : function() + { + return this.$.nodeValue; + }, + + /** + * Breaks this text node into two nodes at the specified offset, + * keeping both in the tree as siblings. This node then only contains + * all the content up to the offset point. A new text node, which is + * inserted as the next sibling of this node, contains all the content + * at and after the offset point. When the offset is equal to the + * length of this node, the new node has no data. + * @param {Number} The position at which to split, starting from zero. + * @returns {CKEDITOR.dom.text} The new text node. + */ + split : function( offset ) + { + // If the offset is after the last char, IE creates the text node + // on split, but don't include it into the DOM. So, we have to do + // that manually here. + if ( CKEDITOR.env.ie && offset == this.getLength() ) + { + var next = this.getDocument().createText( '' ); + next.insertAfter( this ); + return next; + } + + var doc = this.getDocument(); + var retval = new CKEDITOR.dom.text( this.$.splitText( offset ), doc ); + + // IE BUG: IE8 does not update the childNodes array in DOM after splitText(), + // we need to make some DOM changes to make it update. (#3436) + if ( CKEDITOR.env.ie8 ) + { + var workaround = new CKEDITOR.dom.text( '', doc ); + workaround.insertAfter( retval ); + workaround.remove(); + } + + return retval; + }, + + /** + * Extracts characters from indexA up to but not including indexB. + * @param {Number} indexA An integer between 0 and one less than the + * length of the text. + * @param {Number} [indexB] An integer between 0 and the length of the + * string. If omitted, extracts characters to the end of the text. + */ + substring : function( indexA, indexB ) + { + // We need the following check due to a Firefox bug + // https://bugzilla.mozilla.org/show_bug.cgi?id=458886 + if ( typeof indexB != 'number' ) + return this.$.nodeValue.substr( indexA ); + else + return this.$.nodeValue.substring( indexA, indexB ); + } + }); diff --git a/rte/_source/core/dom/walker.js b/rte/_source/core/dom/walker.js new file mode 100644 index 0000000..7c21747 --- /dev/null +++ b/rte/_source/core/dom/walker.js @@ -0,0 +1,451 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + // This function is to be called under a "walker" instance scope. + function iterate( rtl, breakOnFalse ) + { + // Return null if we have reached the end. + if ( this._.end ) + return null; + + var node, + range = this.range, + guard, + userGuard = this.guard, + type = this.type, + getSourceNodeFn = ( rtl ? 'getPreviousSourceNode' : 'getNextSourceNode' ); + + // This is the first call. Initialize it. + if ( !this._.start ) + { + this._.start = 1; + + // Trim text nodes and optmize the range boundaries. DOM changes + // may happen at this point. + range.trim(); + + // A collapsed range must return null at first call. + if ( range.collapsed ) + { + this.end(); + return null; + } + } + + // Create the LTR guard function, if necessary. + if ( !rtl && !this._.guardLTR ) + { + // Gets the node that stops the walker when going LTR. + var limitLTR = range.endContainer, + blockerLTR = limitLTR.getChild( range.endOffset ); + + this._.guardLTR = function( node, movingOut ) + { + return ( ( !movingOut || !limitLTR.equals( node ) ) + && ( !blockerLTR || !node.equals( blockerLTR ) ) + && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || node.getName() != 'body' ) ); + }; + } + + // Create the RTL guard function, if necessary. + if ( rtl && !this._.guardRTL ) + { + // Gets the node that stops the walker when going LTR. + var limitRTL = range.startContainer, + blockerRTL = ( range.startOffset > 0 ) && limitRTL.getChild( range.startOffset - 1 ); + + this._.guardRTL = function( node, movingOut ) + { + return ( ( !movingOut || !limitRTL.equals( node ) ) + && ( !blockerRTL || !node.equals( blockerRTL ) ) + && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || node.getName() != 'body' ) ); + }; + } + + // Define which guard function to use. + var stopGuard = rtl ? this._.guardRTL : this._.guardLTR; + + // Make the user defined guard function participate in the process, + // otherwise simply use the boundary guard. + if ( userGuard ) + { + guard = function( node, movingOut ) + { + if ( stopGuard( node, movingOut ) === false ) + return false; + + return userGuard( node, movingOut ); + }; + } + else + guard = stopGuard; + + if ( this.current ) + node = this.current[ getSourceNodeFn ]( false, type, guard ); + else + { + // Get the first node to be returned. + + if ( rtl ) + { + node = range.endContainer; + + if ( range.endOffset > 0 ) + { + node = node.getChild( range.endOffset - 1 ); + if ( guard( node ) === false ) + node = null; + } + else + node = ( guard ( node, true ) === false ) ? + null : node.getPreviousSourceNode( true, type, guard ); + } + else + { + node = range.startContainer; + node = node.getChild( range.startOffset ); + + if ( node ) + { + if ( guard( node ) === false ) + node = null; + } + else + node = ( guard ( range.startContainer, true ) === false ) ? + null : range.startContainer.getNextSourceNode( true, type, guard ) ; + } + } + + while ( node && !this._.end ) + { + this.current = node; + + if ( !this.evaluator || this.evaluator( node ) !== false ) + { + if ( !breakOnFalse ) + return node; + } + else if ( breakOnFalse && this.evaluator ) + return false; + + node = node[ getSourceNodeFn ]( false, type, guard ); + } + + this.end(); + return this.current = null; + } + + function iterateToLast( rtl ) + { + var node, last = null; + + while ( ( node = iterate.call( this, rtl ) ) ) + last = node; + + return last; + } + + CKEDITOR.dom.walker = CKEDITOR.tools.createClass( + { + /** + * Utility class to "walk" the DOM inside a range boundaries. If + * necessary, partially included nodes (text nodes) are broken to + * reflect the boundaries limits, so DOM and range changes may happen. + * Outside changes to the range may break the walker. + * + * The walker may return nodes that are not totaly included into the + * range boundaires. Let's take the following range representation, + * where the square brackets indicate the boundaries: + * + * [<p>Some <b>sample] text</b> + * + * While walking forward into the above range, the following nodes are + * returned: <p>, "Some ", <b> and "sample". Going + * backwards instead we have: "sample" and "Some ". So note that the + * walker always returns nodes when "entering" them, but not when + * "leaving" them. The guard function is instead called both when + * entering and leaving nodes. + * + * @constructor + * @param {CKEDITOR.dom.range} range The range within which walk. + */ + $ : function( range ) + { + this.range = range; + + /** + * A function executed for every matched node, to check whether + * it's to be considered into the walk or not. If not provided, all + * matched nodes are considered good. + * If the function returns "false" the node is ignored. + * @name CKEDITOR.dom.walker.prototype.evaluator + * @property + * @type Function + */ + // this.evaluator = null; + + /** + * A function executed for every node the walk pass by to check + * whether the walk is to be finished. It's called when both + * entering and exiting nodes, as well as for the matched nodes. + * If this function returns "false", the walking ends and no more + * nodes are evaluated. + * @name CKEDITOR.dom.walker.prototype.guard + * @property + * @type Function + */ + // this.guard = null; + + /** @private */ + this._ = {}; + }, + +// statics : +// { +// /* Creates a CKEDITOR.dom.walker instance to walk inside DOM boundaries set by nodes. +// * @param {CKEDITOR.dom.node} startNode The node from wich the walk +// * will start. +// * @param {CKEDITOR.dom.node} [endNode] The last node to be considered +// * in the walk. No more nodes are retrieved after touching or +// * passing it. If not provided, the walker stops at the +// * <body> closing boundary. +// * @returns {CKEDITOR.dom.walker} A DOM walker for the nodes between the +// * provided nodes. +// */ +// createOnNodes : function( startNode, endNode, startInclusive, endInclusive ) +// { +// var range = new CKEDITOR.dom.range(); +// if ( startNode ) +// range.setStartAt( startNode, startInclusive ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_END ) ; +// else +// range.setStartAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_AFTER_START ) ; +// +// if ( endNode ) +// range.setEndAt( endNode, endInclusive ? CKEDITOR.POSITION_AFTER_END : CKEDITOR.POSITION_BEFORE_START ) ; +// else +// range.setEndAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_BEFORE_END ) ; +// +// return new CKEDITOR.dom.walker( range ); +// } +// }, +// + proto : + { + /** + * Stop walking. No more nodes are retrieved if this function gets + * called. + */ + end : function() + { + this._.end = 1; + }, + + /** + * Retrieves the next node (at right). + * @returns {CKEDITOR.dom.node} The next node or null if no more + * nodes are available. + */ + next : function() + { + return iterate.call( this ); + }, + + /** + * Retrieves the previous node (at left). + * @returns {CKEDITOR.dom.node} The previous node or null if no more + * nodes are available. + */ + previous : function() + { + return iterate.call( this, true ); + }, + + /** + * Check all nodes at right, executing the evaluation fuction. + * @returns {Boolean} "false" if the evaluator function returned + * "false" for any of the matched nodes. Otherwise "true". + */ + checkForward : function() + { + return iterate.call( this, false, true ) !== false; + }, + + /** + * Check all nodes at left, executing the evaluation fuction. + * @returns {Boolean} "false" if the evaluator function returned + * "false" for any of the matched nodes. Otherwise "true". + */ + checkBackward : function() + { + return iterate.call( this, true, true ) !== false; + }, + + /** + * Executes a full walk forward (to the right), until no more nodes + * are available, returning the last valid node. + * @returns {CKEDITOR.dom.node} The last node at the right or null + * if no valid nodes are available. + */ + lastForward : function() + { + return iterateToLast.call( this ); + }, + + /** + * Executes a full walk backwards (to the left), until no more nodes + * are available, returning the last valid node. + * @returns {CKEDITOR.dom.node} The last node at the left or null + * if no valid nodes are available. + */ + lastBackward : function() + { + return iterateToLast.call( this, true ); + }, + + reset : function() + { + delete this.current; + this._ = {}; + } + + } + }); + + /* + * Anything whose display computed style is block, list-item, table, + * table-row-group, table-header-group, table-footer-group, table-row, + * table-column-group, table-column, table-cell, table-caption, or whose node + * name is hr, br (when enterMode is br only) is a block boundary. + */ + var blockBoundaryDisplayMatch = + { + block : 1, + 'list-item' : 1, + table : 1, + 'table-row-group' : 1, + 'table-header-group' : 1, + 'table-footer-group' : 1, + 'table-row' : 1, + 'table-column-group' : 1, + 'table-column' : 1, + 'table-cell' : 1, + 'table-caption' : 1 + }, + blockBoundaryNodeNameMatch = { hr : 1 }; + + CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames ) + { + var nodeNameMatches = CKEDITOR.tools.extend( {}, + blockBoundaryNodeNameMatch, customNodeNames || {} ); + + return blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ] || + nodeNameMatches[ this.getName() ]; + }; + + CKEDITOR.dom.walker.blockBoundary = function( customNodeNames ) + { + return function( node , type ) + { + return ! ( node.type == CKEDITOR.NODE_ELEMENT + && node.isBlockBoundary( customNodeNames ) ); + }; + }; + + CKEDITOR.dom.walker.listItemBoundary = function() + { + return this.blockBoundary( { br : 1 } ); + }; + /** + * Whether the node is a bookmark node's inner text node. + */ + CKEDITOR.dom.walker.bookmarkContents = function( node ) + { + }, + + /** + * Whether the to-be-evaluated node is a bookmark node OR bookmark node + * inner contents. + * @param {Boolean} contentOnly Whether only test againt the text content of + * bookmark node instead of the element itself(default). + * @param {Boolean} isReject Whether should return 'false' for the bookmark + * node instead of 'true'(default). + */ + CKEDITOR.dom.walker.bookmark = function( contentOnly, isReject ) + { + function isBookmarkNode( node ) + { + return ( node && node.getName + && node.getName() == 'span' + && node.hasAttribute('_fck_bookmark') ); + } + + return function( node ) + { + var isBookmark, parent; + // Is bookmark inner text node? + isBookmark = ( node && !node.getName && ( parent = node.getParent() ) + && isBookmarkNode( parent ) ); + // Is bookmark node? + isBookmark = contentOnly ? isBookmark : isBookmark || isBookmarkNode( node ); + return isReject ^ isBookmark; + }; + }; + + /** + * Whether the node is a text node containing only whitespaces characters. + * @param isReject + */ + CKEDITOR.dom.walker.whitespaces = function( isReject ) + { + return function( node ) + { + var isWhitespace = node && ( node.type == CKEDITOR.NODE_TEXT ) + && !CKEDITOR.tools.trim( node.getText() ); + return isReject ^ isWhitespace; + }; + }; + + /** + * Whether the node is invisible in wysiwyg mode. + * @param isReject + */ + CKEDITOR.dom.walker.invisible = function( isReject ) + { + var whitespace = CKEDITOR.dom.walker.whitespaces(); + return function( node ) + { + // Nodes that take no spaces in wysiwyg: + // 1. White-spaces but not including NBSP; + // 2. Empty inline elements, e.g. we're checking here + // 'offsetHeight' instead of 'offsetWidth' for properly excluding + // all sorts of empty paragraph, e.g.
        . + var isInvisible = whitespace( node ) || node.is && !node.$.offsetHeight; + return isReject ^ isInvisible; + }; + }; + + var tailNbspRegex = /^[\t\r\n ]*(?: |\xa0)$/, + isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), + isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true ), + fillerEvaluator = function( element ) + { + return isNotBookmark( element ) && isNotWhitespaces( element ); + }; + + // Check if there's a filler node at the end of an element, and return it. + CKEDITOR.dom.element.prototype.getBogus = function () + { + var tail = this.getLast( fillerEvaluator ); + if ( tail && ( !CKEDITOR.env.ie ? tail.is && tail.is( 'br' ) + : tail.getText && tailNbspRegex.test( tail.getText() ) ) ) + { + return tail; + } + return false; + }; + +})(); diff --git a/rte/_source/core/dom/window.js b/rte/_source/core/dom/window.js new file mode 100644 index 0000000..022101f --- /dev/null +++ b/rte/_source/core/dom/window.js @@ -0,0 +1,96 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.dom.document} class, which + * represents a DOM document. + */ + +/** + * Represents a DOM window. + * @constructor + * @augments CKEDITOR.dom.domObject + * @param {Object} domWindow A native DOM window. + * @example + * var document = new CKEDITOR.dom.window( window ); + */ +CKEDITOR.dom.window = function( domWindow ) +{ + CKEDITOR.dom.domObject.call( this, domWindow ); +}; + +CKEDITOR.dom.window.prototype = new CKEDITOR.dom.domObject(); + +CKEDITOR.tools.extend( CKEDITOR.dom.window.prototype, + /** @lends CKEDITOR.dom.window.prototype */ + { + /** + * Moves the selection focus to this window. + * @function + * @example + * var win = new CKEDITOR.dom.window( window ); + * win.focus(); + */ + focus : function() + { + // Webkit is sometimes failed to focus iframe, blur it first(#3835). + if ( CKEDITOR.env.webkit && this.$.parent ) + this.$.parent.focus(); + this.$.focus(); + }, + + /** + * Gets the width and height of this window's viewable area. + * @function + * @returns {Object} An object with the "width" and "height" + * properties containing the size. + * @example + * var win = new CKEDITOR.dom.window( window ); + * var size = win.getViewPaneSize(); + * alert( size.width ); + * alert( size.height ); + */ + getViewPaneSize : function() + { + var doc = this.$.document, + stdMode = doc.compatMode == 'CSS1Compat'; + return { + width : ( stdMode ? doc.documentElement.clientWidth : doc.body.clientWidth ) || 0, + height : ( stdMode ? doc.documentElement.clientHeight : doc.body.clientHeight ) || 0 + }; + }, + + /** + * Gets the current position of the window's scroll. + * @function + * @returns {Object} An object with the "x" and "y" properties + * containing the scroll position. + * @example + * var win = new CKEDITOR.dom.window( window ); + * var pos = win.getScrollPosition(); + * alert( pos.x ); + * alert( pos.y ); + */ + getScrollPosition : function() + { + var $ = this.$; + + if ( 'pageXOffset' in $ ) + { + return { + x : $.pageXOffset || 0, + y : $.pageYOffset || 0 + }; + } + else + { + var doc = $.document; + return { + x : doc.documentElement.scrollLeft || doc.body.scrollLeft || 0, + y : doc.documentElement.scrollTop || doc.body.scrollTop || 0 + }; + } + } + }); diff --git a/rte/_source/core/dtd.js b/rte/_source/core/dtd.js new file mode 100644 index 0000000..bff1e1d --- /dev/null +++ b/rte/_source/core/dtd.js @@ -0,0 +1,233 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.dtd} object, which holds the DTD + * mapping for XHTML 1.0 Transitional. This file was automatically + * generated from the file: xhtml1-transitional.dtd. + */ + +/** + * Holds and object representation of the HTML DTD to be used by the editor in + * its internal operations. + * + * Each element in the DTD is represented by a + * property in this object. Each property contains the list of elements that + * can be contained by the element. Text is represented by the "#" property. + * + * Several special grouping properties are also available. Their names start + * with the "$" character. + * @namespace + * @example + * // Check if "div" can be contained in a "p" element. + * alert( !!CKEDITOR.dtd[ 'p' ][ 'div' ] ); "false" + * @example + * // Check if "p" can be contained in a "div" element. + * alert( !!CKEDITOR.dtd[ 'div' ][ 'p' ] ); "true" + * @example + * // Check if "p" is a block element. + * alert( !!CKEDITOR.dtd.$block[ 'p' ] ); "true" + */ +CKEDITOR.dtd = (function() +{ + var X = CKEDITOR.tools.extend, + + A = {isindex:1,fieldset:1}, + B = {input:1,button:1,select:1,textarea:1,label:1}, + C = X({a:1},B), + D = X({iframe:1},C), + E = {hr:1,ul:1,menu:1,div:1,blockquote:1,noscript:1,table:1,center:1,address:1,dir:1,pre:1,h5:1,dl:1,h4:1,noframes:1,h6:1,ol:1,h1:1,h3:1,h2:1}, + F = {ins:1,del:1,script:1,style:1}, + G = X({b:1,acronym:1,bdo:1,'var':1,'#':1,abbr:1,code:1,br:1,i:1,cite:1,kbd:1,u:1,strike:1,s:1,tt:1,strong:1,q:1,samp:1,em:1,dfn:1,span:1},F), + H = X({sub:1,img:1,object:1,sup:1,basefont:1,map:1,applet:1,font:1,big:1,small:1},G), + I = X({p:1},H), + J = X({iframe:1},H,B), + K = {img:1,noscript:1,br:1,kbd:1,center:1,button:1,basefont:1,h5:1,h4:1,samp:1,h6:1,ol:1,h1:1,h3:1,h2:1,form:1,font:1,'#':1,select:1,menu:1,ins:1,abbr:1,label:1,code:1,table:1,script:1,cite:1,input:1,iframe:1,strong:1,textarea:1,noframes:1,big:1,small:1,span:1,hr:1,sub:1,bdo:1,'var':1,div:1,object:1,sup:1,strike:1,dir:1,map:1,dl:1,applet:1,del:1,isindex:1,fieldset:1,ul:1,b:1,acronym:1,a:1,blockquote:1,i:1,u:1,s:1,tt:1,address:1,q:1,pre:1,p:1,em:1,dfn:1}, + + L = X({a:1},J), + M = {tr:1}, + N = {'#':1}, + O = X({param:1},K), + P = X({form:1},A,D,E,I), + Q = {li:1}, + R = {style:1,script:1}, + S = {base:1,link:1,meta:1,title:1}, + T = X(S,R), + U = {head:1,body:1}, + V = {html:1}; + + var block = {address:1,blockquote:1,center:1,dir:1,div:1,dl:1,fieldset:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,isindex:1,menu:1,noframes:1,ol:1,p:1,pre:1,table:1,ul:1}; + + return /** @lends CKEDITOR.dtd */ { + + // The "$" items have been added manually. + + // List of elements living outside body. + $nonBodyContent: X(V,U,S), + + /** + * List of block elements, like "p" or "div". + * @type Object + * @example + */ + $block : block, + + /** + * List of block limit elements. + * @type Object + * @example + */ + $blockLimit : { body:1,div:1,td:1,th:1,caption:1,form:1 }, + + $inline : L, // Just like span. + + $body : X({script:1,style:1}, block), + + $cdata : {script:1,style:1}, + + /** + * List of empty (self-closing) elements, like "br" or "img". + * @type Object + * @example + */ + $empty : {area:1,base:1,br:1,col:1,hr:1,img:1,input:1,link:1,meta:1,param:1}, + + /** + * List of list item elements, like "li" or "dd". + * @type Object + * @example + */ + $listItem : {dd:1,dt:1,li:1}, + + /** + * List of list root elements. + * @type Object + * @example + */ + $list: { ul:1,ol:1,dl:1}, + + /** + * Elements that accept text nodes, but are not possible to edit into + * the browser. + * @type Object + * @example + */ + $nonEditable : {applet:1,button:1,embed:1,iframe:1,map:1,object:1,option:1,script:1,textarea:1,param:1}, + + /** + * List of elements that can be ignored if empty, like "b" or "span". + * @type Object + * @example + */ + $removeEmpty : {abbr:1,acronym:1,address:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,s:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1}, + + /** + * List of elements that have tabindex set to zero by default. + * @type Object + * @example + */ + $tabIndex : {a:1,area:1,button:1,input:1,object:1,select:1,textarea:1}, + + /** + * List of elements used inside the "table" element, like "tbody" or "td". + * @type Object + * @example + */ + $tableContent : {caption:1,col:1,colgroup:1,tbody:1,td:1,tfoot:1,th:1,thead:1,tr:1}, + + html: U, + head: T, + style: N, + script: N, + body: P, + base: {}, + link: {}, + meta: {}, + title: N, + col : {}, + tr : {td:1,th:1}, + img : {}, + colgroup : {col:1}, + noscript : P, + td : P, + br : {}, + th : P, + center : P, + kbd : L, + button : X(I,E), + basefont : {}, + h5 : L, + h4 : L, + samp : L, + h6 : L, + ol : Q, + h1 : L, + h3 : L, + option : N, + h2 : L, + form : X(A,D,E,I), + select : {optgroup:1,option:1}, + font : L, + ins : L, + menu : Q, + abbr : L, + label : L, + table : {thead:1,col:1,tbody:1,tr:1,colgroup:1,caption:1,tfoot:1}, + code : L, + script : N, + tfoot : M, + cite : L, + li : P, + input : {}, + iframe : P, + strong : L, + textarea : N, + noframes : P, + big : L, + small : L, + span : L, + hr : {}, + dt : L, + sub : L, + optgroup : {option:1}, + param : {}, + bdo : L, + 'var' : L, + div : P, + object : O, + sup : L, + dd : P, + strike : L, + area : {}, + dir : Q, + map : X({area:1,form:1,p:1},A,F,E), + applet : O, + dl : {dt:1,dd:1}, + del : L, + isindex : {}, + fieldset : X({legend:1},K), + thead : M, + ul : Q, + acronym : L, + b : L, + a : J, + blockquote : P, + caption : L, + i : L, + u : L, + tbody : M, + s : L, + address : X(D,I), + tt : L, + legend : L, + q : L, + pre : X(G,C), + p : L, + em : L, + dfn : L + }; +})(); + +// PACKAGER_RENAME( CKEDITOR.dtd ) diff --git a/rte/_source/core/editor.js b/rte/_source/core/editor.js new file mode 100644 index 0000000..1ce7a3c --- /dev/null +++ b/rte/_source/core/editor.js @@ -0,0 +1,759 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.editor} class, which represents an + * editor instance. + */ + +(function() +{ + // The counter for automatic instance names. + var nameCounter = 0; + + var getNewName = function() + { + var name = 'editor' + ( ++nameCounter ); + return ( CKEDITOR.instances && CKEDITOR.instances[ name ] ) ? getNewName() : name; + }; + + // ##### START: Config Privates + + // These function loads custom configuration files and cache the + // CKEDITOR.editorConfig functions defined on them, so there is no need to + // download them more than once for several instances. + var loadConfigLoaded = {}; + var loadConfig = function( editor ) + { + var customConfig = editor.config.customConfig; + + // Check if there is a custom config to load. + if ( !customConfig ) + return false; + + customConfig = CKEDITOR.getUrl( customConfig ); + + var loadedConfig = loadConfigLoaded[ customConfig ] || ( loadConfigLoaded[ customConfig ] = {} ); + + // If the custom config has already been downloaded, reuse it. + if ( loadedConfig.fn ) + { + // Call the cached CKEDITOR.editorConfig defined in the custom + // config file for the editor instance depending on it. + loadedConfig.fn.call( editor, editor.config ); + + // If there is no other customConfig in the chain, fire the + // "configLoaded" event. + if ( CKEDITOR.getUrl( editor.config.customConfig ) == customConfig || !loadConfig( editor ) ) + editor.fireOnce( 'customConfigLoaded' ); + } + else + { + // Load the custom configuration file. + CKEDITOR.scriptLoader.load( customConfig, function() + { + // If the CKEDITOR.editorConfig function has been properly + // defined in the custom configuration file, cache it. + if ( CKEDITOR.editorConfig ) + loadedConfig.fn = CKEDITOR.editorConfig; + else + loadedConfig.fn = function(){}; + + // Call the load config again. This time the custom + // config is already cached and so it will get loaded. + loadConfig( editor ); + }); + } + + return true; + }; + + var initConfig = function( editor, instanceConfig ) + { + // Setup the lister for the "customConfigLoaded" event. + editor.on( 'customConfigLoaded', function() + { + if ( instanceConfig ) + { + // Register the events that may have been set at the instance + // configuration object. + if ( instanceConfig.on ) + { + for ( var eventName in instanceConfig.on ) + { + editor.on( eventName, instanceConfig.on[ eventName ] ); + } + } + + // Overwrite the settings from the in-page config. + CKEDITOR.tools.extend( editor.config, instanceConfig, true ); + + delete editor.config.on; + } + + onConfigLoaded( editor ); + }); + + // The instance config may override the customConfig setting to avoid + // loading the default ~/config.js file. + if ( instanceConfig && instanceConfig.customConfig != undefined ) + editor.config.customConfig = instanceConfig.customConfig; + + // Load configs from the custom configuration files. + if ( !loadConfig( editor ) ) + editor.fireOnce( 'customConfigLoaded' ); + }; + + // ##### END: Config Privates + + var onConfigLoaded = function( editor ) + { + // Set config related properties. + + var skin = editor.config.skin.split( ',' ), + skinName = skin[ 0 ], + skinPath = CKEDITOR.getUrl( skin[ 1 ] || ( + '_source/' + // @Packager.RemoveLine + 'skins/' + skinName + '/' ) ); + + editor.skinName = skinName; + editor.skinPath = skinPath; + editor.skinClass = 'cke_skin_' + skinName; + + editor.tabIndex = editor.config.tabIndex || editor.element.getAttribute( 'tabindex' ) || 0; + + // Fire the "configLoaded" event. + editor.fireOnce( 'configLoaded' ); + + // Load language file. + loadSkin( editor ); + }; + + var loadLang = function( editor ) + { + CKEDITOR.lang.load( editor.config.language, editor.config.defaultLanguage, function( languageCode, lang ) + { + editor.langCode = languageCode; + + // As we'll be adding plugin specific entries that could come + // from different language code files, we need a copy of lang, + // not a direct reference to it. + editor.lang = CKEDITOR.tools.prototypedCopy( lang ); + + // We're not able to support RTL in Firefox 2 at this time. + if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 && editor.lang.dir == 'rtl' ) + editor.lang.dir = 'ltr'; + + var config = editor.config; + config.contentsLangDirection == 'ui' && ( config.contentsLangDirection = editor.lang.dir ); + + loadPlugins( editor ); + }); + }; + + var loadPlugins = function( editor ) + { + var config = editor.config, + plugins = config.plugins, + extraPlugins = config.extraPlugins, + removePlugins = config.removePlugins; + + if ( extraPlugins ) + { + // Remove them first to avoid duplications. + var removeRegex = new RegExp( '(?:^|,)(?:' + extraPlugins.replace( /\s*,\s*/g, '|' ) + ')(?=,|$)' , 'g' ); + plugins = plugins.replace( removeRegex, '' ); + + plugins += ',' + extraPlugins; + } + + if ( removePlugins ) + { + removeRegex = new RegExp( '(?:^|,)(?:' + removePlugins.replace( /\s*,\s*/g, '|' ) + ')(?=,|$)' , 'g' ); + plugins = plugins.replace( removeRegex, '' ); + } + + // Load all plugins defined in the "plugins" setting. + CKEDITOR.plugins.load( plugins.split( ',' ), function( plugins ) + { + // The list of plugins. + var pluginsArray = []; + + // The language code to get loaded for each plugin. Null + // entries will be appended for plugins with no language files. + var languageCodes = []; + + // The list of URLs to language files. + var languageFiles = []; + + // Cache the loaded plugin names. + editor.plugins = plugins; + + // Loop through all plugins, to build the list of language + // files to get loaded. + for ( var pluginName in plugins ) + { + var plugin = plugins[ pluginName ], + pluginLangs = plugin.lang, + pluginPath = CKEDITOR.plugins.getPath( pluginName ), + lang = null; + + // Set the plugin path in the plugin. + plugin.path = pluginPath; + + // If the plugin has "lang". + if ( pluginLangs ) + { + // Resolve the plugin language. If the current language + // is not available, get the first one (default one). + lang = ( CKEDITOR.tools.indexOf( pluginLangs, editor.langCode ) >= 0 ? editor.langCode : pluginLangs[ 0 ] ); + + if ( !plugin.lang[ lang ] ) + { + // Put the language file URL into the list of files to + // get downloaded. + languageFiles.push( CKEDITOR.getUrl( pluginPath + 'lang/' + lang + '.js' ) ); + } + else + { + CKEDITOR.tools.extend( editor.lang, plugin.lang[ lang ] ); + lang = null; + } + } + + // Save the language code, so we know later which + // language has been resolved to this plugin. + languageCodes.push( lang ); + + pluginsArray.push( plugin ); + } + + // Load all plugin specific language files in a row. + CKEDITOR.scriptLoader.load( languageFiles, function() + { + // Initialize all plugins that have the "beforeInit" and "init" methods defined. + var methods = [ 'beforeInit', 'init', 'afterInit' ]; + for ( var m = 0 ; m < methods.length ; m++ ) + { + for ( var i = 0 ; i < pluginsArray.length ; i++ ) + { + var plugin = pluginsArray[ i ]; + + // Uses the first loop to update the language entries also. + if ( m === 0 && languageCodes[ i ] && plugin.lang ) + CKEDITOR.tools.extend( editor.lang, plugin.lang[ languageCodes[ i ] ] ); + + // Call the plugin method (beforeInit and init). + if ( plugin[ methods[ m ] ] ) + plugin[ methods[ m ] ]( editor ); + } + } + + // Load the editor skin. + editor.fire( 'pluginsLoaded' ); + loadTheme( editor ); + }); + }); + }; + + var loadSkin = function( editor ) + { + CKEDITOR.skins.load( editor, 'editor', function() + { + loadLang( editor ); + }); + }; + + var loadTheme = function( editor ) + { + var theme = editor.config.theme; + CKEDITOR.themes.load( theme, function() + { + var editorTheme = editor.theme = CKEDITOR.themes.get( theme ); + editorTheme.path = CKEDITOR.themes.getPath( theme ); + editorTheme.build( editor ); + + if ( editor.config.autoUpdateElement ) + attachToForm( editor ); + }); + }; + + var attachToForm = function( editor ) + { + var element = editor.element; + + // If are replacing a textarea, we must + if ( editor.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE && element.is( 'textarea' ) ) + { + var form = element.$.form && new CKEDITOR.dom.element( element.$.form ); + if ( form ) + { + function onSubmit() + { + editor.updateElement(); + } + form.on( 'submit',onSubmit ); + + // Setup the submit function because it doesn't fire the + // "submit" event. + if ( !form.$.submit.nodeName ) + { + form.$.submit = CKEDITOR.tools.override( form.$.submit, function( originalSubmit ) + { + return function() + { + editor.updateElement(); + + // For IE, the DOM submit function is not a + // function, so we need thid check. + if ( originalSubmit.apply ) + originalSubmit.apply( this, arguments ); + else + originalSubmit(); + }; + }); + } + + // Remove 'submit' events registered on form element before destroying.(#3988) + editor.on( 'destroy', function() + { + form.removeListener( 'submit', onSubmit ); + } ); + } + } + }; + + function updateCommandsMode() + { + var command, + commands = this._.commands, + mode = this.mode; + + for ( var name in commands ) + { + command = commands[ name ]; + command[ command.startDisabled ? 'disable' : command.modes[ mode ] ? 'enable' : 'disable' ](); + } + } + + /** + * Initializes the editor instance. This function is called by the editor + * contructor (editor_basic.js). + * @private + */ + CKEDITOR.editor.prototype._init = function() + { + // Get the properties that have been saved in the editor_base + // implementation. + var element = CKEDITOR.dom.element.get( this._.element ), + instanceConfig = this._.instanceConfig; + delete this._.element; + delete this._.instanceConfig; + + this._.commands = {}; + this._.styles = []; + + /** + * The DOM element that has been replaced by this editor instance. This + * element holds the editor data on load and post. + * @name CKEDITOR.editor.prototype.element + * @type CKEDITOR.dom.element + * @example + * var editor = CKEDITOR.instances.editor1; + * alert( editor.element.getName() ); "textarea" + */ + this.element = element; + + /** + * The editor instance name. It hay be the replaced element id, name or + * a default name using a progressive counter (editor1, editor2, ...). + * @name CKEDITOR.editor.prototype.name + * @type String + * @example + * var editor = CKEDITOR.instances.editor1; + * alert( editor.name ); "editor1" + */ + this.name = ( element && ( this.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) + && ( element.getId() || element.getNameAtt() ) ) + || getNewName(); + + if ( this.name in CKEDITOR.instances ) + throw '[CKEDITOR.editor] The instance "' + this.name + '" already exists.'; + + /** + * The configurations for this editor instance. It inherits all + * settings defined in (@link CKEDITOR.config}, combined with settings + * loaded from custom configuration files and those defined inline in + * the page when creating the editor. + * @name CKEDITOR.editor.prototype.config + * @type Object + * @example + * var editor = CKEDITOR.instances.editor1; + * alert( editor.config.theme ); "default" e.g. + */ + this.config = CKEDITOR.tools.prototypedCopy( CKEDITOR.config ); + + /** + * Namespace containing UI features related to this editor instance. + * @name CKEDITOR.editor.prototype.ui + * @type CKEDITOR.ui + * @example + */ + this.ui = new CKEDITOR.ui( this ); + + /** + * Controls the focus state of this editor instance. This property + * is rarely used for normal API operations. It is mainly + * destinated to developer adding UI elements to the editor interface. + * @name CKEDITOR.editor.prototype.focusManager + * @type CKEDITOR.focusManager + * @example + */ + this.focusManager = new CKEDITOR.focusManager( this ); + + CKEDITOR.fire( 'instanceCreated', null, this ); + + this.on( 'mode', updateCommandsMode, null, null, 1 ); + + initConfig( this, instanceConfig ); + }; +})(); + +CKEDITOR.tools.extend( CKEDITOR.editor.prototype, + /** @lends CKEDITOR.editor.prototype */ + { + /** + * Adds a command definition to the editor instance. Commands added with + * this function can be later executed with {@link #execCommand}. + * @param {String} commandName The indentifier name of the command. + * @param {CKEDITOR.commandDefinition} commandDefinition The command definition. + * @example + * editorInstance.addCommand( 'sample', + * { + * exec : function( editor ) + * { + * alert( 'Executing a command for the editor name "' + editor.name + '"!' ); + * } + * }); + */ + addCommand : function( commandName, commandDefinition ) + { + return this._.commands[ commandName ] = new CKEDITOR.command( this, commandDefinition ); + }, + + /** + * Add a trunk of css text to the editor which will be applied to the wysiwyg editing document. + * Note: This function should be called before editor is loaded to take effect. + * @param css {String} CSS text. + * @example + * editorInstance.addCss( 'body { background-color: grey; }' ); + */ + addCss : function( css ) + { + this._.styles.push( css ); + }, + + /** + * Destroys the editor instance, releasing all resources used by it. + * If the editor replaced an element, the element will be recovered. + * @param {Boolean} [noUpdate] If the instance is replacing a DOM + * element, this parameter indicates whether or not to update the + * element with the instance contents. + * @example + * alert( CKEDITOR.instances.editor1 ); e.g "object" + * CKEDITOR.instances.editor1.destroy(); + * alert( CKEDITOR.instances.editor1 ); "undefined" + */ + destroy : function( noUpdate ) + { + if ( !noUpdate ) + this.updateElement(); + + if ( this.mode ) + { + // -> currentMode.unload( holderElement ); + this._.modes[ this.mode ].unload( this.getThemeSpace( 'contents' ) ); + } + + this.theme.destroy( this ); + + var toolbars, + index = 0, + j, + items, + instance; + + if ( this.toolbox ) + { + toolbars = this.toolbox.toolbars; + for ( ; index < toolbars.length ; index++ ) + { + items = toolbars[ index ].items; + for ( j = 0 ; j < items.length ; j++ ) + { + instance = items[ j ]; + if ( instance.clickFn ) CKEDITOR.tools.removeFunction( instance.clickFn ); + if ( instance.keyDownFn ) CKEDITOR.tools.removeFunction( instance.keyDownFn ); + + if ( instance.index ) CKEDITOR.ui.button._.instances[ instance.index ] = null; + } + } + } + + if ( this.contextMenu ) + CKEDITOR.tools.removeFunction( this.contextMenu._.functionId ); + + if ( this._.filebrowserFn ) + CKEDITOR.tools.removeFunction( this._.filebrowserFn ); + + this.fire( 'destroy' ); + CKEDITOR.remove( this ); + CKEDITOR.fire( 'instanceDestroyed', null, this ); + }, + + /** + * Executes a command. + * @param {String} commandName The indentifier name of the command. + * @param {Object} [data] Data to be passed to the command + * @returns {Boolean} "true" if the command has been successfuly + * executed, otherwise "false". + * @example + * editorInstance.execCommand( 'Bold' ); + */ + execCommand : function( commandName, data ) + { + var command = this.getCommand( commandName ); + + var eventData = + { + name: commandName, + commandData: data, + command: command + }; + + if ( command && command.state != CKEDITOR.TRISTATE_DISABLED ) + { + if ( this.fire( 'beforeCommandExec', eventData ) !== true ) + { + eventData.returnValue = command.exec( eventData.commandData ); + + // Fire the 'afterCommandExec' immediately if command is synchronous. + if ( !command.async && this.fire( 'afterCommandExec', eventData ) !== true ) + return eventData.returnValue; + } + } + + // throw 'Unknown command name "' + commandName + '"'; + return false; + }, + + /** + * Gets one of the registered commands. Note that, after registering a + * command definition with addCommand, it is transformed internally + * into an instance of {@link CKEDITOR.command}, which will be then + * returned by this function. + * @param {String} commandName The name of the command to be returned. + * This is the same used to register the command with addCommand. + * @returns {CKEDITOR.command} The command object identified by the + * provided name. + */ + getCommand : function( commandName ) + { + return this._.commands[ commandName ]; + }, + + /** + * Gets the editor data. The data will be in raw format. It is the same + * data that is posted by the editor. + * @type String + * @returns (String) The editor data. + * @example + * if ( CKEDITOR.instances.editor1.getData() == '' ) + * alert( 'There is no data available' ); + */ + getData : function() + { + this.fire( 'beforeGetData' ); + + var eventData = this._.data; + + if ( typeof eventData != 'string' ) + { + var element = this.element; + if ( element && this.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) + eventData = element.is( 'textarea' ) ? element.getValue() : element.getHtml(); + else + eventData = ''; + } + + eventData = { dataValue : eventData }; + + // Fire "getData" so data manipulation may happen. + this.fire( 'getData', eventData ); + + return eventData.dataValue; + }, + + getSnapshot : function() + { + var data = this.fire( 'getSnapshot' ); + + if ( typeof data != 'string' ) + { + var element = this.element; + if ( element && this.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) + data = element.is( 'textarea' ) ? element.getValue() : element.getHtml(); + } + + return data; + }, + + loadSnapshot : function( snapshot ) + { + this.fire( 'loadSnapshot', snapshot ); + }, + + /** + * Sets the editor data. The data must be provided in raw format (HTML).
        + *
        + * Note that this menthod is asynchronous. The "callback" parameter must + * be used if interaction with the editor is needed after setting the data. + * @param {String} data HTML code to replace the curent content in the + * editor. + * @param {Function} callback Function to be called after the setData + * is completed. + * @example + * CKEDITOR.instances.editor1.setData( '<p>This is the editor data.</p>' ); + * @example + * CKEDITOR.instances.editor1.setData( '<p>Some other editor data.</p>', function() + * { + * this.checkDirty(); // true + * }); + */ + setData : function( data , callback ) + { + if( callback ) + { + this.on( 'dataReady', function( evt ) + { + evt.removeListener(); + callback.call( evt.editor ); + } ); + } + + // Fire "setData" so data manipulation may happen. + var eventData = { dataValue : data }; + this.fire( 'setData', eventData ); + + this._.data = eventData.dataValue; + + this.fire( 'afterSetData', eventData ); + }, + + /** + * Inserts HTML into the currently selected position in the editor. + * @param {String} data HTML code to be inserted into the editor. + * @example + * CKEDITOR.instances.editor1.insertHtml( '<p>This is a new paragraph.</p>' ); + */ + insertHtml : function( data ) + { + this.fire( 'insertHtml', data ); + }, + + /** + * Inserts an element into the currently selected position in the + * editor. + * @param {CKEDITOR.dom.element} element The element to be inserted + * into the editor. + * @example + * var element = CKEDITOR.dom.element.createFromHtml( '<img src="hello.png" border="0" title="Hello" />' ); + * CKEDITOR.instances.editor1.insertElement( element ); + */ + insertElement : function( element ) + { + this.fire( 'insertElement', element ); + }, + + checkDirty : function() + { + return ( this.mayBeDirty && this._.previousValue !== this.getSnapshot() ); + }, + + resetDirty : function() + { + if ( this.mayBeDirty ) + this._.previousValue = this.getSnapshot(); + }, + + /** + * Updates the <textarea> element that has been replaced by the editor with + * the current data available in the editor. + * @example + * CKEDITOR.instances.editor1.updateElement(); + * alert( document.getElementById( 'editor1' ).value ); // The current editor data. + */ + updateElement : function() + { + var element = this.element; + if ( element && this.elementMode == CKEDITOR.ELEMENT_MODE_REPLACE ) + { + var data = this.getData(); + + if ( this.config.htmlEncodeOutput ) + data = CKEDITOR.tools.htmlEncode( data ); + + if ( element.is( 'textarea' ) ) + element.setValue( data ); + else + element.setHtml( data ); + } + } + }); + +CKEDITOR.on( 'loaded', function() + { + // Run the full initialization for pending editors. + var pending = CKEDITOR.editor._pending; + if ( pending ) + { + delete CKEDITOR.editor._pending; + + for ( var i = 0 ; i < pending.length ; i++ ) + pending[ i ]._init(); + } + }); + +/** + * Whether escape HTML when editor update original input element. + * @name CKEDITOR.config.htmlEncodeOutput + * @since 3.1 + * @type Boolean + * @default false + * @example + * config.htmlEncodeOutput = true; + */ + +/** + * Fired when a CKEDITOR instance is created, but still before initializing it. + * To interact with a fully initialized instance, use the + * {@link CKEDITOR#instanceReady} event instead. + * @name CKEDITOR#instanceCreated + * @event + * @param {CKEDITOR.editor} editor The editor instance that has been created. + */ + +/** + * Fired when a CKEDITOR instance is destroyed. + * @name CKEDITOR#instanceDestroyed + * @event + * @param {CKEDITOR.editor} editor The editor instance that has been destroyed. + */ + +/** + * Fired when all plugins are loaded and initialized into the editor instance. + * @name CKEDITOR#pluginsLoaded + * @event + */ diff --git a/rte/_source/core/editor_basic.js b/rte/_source/core/editor_basic.js new file mode 100644 index 0000000..0a1b7f6 --- /dev/null +++ b/rte/_source/core/editor_basic.js @@ -0,0 +1,182 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +if ( !CKEDITOR.editor ) +{ + /** + * No element is linked to the editor instance. + * @constant + * @example + */ + CKEDITOR.ELEMENT_MODE_NONE = 0; + + /** + * The element is to be replaced by the editor instance. + * @constant + * @example + */ + CKEDITOR.ELEMENT_MODE_REPLACE = 1; + + /** + * The editor is to be created inside the element. + * @constant + * @example + */ + CKEDITOR.ELEMENT_MODE_APPENDTO = 2; + + /** + * Represents an editor instance. This constructor should be rarely used, + * being the {@link CKEDITOR} methods preferible. + * @constructor + * @param {Object} instanceConfig Configuration values for this specific + * instance. + * @param {CKEDITOR.dom.element} [element] The element linked to this + * instance. + * @param {Number} [mode] The mode in which the element is linked to this + * instance. + * @param {String} [data] Since 3.3. Initial value for the instance. + * @augments CKEDITOR.event + * @example + */ + CKEDITOR.editor = function( instanceConfig, element, mode, data ) + { + this._ = + { + // Save the config to be processed later by the full core code. + instanceConfig : instanceConfig, + element : element, + data : data + }; + + /** + * The mode in which the {@link #element} is linked to this editor + * instance. It can be any of the following values: + *
          + *
        • CKEDITOR.ELEMENT_MODE_NONE: No element is linked to the + * editor instance.
        • + *
        • CKEDITOR.ELEMENT_MODE_REPLACE: The element is to be + * replaced by the editor instance.
        • + *
        • CKEDITOR.ELEMENT_MODE_APPENDTO: The editor is to be + * created inside the element.
        • + *
        + * @name CKEDITOR.editor.prototype.elementMode + * @type Number + * @example + * var editor = CKEDITOR.replace( 'editor1' ); + * alert( editor.elementMode ); "1" + */ + this.elementMode = mode || CKEDITOR.ELEMENT_MODE_NONE; + + // Call the CKEDITOR.event constructor to initialize this instance. + CKEDITOR.event.call( this ); + + this._init(); + }; + + /** + * Replaces a <textarea> or a DOM element (DIV) with a CKEditor + * instance. For textareas, the initial value in the editor will be the + * textarea value. For DOM elements, their innerHTML will be used + * instead. We recommend using TEXTAREA and DIV elements only. Do not use + * this function directly. Use {@link CKEDITOR.replace} instead. + * @param {Object|String} elementOrIdOrName The DOM element (textarea), its + * ID or name. + * @param {Object} [config] The specific configurations to apply to this + * editor instance. Configurations set here will override global CKEditor + * settings. + * @returns {CKEDITOR.editor} The editor instance created. + * @example + */ + CKEDITOR.editor.replace = function( elementOrIdOrName, config ) + { + var element = elementOrIdOrName; + + if ( typeof element != 'object' ) + { + // Look for the element by id. We accept any kind of element here. + element = document.getElementById( elementOrIdOrName ); + + // If not found, look for elements by name. In this case we accept only + // textareas. + if ( !element ) + { + var i = 0, + textareasByName = document.getElementsByName( elementOrIdOrName ); + + while ( ( element = textareasByName[ i++ ] ) && element.tagName.toLowerCase() != 'textarea' ) + { /*jsl:pass*/ } + } + + if ( !element ) + throw '[CKEDITOR.editor.replace] The element with id or name "' + elementOrIdOrName + '" was not found.'; + } + + // Do not replace the textarea right now, just hide it. The effective + // replacement will be done by the _init function. + element.style.visibility = 'hidden'; + + // Create the editor instance. + return new CKEDITOR.editor( config, element, CKEDITOR.ELEMENT_MODE_REPLACE ); + }; + + /** + * Creates a new editor instance inside a specific DOM element. Do not use + * this function directly. Use {@link CKEDITOR.appendTo} instead. + * @param {Object|String} elementOrId The DOM element or its ID. + * @param {Object} [config] The specific configurations to apply to this + * editor instance. Configurations set here will override global CKEditor + * settings. + * @param {String} [data] Since 3.3. Initial value for the instance. + * @returns {CKEDITOR.editor} The editor instance created. + * @example + */ + CKEDITOR.editor.appendTo = function( elementOrId, config, data ) + { + var element = elementOrId; + if ( typeof element != 'object' ) + { + element = document.getElementById( elementOrId ); + + if ( !element ) + throw '[CKEDITOR.editor.appendTo] The element with id "' + elementOrId + '" was not found.'; + } + + // Create the editor instance. + return new CKEDITOR.editor( config, element, CKEDITOR.ELEMENT_MODE_APPENDTO, data ); + }; + + CKEDITOR.editor.prototype = + { + /** + * Initializes the editor instance. This function will be overriden by the + * full CKEDITOR.editor implementation (editor.js). + * @private + */ + _init : function() + { + var pending = CKEDITOR.editor._pending || ( CKEDITOR.editor._pending = [] ); + pending.push( this ); + }, + + // Both fire and fireOnce will always pass this editor instance as the + // "editor" param in CKEDITOR.event.fire. So, we override it to do that + // automaticaly. + + /** @ignore */ + fire : function( eventName, data ) + { + return CKEDITOR.event.prototype.fire.call( this, eventName, data, this ); + }, + + /** @ignore */ + fireOnce : function( eventName, data ) + { + return CKEDITOR.event.prototype.fireOnce.call( this, eventName, data, this ); + } + }; + + // "Inherit" (copy actually) from CKEDITOR.event. + CKEDITOR.event.implementOn( CKEDITOR.editor.prototype, true ); +} diff --git a/rte/_source/core/env.js b/rte/_source/core/env.js new file mode 100644 index 0000000..93c0372 --- /dev/null +++ b/rte/_source/core/env.js @@ -0,0 +1,227 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.env} object, which constains + * environment and browser information. + */ + +if ( !CKEDITOR.env ) +{ + /** + * Environment and browser information. + * @namespace + * @example + */ + CKEDITOR.env = (function() + { + var agent = navigator.userAgent.toLowerCase(); + var opera = window.opera; + + var env = + /** @lends CKEDITOR.env */ + { + /** + * Indicates that CKEditor is running on Internet Explorer. + * @type Boolean + * @example + * if ( CKEDITOR.env.ie ) + * alert( "I'm on IE!" ); + */ + ie : /*@cc_on!@*/false, + + /** + * Indicates that CKEditor is running on Opera. + * @type Boolean + * @example + * if ( CKEDITOR.env.opera ) + * alert( "I'm on Opera!" ); + */ + opera : ( !!opera && opera.version ), + + /** + * Indicates that CKEditor is running on a WebKit based browser, like + * Safari. + * @type Boolean + * @example + * if ( CKEDITOR.env.webkit ) + * alert( "I'm on WebKit!" ); + */ + webkit : ( agent.indexOf( ' applewebkit/' ) > -1 ), + + /** + * Indicates that CKEditor is running on Adobe AIR. + * @type Boolean + * @example + * if ( CKEDITOR.env.air ) + * alert( "I'm on AIR!" ); + */ + air : ( agent.indexOf( ' adobeair/' ) > -1 ), + + /** + * Indicates that CKEditor is running on Macintosh. + * @type Boolean + * @example + * if ( CKEDITOR.env.mac ) + * alert( "I love apples!" ); + */ + mac : ( agent.indexOf( 'macintosh' ) > -1 ), + + quirks : ( document.compatMode == 'BackCompat' ), + + mobile : ( agent.indexOf( 'mobile' ) > -1 ), + + isCustomDomain : function() + { + var domain = document.domain, + hostname = window.location.hostname; + + return this.ie && + domain != hostname && + domain != ( '[' + hostname + ']' ); // IPv6 IP support (#5434) + } + }; + + /** + * Indicates that CKEditor is running on a Gecko based browser, like + * Firefox. + * @name CKEDITOR.env.gecko + * @type Boolean + * @example + * if ( CKEDITOR.env.gecko ) + * alert( "I'm riding a gecko!" ); + */ + env.gecko = ( navigator.product == 'Gecko' && !env.webkit && !env.opera ); + + var version = 0; + + // Internet Explorer 6.0+ + if ( env.ie ) + { + version = parseFloat( agent.match( /msie (\d+)/ )[1] ); + + /** + * Indicate IE8 browser. + */ + env.ie8 = !!document.documentMode; + + /** + * Indicte IE8 document mode. + */ + env.ie8Compat = document.documentMode == 8; + + /** + * Indicates that CKEditor is running on an IE7-like environment, which + * includes IE7 itself and IE8's IE7 document mode. + * @type Boolean + */ + env.ie7Compat = ( ( version == 7 && !document.documentMode ) + || document.documentMode == 7 ); + + /** + * Indicates that CKEditor is running on an IE6-like environment, which + * includes IE6 itself and IE7 and IE8 quirks mode. + * @type Boolean + * @example + * if ( CKEDITOR.env.ie6Compat ) + * alert( "I'm on IE6 or quirks mode!" ); + */ + env.ie6Compat = ( version < 7 || env.quirks ); + + } + + // Gecko. + if ( env.gecko ) + { + var geckoRelease = agent.match( /rv:([\d\.]+)/ ); + if ( geckoRelease ) + { + geckoRelease = geckoRelease[1].split( '.' ); + version = geckoRelease[0] * 10000 + ( geckoRelease[1] || 0 ) * 100 + ( geckoRelease[2] || 0 ) * 1; + } + } + + // Opera 9.50+ + if ( env.opera ) + version = parseFloat( opera.version() ); + + // Adobe AIR 1.0+ + // Checked before Safari because AIR have the WebKit rich text editor + // features from Safari 3.0.4, but the version reported is 420. + if ( env.air ) + version = parseFloat( agent.match( / adobeair\/(\d+)/ )[1] ); + + // WebKit 522+ (Safari 3+) + if ( env.webkit ) + version = parseFloat( agent.match( / applewebkit\/(\d+)/ )[1] ); + + /** + * Contains the browser version. + * + * For gecko based browsers (like Firefox) it contains the revision + * number with first three parts concatenated with a padding zero + * (e.g. for revision 1.9.0.2 we have 10900). + * + * For webkit based browser (like Safari and Chrome) it contains the + * WebKit build version (e.g. 522). + * @name CKEDITOR.env.version + * @type Boolean + * @example + * if ( CKEDITOR.env.ie && CKEDITOR.env.version <= 6 ) + * alert( "Ouch!" ); + */ + env.version = version; + + /** + * Indicates that CKEditor is running on a compatible browser. + * @name CKEDITOR.env.isCompatible + * @type Boolean + * @example + * if ( CKEDITOR.env.isCompatible ) + * alert( "Your browser is pretty cool!" ); + */ + env.isCompatible = + !env.mobile && ( + ( env.ie && version >= 6 ) || + ( env.gecko && version >= 10801 ) || + ( env.opera && version >= 9.5 ) || + ( env.air && version >= 1 ) || + ( env.webkit && version >= 522 ) || + false ); + + // The CSS class to be appended on the main UI containers, making it + // easy to apply browser specific styles to it. + env.cssClass = + 'cke_browser_' + ( + env.ie ? 'ie' : + env.gecko ? 'gecko' : + env.opera ? 'opera' : + env.air ? 'air' : + env.webkit ? 'webkit' : + 'unknown' ); + + if ( env.quirks ) + env.cssClass += ' cke_browser_quirks'; + + if ( env.ie ) + { + env.cssClass += ' cke_browser_ie' + ( + env.version < 7 ? '6' : + env.version >= 8 ? '8' : + '7' ); + + if ( env.quirks ) + env.cssClass += ' cke_browser_iequirks'; + } + + if ( env.gecko && version < 10900 ) + env.cssClass += ' cke_browser_gecko18'; + + return env; + })(); +} + +// PACKAGER_RENAME( CKEDITOR.env ) +// PACKAGER_RENAME( CKEDITOR.env.ie ) diff --git a/rte/_source/core/event.js b/rte/_source/core/event.js new file mode 100644 index 0000000..5837231 --- /dev/null +++ b/rte/_source/core/event.js @@ -0,0 +1,336 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.event} class, which serves as the + * base for classes and objects that require event handling features. + */ + +if ( !CKEDITOR.event ) +{ + /** + * This is a base class for classes and objects that require event handling + * features. + * @constructor + * @example + */ + CKEDITOR.event = function() + {}; + + /** + * Implements the {@link CKEDITOR.event} features in an object. + * @param {Object} targetObject The object in which implement the features. + * @example + * var myObject = { message : 'Example' }; + * CKEDITOR.event.implementOn( myObject }; + * myObject.on( 'testEvent', function() + * { + * alert( this.message ); // "Example" + * }); + * myObject.fire( 'testEvent' ); + */ + CKEDITOR.event.implementOn = function( targetObject, isTargetPrototype ) + { + var eventProto = CKEDITOR.event.prototype; + + for ( var prop in eventProto ) + { + if ( targetObject[ prop ] == undefined ) + targetObject[ prop ] = eventProto[ prop ]; + } + }; + + CKEDITOR.event.prototype = (function() + { + // Returns the private events object for a given object. + var getPrivate = function( obj ) + { + var _ = ( obj.getPrivate && obj.getPrivate() ) || obj._ || ( obj._ = {} ); + return _.events || ( _.events = {} ); + }; + + var eventEntry = function( eventName ) + { + this.name = eventName; + this.listeners = []; + }; + + eventEntry.prototype = + { + // Get the listener index for a specified function. + // Returns -1 if not found. + getListenerIndex : function( listenerFunction ) + { + for ( var i = 0, listeners = this.listeners ; i < listeners.length ; i++ ) + { + if ( listeners[i].fn == listenerFunction ) + return i; + } + return -1; + } + }; + + return /** @lends CKEDITOR.event.prototype */ { + /** + * Registers a listener to a specific event in the current object. + * @param {String} eventName The event name to which listen. + * @param {Function} listenerFunction The function listening to the + * event. A single {@link CKEDITOR.eventInfo} object instanced + * is passed to this function containing all the event data. + * @param {Object} [scopeObj] The object used to scope the listener + * call (the this object. If omitted, the current object is used. + * @param {Object} [listenerData] Data to be sent as the + * {@link CKEDITOR.eventInfo#listenerData} when calling the + * listener. + * @param {Number} [priority] The listener priority. Lower priority + * listeners are called first. Listeners with the same priority + * value are called in registration order. Defaults to 10. + * @example + * someObject.on( 'someEvent', function() + * { + * alert( this == someObject ); // "true" + * }); + * @example + * someObject.on( 'someEvent', function() + * { + * alert( this == anotherObject ); // "true" + * } + * , anotherObject ); + * @example + * someObject.on( 'someEvent', function( event ) + * { + * alert( event.listenerData ); // "Example" + * } + * , null, 'Example' ); + * @example + * someObject.on( 'someEvent', function() { ... } ); // 2nd called + * someObject.on( 'someEvent', function() { ... }, null, null, 100 ); // 3rd called + * someObject.on( 'someEvent', function() { ... }, null, null, 1 ); // 1st called + */ + on : function( eventName, listenerFunction, scopeObj, listenerData, priority ) + { + // Get the event entry (create it if needed). + var events = getPrivate( this ), + event = events[ eventName ] || ( events[ eventName ] = new eventEntry( eventName ) ); + + if ( event.getListenerIndex( listenerFunction ) < 0 ) + { + // Get the listeners. + var listeners = event.listeners; + + // Fill the scope. + if ( !scopeObj ) + scopeObj = this; + + // Default the priority, if needed. + if ( isNaN( priority ) ) + priority = 10; + + var me = this; + + // Create the function to be fired for this listener. + var listenerFirer = function( editor, publisherData, stopFn, cancelFn ) + { + var ev = + { + name : eventName, + sender : this, + editor : editor, + data : publisherData, + listenerData : listenerData, + stop : stopFn, + cancel : cancelFn, + removeListener : function() + { + me.removeListener( eventName, listenerFunction ); + } + }; + + listenerFunction.call( scopeObj, ev ); + + return ev.data; + }; + listenerFirer.fn = listenerFunction; + listenerFirer.priority = priority; + + // Search for the right position for this new listener, based on its + // priority. + for ( var i = listeners.length - 1 ; i >= 0 ; i-- ) + { + // Find the item which should be before the new one. + if ( listeners[ i ].priority <= priority ) + { + // Insert the listener in the array. + listeners.splice( i + 1, 0, listenerFirer ); + return; + } + } + + // If no position has been found (or zero length), put it in + // the front of list. + listeners.unshift( listenerFirer ); + } + }, + + /** + * Fires an specific event in the object. All registered listeners are + * called at this point. + * @function + * @param {String} eventName The event name to fire. + * @param {Object} [data] Data to be sent as the + * {@link CKEDITOR.eventInfo#data} when calling the + * listeners. + * @param {CKEDITOR.editor} [editor] The editor instance to send as the + * {@link CKEDITOR.eventInfo#editor} when calling the + * listener. + * @returns {Boolean|Object} A booloan indicating that the event is to be + * canceled, or data returned by one of the listeners. + * @example + * someObject.on( 'someEvent', function() { ... } ); + * someObject.on( 'someEvent', function() { ... } ); + * someObject.fire( 'someEvent' ); // both listeners are called + * @example + * someObject.on( 'someEvent', function( event ) + * { + * alert( event.data ); // "Example" + * }); + * someObject.fire( 'someEvent', 'Example' ); + */ + fire : (function() + { + // Create the function that marks the event as stopped. + var stopped = false; + var stopEvent = function() + { + stopped = true; + }; + + // Create the function that marks the event as canceled. + var canceled = false; + var cancelEvent = function() + { + canceled = true; + }; + + return function( eventName, data, editor ) + { + // Get the event entry. + var event = getPrivate( this )[ eventName ]; + + // Save the previous stopped and cancelled states. We may + // be nesting fire() calls. + var previousStopped = stopped, + previousCancelled = canceled; + + // Reset the stopped and canceled flags. + stopped = canceled = false; + + if ( event ) + { + var listeners = event.listeners; + + if ( listeners.length ) + { + // As some listeners may remove themselves from the + // event, the original array length is dinamic. So, + // let's make a copy of all listeners, so we are + // sure we'll call all of them. + listeners = listeners.slice( 0 ); + + // Loop through all listeners. + for ( var i = 0 ; i < listeners.length ; i++ ) + { + // Call the listener, passing the event data. + var retData = listeners[i].call( this, editor, data, stopEvent, cancelEvent ); + + if ( typeof retData != 'undefined' ) + data = retData; + + // No further calls is stopped or canceled. + if ( stopped || canceled ) + break; + } + } + } + + var ret = canceled || ( typeof data == 'undefined' ? false : data ); + + // Restore the previous stopped and canceled states. + stopped = previousStopped; + canceled = previousCancelled; + + return ret; + }; + })(), + + /** + * Fires an specific event in the object, releasing all listeners + * registered to that event. The same listeners are not called again on + * successive calls of it or of {@link #fire}. + * @param {String} eventName The event name to fire. + * @param {Object} [data] Data to be sent as the + * {@link CKEDITOR.eventInfo#data} when calling the + * listeners. + * @param {CKEDITOR.editor} [editor] The editor instance to send as the + * {@link CKEDITOR.eventInfo#editor} when calling the + * listener. + * @returns {Boolean|Object} A booloan indicating that the event is to be + * canceled, or data returned by one of the listeners. + * @example + * someObject.on( 'someEvent', function() { ... } ); + * someObject.fire( 'someEvent' ); // above listener called + * someObject.fireOnce( 'someEvent' ); // above listener called + * someObject.fire( 'someEvent' ); // no listeners called + */ + fireOnce : function( eventName, data, editor ) + { + var ret = this.fire( eventName, data, editor ); + delete getPrivate( this )[ eventName ]; + return ret; + }, + + /** + * Unregisters a listener function from being called at the specified + * event. No errors are thrown if the listener has not been + * registered previously. + * @param {String} eventName The event name. + * @param {Function} listenerFunction The listener function to unregister. + * @example + * var myListener = function() { ... }; + * someObject.on( 'someEvent', myListener ); + * someObject.fire( 'someEvent' ); // myListener called + * someObject.removeListener( 'someEvent', myListener ); + * someObject.fire( 'someEvent' ); // myListener not called + */ + removeListener : function( eventName, listenerFunction ) + { + // Get the event entry. + var event = getPrivate( this )[ eventName ]; + + if ( event ) + { + var index = event.getListenerIndex( listenerFunction ); + if ( index >= 0 ) + event.listeners.splice( index, 1 ); + } + }, + + /** + * Checks if there is any listener registered to a given event. + * @param {String} eventName The event name. + * @example + * var myListener = function() { ... }; + * someObject.on( 'someEvent', myListener ); + * alert( someObject.hasListeners( 'someEvent' ) ); // "true" + * alert( someObject.hasListeners( 'noEvent' ) ); // "false" + */ + hasListeners : function( eventName ) + { + var event = getPrivate( this )[ eventName ]; + return ( event && event.listeners.length > 0 ) ; + } + }; + })(); +} diff --git a/rte/_source/core/eventInfo.js b/rte/_source/core/eventInfo.js new file mode 100644 index 0000000..dd79a52 --- /dev/null +++ b/rte/_source/core/eventInfo.js @@ -0,0 +1,120 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the "virtual" {@link CKEDITOR.eventInfo} class, which + * contains the defintions of the event object passed to event listeners. + * This file is for documentation purposes only. + */ + +/** + * This class is not really part of the API. It just illustrates the features + * of the event object passed to event listeners by a {@link CKEDITOR.event} + * based object. + * @name CKEDITOR.eventInfo + * @constructor + * @example + * // Do not do this. + * var myEvent = new CKEDITOR.eventInfo(); // Error: CKEDITOR.eventInfo is undefined + */ + +/** + * The event name. + * @name CKEDITOR.eventInfo.prototype.name + * @field + * @type String + * @example + * someObject.on( 'someEvent', function( event ) + * { + * alert( event.name ); // "someEvent" + * }); + * someObject.fire( 'someEvent' ); + */ + +/** + * The object that publishes (sends) the event. + * @name CKEDITOR.eventInfo.prototype.sender + * @field + * @type Object + * @example + * someObject.on( 'someEvent', function( event ) + * { + * alert( event.sender == someObject ); // "true" + * }); + * someObject.fire( 'someEvent' ); + */ + +/** + * The editor instance that holds the sender. May be the same as sender. May be + * null if the sender is not part of an editor instance, like a component + * running in standalone mode. + * @name CKEDITOR.eventInfo.prototype.editor + * @field + * @type CKEDITOR.editor + * @example + * myButton.on( 'someEvent', function( event ) + * { + * alert( event.editor == myEditor ); // "true" + * }); + * myButton.fire( 'someEvent', null, myEditor ); + */ + +/** + * Any kind of additional data. Its format and usage is event dependent. + * @name CKEDITOR.eventInfo.prototype.data + * @field + * @type Object + * @example + * someObject.on( 'someEvent', function( event ) + * { + * alert( event.data ); // "Example" + * }); + * someObject.fire( 'someEvent', 'Example' ); + */ + +/** + * Any extra data appended during the listener registration. + * @name CKEDITOR.eventInfo.prototype.listenerData + * @field + * @type Object + * @example + * someObject.on( 'someEvent', function( event ) + * { + * alert( event.listenerData ); // "Example" + * } + * , null, 'Example' ); + */ + +/** + * Indicates that no further listeners are to be called. + * @name CKEDITOR.eventInfo.prototype.stop + * @function + * @example + * someObject.on( 'someEvent', function( event ) + * { + * event.stop(); + * }); + * someObject.on( 'someEvent', function( event ) + * { + * // This one will not be called. + * }); + * alert( someObject.fire( 'someEvent' ) ); // "false" + */ + +/** + * Indicates that the event is to be cancelled (if cancelable). + * @name CKEDITOR.eventInfo.prototype.cancel + * @function + * @example + * someObject.on( 'someEvent', function( event ) + * { + * event.cancel(); + * }); + * someObject.on( 'someEvent', function( event ) + * { + * // This one will not be called. + * }); + * alert( someObject.fire( 'someEvent' ) ); // "true" + */ diff --git a/rte/_source/core/focusmanager.js b/rte/_source/core/focusmanager.js new file mode 100644 index 0000000..255c0af --- /dev/null +++ b/rte/_source/core/focusmanager.js @@ -0,0 +1,137 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the {@link CKEDITOR.focusManager} class, which is used + * to handle the focus on editor instances.. + */ + +/** + * Manages the focus activity in an editor instance. This class is to be used + * mainly by UI elements coders when adding interface elements to CKEditor. + * @constructor + * @param {CKEDITOR.editor} editor The editor instance. + * @example + */ +CKEDITOR.focusManager = function( editor ) +{ + if ( editor.focusManager ) + return editor.focusManager; + + /** + * Indicates that the editor instance has focus. + * @type Boolean + * @example + * alert( CKEDITOR.instances.editor1.focusManager.hasFocus ); // e.g "true" + */ + this.hasFocus = false; + + /** + * Object used to hold private stuff. + * @private + */ + this._ = + { + editor : editor + }; + + return this; +}; + +CKEDITOR.focusManager.prototype = +{ + /** + * Indicates that the editor instance has the focus. + * + * This function is not used to set the focus in the editor. Use + * {@link CKEDITOR.editor#focus} for it instead. + * @example + * var editor = CKEDITOR.instances.editor1; + * editor.focusManager.focus(); + */ + focus : function() + { + if ( this._.timer ) + clearTimeout( this._.timer ); + + if ( !this.hasFocus ) + { + // If another editor has the current focus, we first "blur" it. In + // this way the events happen in a more logical sequence, like: + // "focus 1" > "blur 1" > "focus 2" + // ... instead of: + // "focus 1" > "focus 2" > "blur 1" + if ( CKEDITOR.currentInstance ) + CKEDITOR.currentInstance.focusManager.forceBlur(); + + var editor = this._.editor; + + editor.container.getChild( 1 ).addClass( 'cke_focus' ); + + this.hasFocus = true; + editor.fire( 'focus' ); + } + }, + + /** + * Indicates that the editor instance has lost the focus. Note that this + * functions acts asynchronously with a delay of 100ms to avoid subsequent + * blur/focus effects. If you want the "blur" to happen immediately, use + * the {@link #forceBlur} function instead. + * @example + * var editor = CKEDITOR.instances.editor1; + * editor.focusManager.blur(); + */ + blur : function() + { + var focusManager = this; + + if ( focusManager._.timer ) + clearTimeout( focusManager._.timer ); + + focusManager._.timer = setTimeout( + function() + { + delete focusManager._.timer; + focusManager.forceBlur(); + } + , 100 ); + }, + + /** + * Indicates that the editor instance has lost the focus. Unlike + * {@link #blur}, this function is synchronous, marking the instance as + * "blured" immediately. + * @example + * var editor = CKEDITOR.instances.editor1; + * editor.focusManager.forceBlur(); + */ + forceBlur : function() + { + if ( this.hasFocus ) + { + var editor = this._.editor; + + editor.container.getChild( 1 ).removeClass( 'cke_focus' ); + + this.hasFocus = false; + editor.fire( 'blur' ); + } + } +}; + +/** + * Fired when the editor instance receives the input focus. + * @name CKEDITOR.editor#focus + * @event + * @param {CKEDITOR.editor} editor The editor instance. + */ + +/** + * Fired when the editor instance loses the input focus. + * @name CKEDITOR.editor#blur + * @event + * @param {CKEDITOR.editor} editor The editor instance. + */ diff --git a/rte/_source/core/htmlparser.js b/rte/_source/core/htmlparser.js new file mode 100644 index 0000000..9acd8d5 --- /dev/null +++ b/rte/_source/core/htmlparser.js @@ -0,0 +1,212 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * HTML text parser. + * @constructor + * @example + */ +CKEDITOR.htmlParser = function() +{ + this._ = + { + htmlPartsRegex : new RegExp( '<(?:(?:\\/([^>]+)>)|(?:!--([\\S|\\s]*?)-->)|(?:([^\\s>]+)\\s*((?:(?:[^"\'>]+)|(?:"[^"]*")|(?:\'[^\']*\'))*)\\/?>))', 'g' ) + }; +}; + +(function() +{ + var attribsRegex = /([\w\-:.]+)(?:(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))|(?=\s|$))/g, + emptyAttribs = {checked:1,compact:1,declare:1,defer:1,disabled:1,ismap:1,multiple:1,nohref:1,noresize:1,noshade:1,nowrap:1,readonly:1,selected:1}; + + CKEDITOR.htmlParser.prototype = + { + /** + * Function to be fired when a tag opener is found. This function + * should be overriden when using this class. + * @param {String} tagName The tag name. The name is guarantted to be + * lowercased. + * @param {Object} attributes An object containing all tag attributes. Each + * property in this object represent and attribute name and its + * value is the attribute value. + * @param {Boolean} selfClosing true if the tag closes itself, false if the + * tag doesn't. + * @example + * var parser = new CKEDITOR.htmlParser(); + * parser.onTagOpen = function( tagName, attributes, selfClosing ) + * { + * alert( tagName ); // e.g. "b" + * }); + * parser.parse( "<!-- Example --><b>Hello</b>" ); + */ + onTagOpen : function() {}, + + /** + * Function to be fired when a tag closer is found. This function + * should be overriden when using this class. + * @param {String} tagName The tag name. The name is guarantted to be + * lowercased. + * @example + * var parser = new CKEDITOR.htmlParser(); + * parser.onTagClose = function( tagName ) + * { + * alert( tagName ); // e.g. "b" + * }); + * parser.parse( "<!-- Example --><b>Hello</b>" ); + */ + onTagClose : function() {}, + + /** + * Function to be fired when text is found. This function + * should be overriden when using this class. + * @param {String} text The text found. + * @example + * var parser = new CKEDITOR.htmlParser(); + * parser.onText = function( text ) + * { + * alert( text ); // e.g. "Hello" + * }); + * parser.parse( "<!-- Example --><b>Hello</b>" ); + */ + onText : function() {}, + + /** + * Function to be fired when CDATA section is found. This function + * should be overriden when using this class. + * @param {String} cdata The CDATA been found. + * @example + * var parser = new CKEDITOR.htmlParser(); + * parser.onCDATA = function( cdata ) + * { + * alert( cdata ); // e.g. "var hello;" + * }); + * parser.parse( "<script>var hello;</script>" ); + */ + onCDATA : function() {}, + + /** + * Function to be fired when a commend is found. This function + * should be overriden when using this class. + * @param {String} comment The comment text. + * @example + * var parser = new CKEDITOR.htmlParser(); + * parser.onText = function( comment ) + * { + * alert( comment ); // e.g. " Example " + * }); + * parser.parse( "<!-- Example --><b>Hello</b>" ); + */ + onComment : function() {}, + + /** + * Parses text, looking for HTML tokens, like tag openers or closers, + * or comments. This function fires the onTagOpen, onTagClose, onText + * and onComment function during its execution. + * @param {String} html The HTML to be parsed. + * @example + * var parser = new CKEDITOR.htmlParser(); + * // The onTagOpen, onTagClose, onText and onComment should be overriden + * // at this point. + * parser.parse( "<!-- Example --><b>Hello</b>" ); + */ + parse : function( html ) + { + var parts, + tagName, + nextIndex = 0, + cdata; // The collected data inside a CDATA section. + + while ( ( parts = this._.htmlPartsRegex.exec( html ) ) ) + { + var tagIndex = parts.index; + if ( tagIndex > nextIndex ) + { + var text = html.substring( nextIndex, tagIndex ); + + if ( cdata ) + cdata.push( text ); + else + this.onText( text ); + } + + nextIndex = this._.htmlPartsRegex.lastIndex; + + /* + "parts" is an array with the following items: + 0 : The entire match for opening/closing tags and comments. + 1 : Group filled with the tag name for closing tags. + 2 : Group filled with the comment text. + 3 : Group filled with the tag name for opening tags. + 4 : Group filled with the attributes part of opening tags. + */ + + // Closing tag + if ( ( tagName = parts[ 1 ] ) ) + { + tagName = tagName.toLowerCase(); + + if ( cdata && CKEDITOR.dtd.$cdata[ tagName ] ) + { + // Send the CDATA data. + this.onCDATA( cdata.join('') ); + cdata = null; + } + + if ( !cdata ) + { + this.onTagClose( tagName ); + continue; + } + } + + // If CDATA is enabled, just save the raw match. + if ( cdata ) + { + cdata.push( parts[ 0 ] ); + continue; + } + + // Opening tag + if ( ( tagName = parts[ 3 ] ) ) + { + tagName = tagName.toLowerCase(); + var attribs = {}, + attribMatch, + attribsPart = parts[ 4 ], + selfClosing = !!( attribsPart && attribsPart.charAt( attribsPart.length - 1 ) == '/' ); + + if ( attribsPart ) + { + while ( ( attribMatch = attribsRegex.exec( attribsPart ) ) ) + { + var attName = attribMatch[1].toLowerCase(), + attValue = attribMatch[2] || attribMatch[3] || attribMatch[4] || ''; + + if ( !attValue && emptyAttribs[ attName ] ) + attribs[ attName ] = attName; + else + attribs[ attName ] = attValue; + } + } + + this.onTagOpen( tagName, attribs, selfClosing ); + + // Open CDATA mode when finding the appropriate tags. + if ( !cdata && CKEDITOR.dtd.$cdata[ tagName ] ) + cdata = []; + + continue; + } + + // Comment + if ( ( tagName = parts[ 2 ] ) ) + this.onComment( tagName ); + } + + if ( html.length > nextIndex ) + this.onText( html.substring( nextIndex, html.length ) ); + } + }; +})(); diff --git a/rte/_source/core/htmlparser/basicwriter.js b/rte/_source/core/htmlparser/basicwriter.js new file mode 100644 index 0000000..2e8168a --- /dev/null +++ b/rte/_source/core/htmlparser/basicwriter.js @@ -0,0 +1,145 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.htmlParser.basicWriter = CKEDITOR.tools.createClass( +{ + $ : function() + { + this._ = + { + output : [] + }; + }, + + proto : + { + /** + * Writes the tag opening part for a opener tag. + * @param {String} tagName The element name for this tag. + * @param {Object} attributes The attributes defined for this tag. The + * attributes could be used to inspect the tag. + * @example + * // Writes "<p". + * writer.openTag( 'p', { class : 'MyClass', id : 'MyId' } ); + */ + openTag : function( tagName, attributes ) + { + this._.output.push( '<', tagName ); + }, + + /** + * Writes the tag closing part for a opener tag. + * @param {String} tagName The element name for this tag. + * @param {Boolean} isSelfClose Indicates that this is a self-closing tag, + * like "br" or "img". + * @example + * // Writes ">". + * writer.openTagClose( 'p', false ); + * @example + * // Writes " />". + * writer.openTagClose( 'br', true ); + */ + openTagClose : function( tagName, isSelfClose ) + { + if ( isSelfClose ) + this._.output.push( ' />' ); + else + this._.output.push( '>' ); + }, + + /** + * Writes an attribute. This function should be called after opening the + * tag with {@link #openTagClose}. + * @param {String} attName The attribute name. + * @param {String} attValue The attribute value. + * @example + * // Writes ' class="MyClass"'. + * writer.attribute( 'class', 'MyClass' ); + */ + attribute : function( attName, attValue ) + { + // Browsers don't always escape special character in attribute values. (#4683, #4719). + if ( typeof attValue == 'string' ) + attValue = CKEDITOR.tools.htmlEncodeAttr( attValue ); + + this._.output.push( ' ', attName, '="', attValue, '"' ); + }, + + /** + * Writes a closer tag. + * @param {String} tagName The element name for this tag. + * @example + * // Writes "</p>". + * writer.closeTag( 'p' ); + */ + closeTag : function( tagName ) + { + this._.output.push( '' ); + }, + + /** + * Writes text. + * @param {String} text The text value + * @example + * // Writes "Hello Word". + * writer.text( 'Hello Word' ); + */ + text : function( text ) + { + this._.output.push( text ); + }, + + /** + * Writes a comment. + * @param {String} comment The comment text. + * @example + * // Writes "<!-- My comment -->". + * writer.comment( ' My comment ' ); + */ + comment : function( comment ) + { + this._.output.push( '' ); + }, + + /** + * Writes any kind of data to the ouput. + * @example + * writer.write( 'This is an <b>example</b>.' ); + */ + write : function( data ) + { + this._.output.push( data ); + }, + + /** + * Empties the current output buffer. + * @example + * writer.reset(); + */ + reset : function() + { + this._.output = []; + this._.indent = false; + }, + + /** + * Empties the current output buffer. + * @param {Boolean} reset Indicates that the {@link reset} function is to + * be automatically called after retrieving the HTML. + * @returns {String} The HTML written to the writer so far. + * @example + * var html = writer.getHtml(); + */ + getHtml : function( reset ) + { + var html = this._.output.join( '' ); + + if ( reset ) + this.reset(); + + return html; + } + } +}); diff --git a/rte/_source/core/htmlparser/cdata.js b/rte/_source/core/htmlparser/cdata.js new file mode 100644 index 0000000..d407278 --- /dev/null +++ b/rte/_source/core/htmlparser/cdata.js @@ -0,0 +1,43 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + + /** + * A lightweight representation of HTML text. + * @constructor + * @example + */ + CKEDITOR.htmlParser.cdata = function( value ) + { + /** + * The CDATA value. + * @type String + * @example + */ + this.value = value; + }; + + CKEDITOR.htmlParser.cdata.prototype = + { + /** + * CDATA has the same type as {@link CKEDITOR.htmlParser.text} This is + * a constant value set to {@link CKEDITOR.NODE_TEXT}. + * @type Number + * @example + */ + type : CKEDITOR.NODE_TEXT, + + /** + * Writes write the CDATA with no special manipulations. + * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML. + */ + writeHtml : function( writer ) + { + writer.write( this.value ); + } + }; +})(); diff --git a/rte/_source/core/htmlparser/comment.js b/rte/_source/core/htmlparser/comment.js new file mode 100644 index 0000000..9cf7ebc --- /dev/null +++ b/rte/_source/core/htmlparser/comment.js @@ -0,0 +1,60 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * A lightweight representation of an HTML comment. + * @constructor + * @example + */ +CKEDITOR.htmlParser.comment = function( value ) +{ + /** + * The comment text. + * @type String + * @example + */ + this.value = value; + + /** @private */ + this._ = + { + isBlockLike : false + }; +}; + +CKEDITOR.htmlParser.comment.prototype = +{ + /** + * The node type. This is a constant value set to {@link CKEDITOR.NODE_COMMENT}. + * @type Number + * @example + */ + type : CKEDITOR.NODE_COMMENT, + + /** + * Writes the HTML representation of this comment to a CKEDITOR.htmlWriter. + * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML. + * @example + */ + writeHtml : function( writer, filter ) + { + var comment = this.value; + + if ( filter ) + { + if ( !( comment = filter.onComment( comment, this ) ) ) + return; + + if ( typeof comment != 'string' ) + { + comment.parent = this.parent; + comment.writeHtml( writer, filter ); + return; + } + } + + writer.comment( comment ); + } +}; diff --git a/rte/_source/core/htmlparser/element.js b/rte/_source/core/htmlparser/element.js new file mode 100644 index 0000000..a187883 --- /dev/null +++ b/rte/_source/core/htmlparser/element.js @@ -0,0 +1,240 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * A lightweight representation of an HTML element. + * @param {String} name The element name. + * @param {Object} attributes And object holding all attributes defined for + * this element. + * @constructor + * @example + */ +CKEDITOR.htmlParser.element = function( name, attributes ) +{ + /** + * The element name. + * @type String + * @example + */ + this.name = name; + + /** + * Holds the attributes defined for this element. + * @type Object + * @example + */ + this.attributes = attributes || ( attributes = {} ); + + /** + * The nodes that are direct children of this element. + * @type Array + * @example + */ + this.children = []; + + var tagName = attributes._cke_real_element_type || name; + + var dtd = CKEDITOR.dtd, + isBlockLike = !!( dtd.$nonBodyContent[ tagName ] || dtd.$block[ tagName ] || dtd.$listItem[ tagName ] || dtd.$tableContent[ tagName ] || dtd.$nonEditable[ tagName ] || tagName == 'br' ), + isEmpty = !!dtd.$empty[ name ]; + + this.isEmpty = isEmpty; + this.isUnknown = !dtd[ name ]; + + /** @private */ + this._ = + { + isBlockLike : isBlockLike, + hasInlineStarted : isEmpty || !isBlockLike + }; +}; + +(function() +{ + // Used to sort attribute entries in an array, where the first element of + // each object is the attribute name. + var sortAttribs = function( a, b ) + { + a = a[0]; + b = b[0]; + return a < b ? -1 : a > b ? 1 : 0; + }; + + CKEDITOR.htmlParser.element.prototype = + { + /** + * The node type. This is a constant value set to {@link CKEDITOR.NODE_ELEMENT}. + * @type Number + * @example + */ + type : CKEDITOR.NODE_ELEMENT, + + /** + * Adds a node to the element children list. + * @param {Object} node The node to be added. It can be any of of the + * following types: {@link CKEDITOR.htmlParser.element}, + * {@link CKEDITOR.htmlParser.text} and + * {@link CKEDITOR.htmlParser.comment}. + * @function + * @example + */ + add : CKEDITOR.htmlParser.fragment.prototype.add, + + /** + * Clone this element. + * @returns {CKEDITOR.htmlParser.element} The element clone. + * @example + */ + clone : function() + { + return new CKEDITOR.htmlParser.element( this.name, this.attributes ); + }, + + /** + * Writes the element HTML to a CKEDITOR.htmlWriter. + * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML. + * @example + */ + writeHtml : function( writer, filter ) + { + var attributes = this.attributes; + + // Ignore cke: prefixes when writing HTML. + var element = this, + writeName = element.name, + a, newAttrName, value; + + var isChildrenFiltered; + + /** + * Providing an option for bottom-up filtering order ( element + * children to be pre-filtered before the element itself ). + */ + element.filterChildren = function() + { + if ( !isChildrenFiltered ) + { + var writer = new CKEDITOR.htmlParser.basicWriter(); + CKEDITOR.htmlParser.fragment.prototype.writeChildrenHtml.call( element, writer, filter ); + element.children = new CKEDITOR.htmlParser.fragment.fromHtml( writer.getHtml() ).children; + isChildrenFiltered = 1; + } + }; + + if ( filter ) + { + while ( true ) + { + if ( !( writeName = filter.onElementName( writeName ) ) ) + return; + + element.name = writeName; + + if ( !( element = filter.onElement( element ) ) ) + return; + + element.parent = this.parent; + + if ( element.name == writeName ) + break; + + // If the element has been replaced with something of a + // different type, then make the replacement write itself. + if ( element.type != CKEDITOR.NODE_ELEMENT ) + { + element.writeHtml( writer, filter ); + return; + } + + writeName = element.name; + + // This indicate that the element has been dropped by + // filter but not the children. + if ( !writeName ) + { + this.writeChildrenHtml.call( element, writer, isChildrenFiltered ? null : filter ); + return; + } + } + + // The element may have been changed, so update the local + // references. + attributes = element.attributes; + } + + // Open element tag. + writer.openTag( writeName, attributes ); + + // Copy all attributes to an array. + var attribsArray = []; + // Iterate over the attributes twice since filters may alter + // other attributes. + for ( var i = 0 ; i < 2; i++ ) + { + for ( a in attributes ) + { + newAttrName = a; + value = attributes[ a ]; + if ( i == 1 ) + attribsArray.push( [ a, value ] ); + else if ( filter ) + { + while ( true ) + { + if ( !( newAttrName = filter.onAttributeName( a ) ) ) + { + delete attributes[ a ]; + break; + } + else if ( newAttrName != a ) + { + delete attributes[ a ]; + a = newAttrName; + continue; + } + else + break; + } + if ( newAttrName ) + { + if ( ( value = filter.onAttribute( element, newAttrName, value ) ) === false ) + delete attributes[ newAttrName ]; + else + attributes [ newAttrName ] = value; + } + } + } + } + // Sort the attributes by name. + if ( writer.sortAttributes ) + attribsArray.sort( sortAttribs ); + + // Send the attributes. + var len = attribsArray.length; + for ( i = 0 ; i < len ; i++ ) + { + var attrib = attribsArray[ i ]; + writer.attribute( attrib[0], attrib[1] ); + } + + // Close the tag. + writer.openTagClose( writeName, element.isEmpty ); + + if ( !element.isEmpty ) + { + this.writeChildrenHtml.call( element, writer, isChildrenFiltered ? null : filter ); + // Close the element. + writer.closeTag( writeName ); + } + }, + + writeChildrenHtml : function( writer, filter ) + { + // Send children. + CKEDITOR.htmlParser.fragment.prototype.writeChildrenHtml.apply( this, arguments ); + + } + }; +})(); diff --git a/rte/_source/core/htmlparser/filter.js b/rte/_source/core/htmlparser/filter.js new file mode 100644 index 0000000..33dbfa4 --- /dev/null +++ b/rte/_source/core/htmlparser/filter.js @@ -0,0 +1,288 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +(function() +{ + CKEDITOR.htmlParser.filter = CKEDITOR.tools.createClass( + { + $ : function( rules ) + { + this._ = + { + elementNames : [], + attributeNames : [], + elements : { $length : 0 }, + attributes : { $length : 0 } + }; + + if ( rules ) + this.addRules( rules, 10 ); + }, + + proto : + { + addRules : function( rules, priority ) + { + if ( typeof priority != 'number' ) + priority = 10; + + // Add the elementNames. + addItemsToList( this._.elementNames, rules.elementNames, priority ); + + // Add the attributeNames. + addItemsToList( this._.attributeNames, rules.attributeNames, priority ); + + // Add the elements. + addNamedItems( this._.elements, rules.elements, priority ); + + // Add the attributes. + addNamedItems( this._.attributes, rules.attributes, priority ); + + // Add the text. + this._.text = transformNamedItem( this._.text, rules.text, priority ) || this._.text; + + // Add the comment. + this._.comment = transformNamedItem( this._.comment, rules.comment, priority ) || this._.comment; + + // Add root fragment. + this._.root = transformNamedItem( this._.root, rules.root, priority ) || this._.root; + }, + + onElementName : function( name ) + { + return filterName( name, this._.elementNames ); + }, + + onAttributeName : function( name ) + { + return filterName( name, this._.attributeNames ); + }, + + onText : function( text ) + { + var textFilter = this._.text; + return textFilter ? textFilter.filter( text ) : text; + }, + + onComment : function( commentText, comment ) + { + var textFilter = this._.comment; + return textFilter ? textFilter.filter( commentText, comment ) : commentText; + }, + + onFragment : function( element ) + { + var rootFilter = this._.root; + return rootFilter ? rootFilter.filter( element ) : element; + }, + + onElement : function( element ) + { + // We must apply filters set to the specific element name as + // well as those set to the generic $ name. So, add both to an + // array and process them in a small loop. + var filters = [ this._.elements[ '^' ], this._.elements[ element.name ], this._.elements.$ ], + filter, ret; + + for ( var i = 0 ; i < 3 ; i++ ) + { + filter = filters[ i ]; + if ( filter ) + { + ret = filter.filter( element, this ); + + if ( ret === false ) + return null; + + if ( ret && ret != element ) + return this.onNode( ret ); + + // The non-root element has been dismissed by one of the filters. + if ( element.parent && !element.name ) + break; + } + } + + return element; + }, + + onNode : function( node ) + { + var type = node.type; + + return type == CKEDITOR.NODE_ELEMENT ? this.onElement( node ) : + type == CKEDITOR.NODE_TEXT ? new CKEDITOR.htmlParser.text( this.onText( node.value ) ) : + type == CKEDITOR.NODE_COMMENT ? new CKEDITOR.htmlParser.comment( this.onComment( node.value ) ): + null; + }, + + onAttribute : function( element, name, value ) + { + var filter = this._.attributes[ name ]; + + if ( filter ) + { + var ret = filter.filter( value, element, this ); + + if ( ret === false ) + return false; + + if ( typeof ret != 'undefined' ) + return ret; + } + + return value; + } + } + }); + + function filterName( name, filters ) + { + for ( var i = 0 ; name && i < filters.length ; i++ ) + { + var filter = filters[ i ]; + name = name.replace( filter[ 0 ], filter[ 1 ] ); + } + return name; + } + + function addItemsToList( list, items, priority ) + { + if ( typeof items == 'function' ) + items = [ items ]; + + var i, j, + listLength = list.length, + itemsLength = items && items.length; + + if ( itemsLength ) + { + // Find the index to insert the items at. + for ( i = 0 ; i < listLength && list[ i ].pri < priority ; i++ ) + { /*jsl:pass*/ } + + // Add all new items to the list at the specific index. + for ( j = itemsLength - 1 ; j >= 0 ; j-- ) + { + var item = items[ j ]; + if ( item ) + { + item.pri = priority; + list.splice( i, 0, item ); + } + } + } + } + + function addNamedItems( hashTable, items, priority ) + { + if ( items ) + { + for ( var name in items ) + { + var current = hashTable[ name ]; + + hashTable[ name ] = + transformNamedItem( + current, + items[ name ], + priority ); + + if ( !current ) + hashTable.$length++; + } + } + } + + function transformNamedItem( current, item, priority ) + { + if ( item ) + { + item.pri = priority; + + if ( current ) + { + // If the current item is not an Array, transform it. + if ( !current.splice ) + { + if ( current.pri > priority ) + current = [ item, current ]; + else + current = [ current, item ]; + + current.filter = callItems; + } + else + addItemsToList( current, item, priority ); + + return current; + } + else + { + item.filter = item; + return item; + } + } + } + + // Invoke filters sequentially on the array, break the iteration + // when it doesn't make sense to continue anymore. + function callItems( currentEntry ) + { + var isNode = currentEntry.type + || currentEntry instanceof CKEDITOR.htmlParser.fragment; + + for ( var i = 0 ; i < this.length ; i++ ) + { + // Backup the node info before filtering. + if ( isNode ) + { + var orgType = currentEntry.type, + orgName = currentEntry.name; + } + + var item = this[ i ], + ret = item.apply( window, arguments ); + + if ( ret === false ) + return ret; + + // We're filtering node (element/fragment). + if ( isNode ) + { + // No further filtering if it's not anymore + // fitable for the subsequent filters. + if ( ret && ( ret.name != orgName + || ret.type != orgType ) ) + { + return ret; + } + } + // Filtering value (nodeName/textValue/attrValue). + else + { + // No further filtering if it's not + // any more values. + if ( typeof ret != 'string' ) + return ret; + } + + ret != undefined && ( currentEntry = ret ); + } + + return currentEntry; + } +})(); + +// "entities" plugin +/* +{ + text : function( text ) + { + // TODO : Process entities. + return text.toUpperCase(); + } +}; +*/ diff --git a/rte/_source/core/htmlparser/fragment.js b/rte/_source/core/htmlparser/fragment.js new file mode 100644 index 0000000..2166e60 --- /dev/null +++ b/rte/_source/core/htmlparser/fragment.js @@ -0,0 +1,496 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * A lightweight representation of an HTML DOM structure. + * @constructor + * @example + */ +CKEDITOR.htmlParser.fragment = function() +{ + /** + * The nodes contained in the root of this fragment. + * @type Array + * @example + * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( 'Sample Text' ); + * alert( fragment.children.length ); "2" + */ + this.children = []; + + /** + * Get the fragment parent. Should always be null. + * @type Object + * @default null + * @example + */ + this.parent = null; + + /** @private */ + this._ = + { + isBlockLike : true, + hasInlineStarted : false + }; +}; + +(function() +{ + // Elements which the end tag is marked as optional in the HTML 4.01 DTD + // (expect empty elements). + var optionalClose = {colgroup:1,dd:1,dt:1,li:1,option:1,p:1,td:1,tfoot:1,th:1,thead:1,tr:1}; + + // Block-level elements whose internal structure should be respected during + // parser fixing. + var nonBreakingBlocks = CKEDITOR.tools.extend( + {table:1,ul:1,ol:1,dl:1}, + CKEDITOR.dtd.table, CKEDITOR.dtd.ul, CKEDITOR.dtd.ol, CKEDITOR.dtd.dl ), + listBlocks = CKEDITOR.dtd.$list, listItems = CKEDITOR.dtd.$listItem; + + /** + * Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string. + * @param {String} fragmentHtml The HTML to be parsed, filling the fragment. + * @param {Number} [fixForBody=false] Wrap body with specified element if needed. + * @returns CKEDITOR.htmlParser.fragment The fragment created. + * @example + * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( 'Sample Text' ); + * alert( fragment.children[0].name ); "b" + * alert( fragment.children[1].value ); " Text" + */ + CKEDITOR.htmlParser.fragment.fromHtml = function( fragmentHtml, fixForBody ) + { + var parser = new CKEDITOR.htmlParser(), + html = [], + fragment = new CKEDITOR.htmlParser.fragment(), + pendingInline = [], + pendingBRs = [], + currentNode = fragment, + // Indicate we're inside a
         element, spaces should be touched differently.
        +			inPre = false,
        +			returnPoint;
        +
        +		function checkPending( newTagName )
        +		{
        +			var pendingBRsSent;
        +
        +			if ( pendingInline.length > 0 )
        +			{
        +				for ( var i = 0 ; i < pendingInline.length ; i++ )
        +				{
        +					var pendingElement = pendingInline[ i ],
        +						pendingName = pendingElement.name,
        +						pendingDtd = CKEDITOR.dtd[ pendingName ],
        +						currentDtd = currentNode.name && CKEDITOR.dtd[ currentNode.name ];
        +
        +					if ( ( !currentDtd || currentDtd[ pendingName ] ) && ( !newTagName || !pendingDtd || pendingDtd[ newTagName ] || !CKEDITOR.dtd[ newTagName ] ) )
        +					{
        +						if ( !pendingBRsSent )
        +						{
        +							sendPendingBRs();
        +							pendingBRsSent = 1;
        +						}
        +
        +						// Get a clone for the pending element.
        +						pendingElement = pendingElement.clone();
        +
        +						// Add it to the current node and make it the current,
        +						// so the new element will be added inside of it.
        +						pendingElement.parent = currentNode;
        +						currentNode = pendingElement;
        +
        +						// Remove the pending element (back the index by one
        +						// to properly process the next entry).
        +						pendingInline.splice( i, 1 );
        +						i--;
        +					}
        +				}
        +			}
        +		}
        +
        +		function sendPendingBRs()
        +		{
        +			while ( pendingBRs.length )
        +				currentNode.add( pendingBRs.shift() );
        +		}
        +
        +		function addElement( element, target, enforceCurrent )
        +		{
        +			target = target || currentNode || fragment;
        +
        +			// If the target is the fragment and this element can't go inside
        +			// body (if fixForBody).
        +			if ( fixForBody && !target.type )
        +			{
        +				var elementName, realElementName;
        +				if ( element.attributes
        +					 && ( realElementName =
        +						  element.attributes[ '_cke_real_element_type' ] ) )
        +					elementName = realElementName;
        +				else
        +					elementName =  element.name;
        +				if ( elementName
        +						&& !( elementName in CKEDITOR.dtd.$body )
        +						&& !( elementName in CKEDITOR.dtd.$nonBodyContent )  )
        +				{
        +					var savedCurrent = currentNode;
        +
        +					// Create a 

        in the fragment. + currentNode = target; + parser.onTagOpen( fixForBody, {} ); + + // The new target now is the

        . + target = currentNode; + + if ( enforceCurrent ) + currentNode = savedCurrent; + } + } + + // Rtrim empty spaces on block end boundary. (#3585) + if ( element._.isBlockLike + && element.name != 'pre' ) + { + + var length = element.children.length, + lastChild = element.children[ length - 1 ], + text; + if ( lastChild && lastChild.type == CKEDITOR.NODE_TEXT ) + { + if ( !( text = CKEDITOR.tools.rtrim( lastChild.value ) ) ) + element.children.length = length -1; + else + lastChild.value = text; + } + } + + target.add( element ); + + if ( element.returnPoint ) + { + currentNode = element.returnPoint; + delete element.returnPoint; + } + } + + parser.onTagOpen = function( tagName, attributes, selfClosing ) + { + var element = new CKEDITOR.htmlParser.element( tagName, attributes ); + + // "isEmpty" will be always "false" for unknown elements, so we + // must force it if the parser has identified it as a selfClosing tag. + if ( element.isUnknown && selfClosing ) + element.isEmpty = true; + + // This is a tag to be removed if empty, so do not add it immediately. + if ( CKEDITOR.dtd.$removeEmpty[ tagName ] ) + { + pendingInline.push( element ); + return; + } + else if ( tagName == 'pre' ) + inPre = true; + else if ( tagName == 'br' && inPre ) + { + currentNode.add( new CKEDITOR.htmlParser.text( '\n' ) ); + return; + } + + if ( tagName == 'br' ) + { + pendingBRs.push( element ); + return; + } + + var currentName = currentNode.name; + + var currentDtd = currentName + && ( CKEDITOR.dtd[ currentName ] + || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) ); + + // If the element cannot be child of the current element. + if ( currentDtd // Fragment could receive any elements. + && !element.isUnknown && !currentNode.isUnknown && !currentDtd[ tagName ] ) + { + + var reApply = false, + addPoint; // New position to start adding nodes. + + // Fixing malformed nested lists by moving it into a previous list item. (#3828) + if ( tagName in listBlocks + && currentName in listBlocks ) + { + var children = currentNode.children, + lastChild = children[ children.length - 1 ]; + + // Establish the list item if it's not existed. + if ( !( lastChild && lastChild.name in listItems ) ) + addElement( ( lastChild = new CKEDITOR.htmlParser.element( 'li' ) ), currentNode ); + + returnPoint = currentNode, addPoint = lastChild; + } + // If the element name is the same as the current element name, + // then just close the current one and append the new one to the + // parent. This situation usually happens with

        ,

      1. ,
        and + //
        , specially in IE. Do not enter in this if block in this case. + else if ( tagName == currentName ) + { + addElement( currentNode, currentNode.parent ); + } + else + { + if ( nonBreakingBlocks[ currentName ] ) + { + if ( !returnPoint ) + returnPoint = currentNode; + } + else + { + addElement( currentNode, currentNode.parent, true ); + + if ( !optionalClose[ currentName ] ) + { + // The current element is an inline element, which + // cannot hold the new one. Put it in the pending list, + // and try adding the new one after it. + pendingInline.unshift( currentNode ); + } + } + + reApply = true; + } + + if ( addPoint ) + currentNode = addPoint; + // Try adding it to the return point, or the parent element. + else + currentNode = currentNode.returnPoint || currentNode.parent; + + if ( reApply ) + { + parser.onTagOpen.apply( this, arguments ); + return; + } + } + + checkPending( tagName ); + sendPendingBRs(); + + element.parent = currentNode; + element.returnPoint = returnPoint; + returnPoint = 0; + + if ( element.isEmpty ) + addElement( element ); + else + currentNode = element; + }; + + parser.onTagClose = function( tagName ) + { + // Check if there is any pending tag to be closed. + for ( var i = pendingInline.length - 1 ; i >= 0 ; i-- ) + { + // If found, just remove it from the list. + if ( tagName == pendingInline[ i ].name ) + { + pendingInline.splice( i, 1 ); + return; + } + } + + var pendingAdd = [], + newPendingInline = [], + candidate = currentNode; + + while ( candidate.type && candidate.name != tagName ) + { + // If this is an inline element, add it to the pending list, if we're + // really closing one of the parents element later, they will continue + // after it. + if ( !candidate._.isBlockLike ) + newPendingInline.unshift( candidate ); + + // This node should be added to it's parent at this point. But, + // it should happen only if the closing tag is really closing + // one of the nodes. So, for now, we just cache it. + pendingAdd.push( candidate ); + + candidate = candidate.parent; + } + + if ( candidate.type ) + { + // Add all elements that have been found in the above loop. + for ( i = 0 ; i < pendingAdd.length ; i++ ) + { + var node = pendingAdd[ i ]; + addElement( node, node.parent ); + } + + currentNode = candidate; + + if ( currentNode.name == 'pre' ) + inPre = false; + + if ( candidate._.isBlockLike ) + sendPendingBRs(); + + addElement( candidate, candidate.parent ); + + // The parent should start receiving new nodes now, except if + // addElement changed the currentNode. + if ( candidate == currentNode ) + currentNode = currentNode.parent; + + pendingInline = pendingInline.concat( newPendingInline ); + } + + if ( tagName == 'body' ) + fixForBody = false; + }; + + parser.onText = function( text ) + { + // Trim empty spaces at beginning of element contents except
        .
        +			if ( !currentNode._.hasInlineStarted && !inPre )
        +			{
        +				text = CKEDITOR.tools.ltrim( text );
        +
        +				if ( text.length === 0 )
        +					return;
        +			}
        +
        +			sendPendingBRs();
        +			checkPending();
        +
        +			if ( fixForBody
        +				 && ( !currentNode.type || currentNode.name == 'body' )
        +				 && CKEDITOR.tools.trim( text ) )
        +			{
        +				this.onTagOpen( fixForBody, {} );
        +			}
        +
        +			// Shrinking consequential spaces into one single for all elements
        +			// text contents.
        +			if ( !inPre )
        +				text = text.replace( /[\t\r\n ]{2,}|[\t\r\n]/g, ' ' );
        +
        +			currentNode.add( new CKEDITOR.htmlParser.text( text ) );
        +		};
        +
        +		parser.onCDATA = function( cdata )
        +		{
        +			currentNode.add( new CKEDITOR.htmlParser.cdata( cdata ) );
        +		};
        +
        +		parser.onComment = function( comment )
        +		{
        +			currentNode.add( new CKEDITOR.htmlParser.comment( comment ) );
        +		};
        +
        +		// Parse it.
        +		parser.parse( fragmentHtml );
        +
        +		sendPendingBRs();
        +
        +		// Close all pending nodes.
        +		while ( currentNode.type )
        +		{
        +			var parent = currentNode.parent,
        +				node = currentNode;
        +
        +			if ( fixForBody
        +				 && ( !parent.type || parent.name == 'body' )
        +				 && !CKEDITOR.dtd.$body[ node.name ] )
        +			{
        +				currentNode = parent;
        +				parser.onTagOpen( fixForBody, {} );
        +				parent = currentNode;
        +			}
        +
        +			parent.add( node );
        +			currentNode = parent;
        +		}
        +
        +		return fragment;
        +	};
        +
        +	CKEDITOR.htmlParser.fragment.prototype =
        +	{
        +		/**
        +		 * Adds a node to this fragment.
        +		 * @param {Object} node The node to be added. It can be any of of the
        +		 *		following types: {@link CKEDITOR.htmlParser.element},
        +		 *		{@link CKEDITOR.htmlParser.text} and
        +		 *		{@link CKEDITOR.htmlParser.comment}.
        +		 * @example
        +		 */
        +		add : function( node )
        +		{
        +			var len = this.children.length,
        +				previous = len > 0 && this.children[ len - 1 ] || null;
        +
        +			if ( previous )
        +			{
        +				// If the block to be appended is following text, trim spaces at
        +				// the right of it.
        +				if ( node._.isBlockLike && previous.type == CKEDITOR.NODE_TEXT )
        +				{
        +					previous.value = CKEDITOR.tools.rtrim( previous.value );
        +
        +					// If we have completely cleared the previous node.
        +					if ( previous.value.length === 0 )
        +					{
        +						// Remove it from the list and add the node again.
        +						this.children.pop();
        +						this.add( node );
        +						return;
        +					}
        +				}
        +
        +				previous.next = node;
        +			}
        +
        +			node.previous = previous;
        +			node.parent = this;
        +
        +			this.children.push( node );
        +
        +			this._.hasInlineStarted = node.type == CKEDITOR.NODE_TEXT || ( node.type == CKEDITOR.NODE_ELEMENT && !node._.isBlockLike );
        +		},
        +
        +		/**
        +		 * Writes the fragment HTML to a CKEDITOR.htmlWriter.
        +		 * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML.
        +		 * @example
        +		 * var writer = new CKEDITOR.htmlWriter();
        +		 * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<P><B>Example' );
        +		 * fragment.writeHtml( writer )
        +		 * alert( writer.getHtml() );  "<p><b>Example</b></p>"
        +		 */
        +		writeHtml : function( writer, filter )
        +		{
        +			var isChildrenFiltered;
        +			this.filterChildren = function()
        +			{
        +				var writer = new CKEDITOR.htmlParser.basicWriter();
        +				this.writeChildrenHtml.call( this, writer, filter, true );
        +				var html = writer.getHtml();
        +				this.children = new CKEDITOR.htmlParser.fragment.fromHtml( html ).children;
        +				isChildrenFiltered = 1;
        +			};
        +
        +			// Filtering the root fragment before anything else.
        +			!this.name && filter && filter.onFragment( this );
        +
        +			this.writeChildrenHtml( writer, isChildrenFiltered ? null : filter );
        +		},
        +
        +		writeChildrenHtml : function( writer, filter )
        +		{
        +			for ( var i = 0 ; i < this.children.length ; i++ )
        +				this.children[i].writeHtml( writer, filter );
        +		}
        +	};
        +})();
        diff --git a/rte/_source/core/htmlparser/text.js b/rte/_source/core/htmlparser/text.js
        new file mode 100644
        index 0000000..ad4ed1f
        --- /dev/null
        +++ b/rte/_source/core/htmlparser/text.js
        @@ -0,0 +1,55 @@
        +/*
        +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
        +For licensing, see LICENSE.html or http://ckeditor.com/license
        +*/
        +
        +(function()
        +{
        +	var spacesRegex = /[\t\r\n ]{2,}|[\t\r\n]/g;
        +
        +	/**
        +	 * A lightweight representation of HTML text.
        +	 * @constructor
        +	 * @example
        +	 */
        + 	CKEDITOR.htmlParser.text = function( value )
        +	{
        +		/**
        +		 * The text value.
        +		 * @type String
        +		 * @example
        +		 */
        +		this.value = value;
        +
        +		/** @private */
        +		this._ =
        +		{
        +			isBlockLike : false
        +		};
        +	};
        +
        +	CKEDITOR.htmlParser.text.prototype =
        +	{
        +		/**
        +		 * The node type. This is a constant value set to {@link CKEDITOR.NODE_TEXT}.
        +		 * @type Number
        +		 * @example
        +		 */
        +		type : CKEDITOR.NODE_TEXT,
        +
        +		/**
        +		 * Writes the HTML representation of this text to a CKEDITOR.htmlWriter.
        +		 * @param {CKEDITOR.htmlWriter} writer The writer to which write the HTML.
        +		 * @example
        +		 */
        +		writeHtml : function( writer, filter )
        +		{
        +			var text = this.value;
        +
        +			if ( filter && !( text = filter.onText( text, this ) ) )
        +				return;
        +
        +			writer.text( text );
        +		}
        +	};
        +})();
        diff --git a/rte/_source/core/imagecacher.js b/rte/_source/core/imagecacher.js
        new file mode 100644
        index 0000000..64dd124
        --- /dev/null
        +++ b/rte/_source/core/imagecacher.js
        @@ -0,0 +1,59 @@
        +/*
        +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
        +For licensing, see LICENSE.html or http://ckeditor.com/license
        +*/
        +
        +(function()
        +{
        +	var loaded = {};
        +
        +	var loadImage = function( image, callback )
        +	{
        +		var doCallback = function()
        +			{
        +				img.removeAllListeners();
        +				loaded[ image ] = 1;
        +				callback();
        +			};
        +
        +		var img = new CKEDITOR.dom.element( 'img' );
        +		img.on( 'load', doCallback );
        +		img.on( 'error', doCallback );
        +		img.setAttribute( 'src', image );
        +	};
        +
        +	/**
        +	 * Load images into the browser cache.
        +	 * @namespace
        +	 * @example
        +	 */
        + 	CKEDITOR.imageCacher =
        +	{
        +		/**
        +		 * Loads one or more images.
        +		 * @param {Array} images The URLs for the images to be loaded.
        +		 * @param {Function} callback The function to be called once all images
        +		 *		are loaded.
        +		 */
        +		load : function( images, callback )
        +		{
        +			var pendingCount = images.length;
        +
        +			var checkPending = function()
        +			{
        +				if ( --pendingCount === 0 )
        +					callback();
        +			};
        +
        +			for ( var i = 0 ; i < images.length ; i++ )
        +			{
        +				var image = images[ i ];
        +
        +				if ( loaded[ image ] )
        +					checkPending();
        +				else
        +					loadImage( image, checkPending );
        +			}
        +		}
        +	};
        +})();
        diff --git a/rte/_source/core/lang.js b/rte/_source/core/lang.js
        new file mode 100644
        index 0000000..5519668
        --- /dev/null
        +++ b/rte/_source/core/lang.js
        @@ -0,0 +1,152 @@
        +/*
        +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
        +For licensing, see LICENSE.html or http://ckeditor.com/license
        +*/
        +
        +(function()
        +{
        +	var loadedLangs = {};
        +
        +	CKEDITOR.lang =
        +	{
        +		/**
        +		 * The list of languages available in the editor core.
        +		 * @type Object
        +		 * @example
        +		 * alert( CKEDITOR.lang.en );  // "true"
        +		 */
        +		languages :
        +		{
        +			'af'	: 1,
        +			'ar'	: 1,
        +			'bg'	: 1,
        +			'bn'	: 1,
        +			'bs'	: 1,
        +			'ca'	: 1,
        +			'cs'	: 1,
        +			'cy'	: 1,
        +			'da'	: 1,
        +			'de'	: 1,
        +			'el'	: 1,
        +			'en-au'	: 1,
        +			'en-ca'	: 1,
        +			'en-gb'	: 1,
        +			'en'	: 1,
        +			'eo'	: 1,
        +			'es'	: 1,
        +			'et'	: 1,
        +			'eu'	: 1,
        +			'fa'	: 1,
        +			'fi'	: 1,
        +			'fo'	: 1,
        +			'fr-ca'	: 1,
        +			'fr'	: 1,
        +			'gl'	: 1,
        +			'gu'	: 1,
        +			'he'	: 1,
        +			'hi'	: 1,
        +			'hr'	: 1,
        +			'hu'	: 1,
        +			'is'	: 1,
        +			'it'	: 1,
        +			'ja'	: 1,
        +			'km'	: 1,
        +			'ko'	: 1,
        +			'lt'	: 1,
        +			'lv'	: 1,
        +			'mn'	: 1,
        +			'ms'	: 1,
        +			'nb'	: 1,
        +			'nl'	: 1,
        +			'no'	: 1,
        +			'pl'	: 1,
        +			'pt-br'	: 1,
        +			'pt'	: 1,
        +			'ro'	: 1,
        +			'ru'	: 1,
        +			'sk'	: 1,
        +			'sl'	: 1,
        +			'sr-latn'	: 1,
        +			'sr'	: 1,
        +			'sv'	: 1,
        +			'th'	: 1,
        +			'tr'	: 1,
        +			'uk'	: 1,
        +			'vi'	: 1,
        +			'zh-cn'	: 1,
        +			'zh'	: 1
        +		},
        +
        +		/**
        +		 * Loads a specific language file, or auto detect it. A callback is
        +		 * then called when the file gets loaded.
        +		 * @param {String} languageCode The code of the language file to be
        +		 *		loaded. If "autoDetect" is set to true, this language will be
        +		 *		used as the default one, if the detect language is not
        +		 *		available in the core.
        +		 * @param {Boolean} autoDetect Indicates that the function must try to
        +		 *		detect the user language and load it instead.
        +		 * @param {Function} callback The function to be called once the
        +		 *		language file is loaded. Two parameters are passed to this
        +		 *		function: the language code and the loaded language entries.
        +		 * @example
        +		 */
        +		load : function( languageCode, defaultLanguage, callback )
        +		{
        +			// If no languageCode - fallback to browser or default.
        +			// If languageCode - fallback to no-localized version or default.
        +			if ( !languageCode || !CKEDITOR.lang.languages[ languageCode ] )
        +				languageCode = this.detect( defaultLanguage, languageCode );
        +
        +			if ( !this[ languageCode ] )
        +			{
        +				CKEDITOR.scriptLoader.load( CKEDITOR.getUrl(
        +					'_source/' +	// @Packager.RemoveLine
        +					'lang/' + languageCode + '.js' ),
        +					function()
        +						{
        +							callback( languageCode, this[ languageCode ] );
        +						}
        +						, this );
        +			}
        +			else
        +				callback( languageCode, this[ languageCode ] );
        +		},
        +
        +		/**
        +		 * Returns the language that best fit the user language. For example,
        +		 * suppose that the user language is "pt-br". If this language is
        +		 * supported by the editor, it is returned. Otherwise, if only "pt" is
        +		 * supported, it is returned instead. If none of the previous are
        +		 * supported, a default language is then returned.
        +		 * @param {String} defaultLanguage The default language to be returned
        +		 *		if the user language is not supported.
        +		 * @returns {String} The detected language code.
        +		 * @example
        +		 * alert( CKEDITOR.lang.detect( 'en' ) );  // e.g., in a German browser: "de"
        +		 */
        +		detect : function( defaultLanguage, probeLanguage )
        +		{
        +			var languages = this.languages;
        +			probeLanguage = probeLanguage || navigator.userLanguage || navigator.language;
        +
        +			var parts = probeLanguage
        +					.toLowerCase()
        +					.match( /([a-z]+)(?:-([a-z]+))?/ ),
        +				lang = parts[1],
        +				locale = parts[2];
        +
        +			if ( languages[ lang + '-' + locale ] )
        +				lang = lang + '-' + locale;
        +			else if ( !languages[ lang ] )
        +				lang = null;
        +
        +			CKEDITOR.lang.detect = lang ?
        +				function() { return lang; } :
        +				function( defaultLanguage ) { return defaultLanguage; };
        +
        +			return lang || defaultLanguage;
        +		}
        +	};
        +
        +})();
        diff --git a/rte/_source/core/loader.js b/rte/_source/core/loader.js
        new file mode 100644
        index 0000000..607d940
        --- /dev/null
        +++ b/rte/_source/core/loader.js
        @@ -0,0 +1,242 @@
        +/*
        +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
        +For licensing, see LICENSE.html or http://ckeditor.com/license
        +*/
        +
        +/**
        + * @fileOverview Defines the {@link CKEDITOR.loader} objects, which is used to
        + *		load core scripts and their dependencies from _source.
        + */
        +
        +if ( typeof CKEDITOR == 'undefined' )
        +	CKEDITOR = {};
        +
        +if ( !CKEDITOR.loader )
        +{
        +	/**
        +	 * Load core scripts and their dependencies from _source.
        +	 * @namespace
        +	 * @example
        +	 */
        +	CKEDITOR.loader = (function()
        +	{
        +		// Table of script names and their dependencies.
        +		var scripts =
        +		{
        +			'core/_bootstrap'		: [ 'core/config', 'core/ckeditor', 'core/plugins', 'core/scriptloader', 'core/tools', /* The following are entries that we want to force loading at the end to avoid dependence recursion */ 'core/dom/comment', 'core/dom/elementpath', 'core/dom/text', 'core/dom/range' ],
        +			'core/ajax'				: [ 'core/xml' ],
        +			'core/ckeditor'			: [ 'core/ckeditor_basic', 'core/dom', 'core/dtd', 'core/dom/document', 'core/dom/element', 'core/editor', 'core/event', 'core/htmlparser', 'core/htmlparser/element', 'core/htmlparser/fragment', 'core/htmlparser/filter', 'core/htmlparser/basicwriter', 'core/tools' ],
        +			'core/ckeditor_base'	: [],
        +			'core/ckeditor_basic'	: [ 'core/editor_basic', 'core/env', 'core/event' ],
        +			'core/command'			: [],
        +			'core/config'			: [ 'core/ckeditor_base' ],
        +			'core/dom'				: [],
        +			'core/dom/comment'		: [ 'core/dom/node' ],
        +			'core/dom/document'		: [ 'core/dom', 'core/dom/domobject', 'core/dom/window' ],
        +			'core/dom/documentfragment'	: [ 'core/dom/element' ],
        +			'core/dom/element'		: [ 'core/dom', 'core/dom/document', 'core/dom/domobject', 'core/dom/node', 'core/dom/nodelist', 'core/tools' ],
        +			'core/dom/elementpath'	: [ 'core/dom/element' ],
        +			'core/dom/event'		: [],
        +			'core/dom/node'			: [ 'core/dom/domobject', 'core/tools' ],
        +			'core/dom/nodelist'		: [ 'core/dom/node' ],
        +			'core/dom/domobject'	: [ 'core/dom/event' ],
        +			'core/dom/range'		: [ 'core/dom/document', 'core/dom/documentfragment', 'core/dom/element', 'core/dom/walker' ],
        +			'core/dom/text'			: [ 'core/dom/node', 'core/dom/domobject' ],
        +			'core/dom/walker'		: [ 'core/dom/node' ],
        +			'core/dom/window'		: [ 'core/dom/domobject' ],
        +			'core/dtd'				: [ 'core/tools' ],
        +			'core/editor'			: [ 'core/command', 'core/config', 'core/editor_basic', 'core/focusmanager', 'core/lang', 'core/plugins', 'core/skins', 'core/themes', 'core/tools', 'core/ui' ],
        +			'core/editor_basic'		: [ 'core/event' ],
        +			'core/env'				: [],
        +			'core/event'			: [],
        +			'core/focusmanager'		: [],
        +			'core/htmlparser'		: [],
        +			'core/htmlparser/comment'	: [ 'core/htmlparser' ],
        +			'core/htmlparser/element'	: [ 'core/htmlparser', 'core/htmlparser/fragment' ],
        +			'core/htmlparser/fragment'	: [ 'core/htmlparser', 'core/htmlparser/comment', 'core/htmlparser/text', 'core/htmlparser/cdata' ],
        +			'core/htmlparser/text'		: [ 'core/htmlparser' ],
        +			'core/htmlparser/cdata'		: [ 'core/htmlparser' ],
        +			'core/htmlparser/filter'	: [ 'core/htmlparser' ],
        +			'core/htmlparser/basicwriter': [ 'core/htmlparser' ],
        +			'core/imagecacher'		: [ 'core/dom/element' ],
        +			'core/lang'				: [],
        +			'core/plugins'			: [ 'core/resourcemanager' ],
        +			'core/resourcemanager'	: [ 'core/scriptloader', 'core/tools' ],
        +			'core/scriptloader'		: [ 'core/dom/element', 'core/env' ],
        +			'core/skins'			: [ 'core/imagecacher', 'core/scriptloader' ],
        +			'core/themes'			: [ 'core/resourcemanager' ],
        +			'core/tools'			: [ 'core/env' ],
        +			'core/ui'				: [],
        +			'core/xml'				: [ 'core/env' ]
        +		};
        +
        +		var basePath = (function()
        +		{
        +			// This is a copy of CKEDITOR.basePath, but requires the script having
        +			// "_source/core/loader.js".
        +			if ( CKEDITOR && CKEDITOR.basePath )
        +				return CKEDITOR.basePath;
        +
        +			// Find out the editor directory path, based on its ' +
        +				'';
        +
        +			var iframe = CKEDITOR.dom.element.createFromHtml(
        +						'' );
        +
        +			iframe.on( 'load', function( e )
        +			{
        +				e.removeListener();
        +				var doc = iframe.getFrameDocument().$;
        +				// Custom domain handling is needed after each document.open().
        +				doc.open();
        +				if ( isCustomDomain )
        +					doc.domain = document.domain;
        +				doc.write( htmlToLoad );
        +				doc.close();
        +			}, this );
        +
        +			iframe.setStyles(
        +				{
        +					width : '346px',
        +					height : '130px',
        +					'background-color' : 'white',
        +					border : '1px solid black'
        +				} );
        +			iframe.setCustomData( 'dialog', this );
        +
        +			var field = this.getContentElement( 'general', 'editing_area' ),
        +				container = field.getElement();
        +			container.setHtml( '' );
        +			container.append( iframe );
        +
        +			// IE need a redirect on focus to make
        +			// the cursor blinking inside iframe. (#5461)
        +			if ( CKEDITOR.env.ie )
        +			{
        +				var focusGrabber = CKEDITOR.dom.element.createFromHtml( '' );
        +				focusGrabber.on( 'focus', function()
        +				{
        +					iframe.$.contentWindow.focus();
        +				});
        +				container.append( focusGrabber );
        +
        +				// Override focus handler on field.
        +				field.focus = function()
        +				{
        +					focusGrabber.focus();
        +					this.fire( 'focus' );
        +				};
        +			}
        +
        +			field.getInputElement = function(){ return iframe; };
        +
        +			// Force container to scale in IE.
        +			if ( CKEDITOR.env.ie )
        +			{
        +				container.setStyle( 'display', 'block' );
        +				container.setStyle( 'height', ( iframe.$.offsetHeight + 2 ) + 'px' );
        +			}
        +		},
        +
        +		onHide : function()
        +		{
        +			if ( CKEDITOR.env.ie )
        +				this.getParentEditor().document.getBody().$.contentEditable = 'true';
        +		},
        +
        +		onLoad : function()
        +		{
        +			if ( ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) && editor.lang.dir == 'rtl' )
        +				this.parts.contents.setStyle( 'overflow', 'hidden' );
        +		},
        +
        +		onOk : function()
        +		{
        +			var container = this.getContentElement( 'general', 'editing_area' ).getElement(),
        +				iframe = container.getElementsByTag( 'iframe' ).getItem( 0 ),
        +				editor = this.getParentEditor(),
        +				html = iframe.$.contentWindow.document.body.innerHTML;
        +
        +			setTimeout( function(){
        +				editor.fire( 'paste', { 'html' : html } );
        +			}, 0 );
        +
        +		},
        +
        +		contents : [
        +			{
        +				id : 'general',
        +				label : editor.lang.common.generalTab,
        +				elements : [
        +					{
        +						type : 'html',
        +						id : 'securityMsg',
        +						html : '
        ' + lang.securityMsg + '
        ' + }, + { + type : 'html', + id : 'pasteMsg', + html : '
        '+lang.pasteMsg +'
        ' + }, + { + type : 'html', + id : 'editing_area', + style : 'width: 100%; height: 100%;', + html : '', + focus : function() + { + var win = this.getInputElement().$.contentWindow; + + // #3291 : JAWS needs the 500ms delay to detect that the editor iframe + // iframe is no longer editable. So that it will put the focus into the + // Paste from Word dialog's editable area instead. + setTimeout( function() + { + win.focus(); + }, 500 ); + } + } + ] + } + ] + }; +}); diff --git a/rte/_source/plugins/clipboard/plugin.js b/rte/_source/plugins/clipboard/plugin.js new file mode 100644 index 0000000..b679a3f --- /dev/null +++ b/rte/_source/plugins/clipboard/plugin.js @@ -0,0 +1,413 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @file Clipboard support + */ + +(function() +{ + // Tries to execute any of the paste, cut or copy commands in IE. Returns a + // boolean indicating that the operation succeeded. + var execIECommand = function( editor, command ) + { + var doc = editor.document, + body = doc.getBody(); + + var enabled = false; + var onExec = function() + { + enabled = true; + }; + + // The following seems to be the only reliable way to detect that + // clipboard commands are enabled in IE. It will fire the + // onpaste/oncut/oncopy events only if the security settings allowed + // the command to execute. + body.on( command, onExec ); + + // IE6/7: document.execCommand has problem to paste into positioned element. + ( CKEDITOR.env.version > 7 ? doc.$ : doc.$.selection.createRange() ) [ 'execCommand' ]( command ); + + body.removeListener( command, onExec ); + + return enabled; + }; + + // Attempts to execute the Cut and Copy operations. + var tryToCutCopy = + CKEDITOR.env.ie ? + function( editor, type ) + { + return execIECommand( editor, type ); + } + : // !IE. + function( editor, type ) + { + try + { + // Other browsers throw an error if the command is disabled. + return editor.document.$.execCommand( type ); + } + catch( e ) + { + return false; + } + }; + + // A class that represents one of the cut or copy commands. + var cutCopyCmd = function( type ) + { + this.type = type; + this.canUndo = ( this.type == 'cut' ); // We can't undo copy to clipboard. + }; + + cutCopyCmd.prototype = + { + exec : function( editor, data ) + { + this.type == 'cut' && fixCut( editor ); + + var success = tryToCutCopy( editor, this.type ); + + if ( !success ) + alert( editor.lang.clipboard[ this.type + 'Error' ] ); // Show cutError or copyError. + + return success; + } + }; + + // Paste command. + var pasteCmd = + { + canUndo : false, + + exec : + CKEDITOR.env.ie ? + function( editor ) + { + // Prevent IE from pasting at the begining of the document. + editor.focus(); + + if ( !editor.document.getBody().fire( 'beforepaste' ) + && !execIECommand( editor, 'paste' ) ) + { + editor.fire( 'pasteDialog' ); + return false; + } + } + : + function( editor ) + { + try + { + if ( !editor.document.getBody().fire( 'beforepaste' ) + && !editor.document.$.execCommand( 'Paste', false, null ) ) + { + throw 0; + } + } + catch ( e ) + { + setTimeout( function() + { + editor.fire( 'pasteDialog' ); + }, 0 ); + return false; + } + } + }; + + // Listens for some clipboard related keystrokes, so they get customized. + var onKey = function( event ) + { + if ( this.mode != 'wysiwyg' ) + return; + + switch ( event.data.keyCode ) + { + // Paste + case CKEDITOR.CTRL + 86 : // CTRL+V + case CKEDITOR.SHIFT + 45 : // SHIFT+INS + + var body = this.document.getBody(); + + // Simulate 'beforepaste' event for all none-IEs. + if ( !CKEDITOR.env.ie && body.fire( 'beforepaste' ) ) + event.cancel(); + // Simulate 'paste' event for Opera/Firefox2. + else if ( CKEDITOR.env.opera + || CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) + body.fire( 'paste' ); + return; + + // Cut + case CKEDITOR.CTRL + 88 : // CTRL+X + case CKEDITOR.SHIFT + 46 : // SHIFT+DEL + + // Save Undo snapshot. + var editor = this; + this.fire( 'saveSnapshot' ); // Save before paste + setTimeout( function() + { + editor.fire( 'saveSnapshot' ); // Save after paste + }, 0 ); + } + }; + + // Allow to peek clipboard content by redirecting the + // pasting content into a temporary bin and grab the content of it. + function getClipboardData( evt, mode, callback ) + { + var doc = this.document; + + // Avoid recursions on 'paste' event for IE. + if ( CKEDITOR.env.ie && doc.getById( 'cke_pastebin' ) ) + return; + + // If the browser supports it, get the data directly + if (mode == 'text' && evt.data && evt.data.$.clipboardData) + { + // evt.data.$.clipboardData.types contains all the flavours in Mac's Safari, but not on windows. + var plain = evt.data.$.clipboardData.getData( 'text/plain' ); + if (plain) + { + evt.data.preventDefault(); + callback( plain ); + return; + } + } + + var sel = this.getSelection(), + range = new CKEDITOR.dom.range( doc ); + + // Create container to paste into + var pastebin = new CKEDITOR.dom.element( mode == 'text' ? 'textarea' : CKEDITOR.env.webkit ? 'body' : 'div', doc ); + pastebin.setAttribute( 'id', 'cke_pastebin' ); + // Safari requires a filler node inside the div to have the content pasted into it. (#4882) + CKEDITOR.env.webkit && pastebin.append( doc.createText( '\xa0' ) ); + doc.getBody().append( pastebin ); + + pastebin.setStyles( + { + position : 'absolute', + // Position the bin exactly at the position of the selected element + // to avoid any subsequent document scroll. + top : sel.getStartElement().getDocumentPosition().y + 'px', + width : '1px', + height : '1px', + overflow : 'hidden' + }); + + // It's definitely a better user experience if we make the paste-bin pretty unnoticed + // by pulling it off the screen. + pastebin.setStyle( this.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-1000px' ); + + var bms = sel.createBookmarks(); + + // Turn off design mode temporarily before give focus to the paste bin. + if ( mode == 'text' ) + { + if ( CKEDITOR.env.ie ) + { + var ieRange = doc.getBody().$.createTextRange(); + ieRange.moveToElementText( pastebin.$ ); + ieRange.execCommand( 'Paste' ); + evt.data.preventDefault(); + } + else + { + doc.$.designMode = 'off'; + pastebin.$.focus(); + } + } + else + { + range.setStartAt( pastebin, CKEDITOR.POSITION_AFTER_START ); + range.setEndAt( pastebin, CKEDITOR.POSITION_BEFORE_END ); + range.select( true ); + } + + // Wait a while and grab the pasted contents + window.setTimeout( function() + { + mode == 'text' && !CKEDITOR.env.ie && ( doc.$.designMode = 'on' ); + pastebin.remove(); + + // Grab the HTML contents. + // We need to look for a apple style wrapper on webkit it also adds + // a div wrapper if you copy/paste the body of the editor. + // Remove hidden div and restore selection. + var bogusSpan; + pastebin = ( CKEDITOR.env.webkit + && ( bogusSpan = pastebin.getFirst() ) + && ( bogusSpan.is && bogusSpan.hasClass( 'Apple-style-span' ) ) ? + bogusSpan : pastebin ); + + sel.selectBookmarks( bms ); + callback( pastebin[ 'get' + ( mode == 'text' ? 'Value' : 'Html' ) ]() ); + }, 0 ); + } + + // Cutting off control type element in IE standards breaks the selection entirely. (#4881) + function fixCut( editor ) + { + if ( !CKEDITOR.env.ie || editor.document.$.compatMode == 'BackCompat' ) + return; + + var sel = editor.getSelection(); + var control; + if( ( sel.getType() == CKEDITOR.SELECTION_ELEMENT ) && ( control = sel.getSelectedElement() ) ) + { + var range = sel.getRanges()[ 0 ]; + var dummy = editor.document.createText( '' ); + dummy.insertBefore( control ); + range.setStartBefore( dummy ); + range.setEndAfter( control ); + sel.selectRanges( [ range ] ); + + // Clear up the fix if the paste wasn't succeeded. + setTimeout( function() + { + // Element still online? + if ( control.getParent() ) + { + dummy.remove(); + sel.selectElement( control ); + } + }, 0 ); + } + } + + // Register the plugin. + CKEDITOR.plugins.add( 'clipboard', + { + requires : [ 'dialog', 'htmldataprocessor' ], + init : function( editor ) + { + // Inserts processed data into the editor at the end of the + // events chain. + editor.on( 'paste', function( evt ) + { + var data = evt.data; + if ( data[ 'html' ] ) + editor.insertHtml( data[ 'html' ] ); + else if ( data[ 'text' ] ) + editor.insertText( data[ 'text' ] ); + + }, null, null, 1000 ); + + editor.on( 'pasteDialog', function( evt ) + { + setTimeout( function() + { + // Open default paste dialog. + editor.openDialog( 'paste' ); + }, 0 ); + }); + + function addButtonCommand( buttonName, commandName, command, ctxMenuOrder ) + { + var lang = editor.lang[ commandName ]; + + editor.addCommand( commandName, command ); + editor.ui.addButton( buttonName, + { + label : lang, + command : commandName + }); + + // If the "menu" plugin is loaded, register the menu item. + if ( editor.addMenuItems ) + { + editor.addMenuItem( commandName, + { + label : lang, + command : commandName, + group : 'clipboard', + order : ctxMenuOrder + }); + } + } + + addButtonCommand( 'Cut', 'cut', new cutCopyCmd( 'cut' ), 1 ); + addButtonCommand( 'Copy', 'copy', new cutCopyCmd( 'copy' ), 4 ); + addButtonCommand( 'Paste', 'paste', pasteCmd, 8 ); + + CKEDITOR.dialog.add( 'paste', CKEDITOR.getUrl( this.path + 'dialogs/paste.js' ) ); + + editor.on( 'key', onKey, editor ); + + var mode = editor.config.forcePasteAsPlainText ? 'text' : 'html'; + + // We'll be catching all pasted content in one line, regardless of whether the + // it's introduced by a document command execution (e.g. toolbar buttons) or + // user paste behaviors. (e.g. Ctrl-V) + editor.on( 'contentDom', function() + { + var body = editor.document.getBody(); + body.on( ( (mode == 'text' && CKEDITOR.env.ie) || CKEDITOR.env.webkit ) ? 'paste' : 'beforepaste', + function( evt ) + { + if ( depressBeforeEvent ) + return; + + getClipboardData.call( editor, evt, mode, function ( data ) + { + // The very last guard to make sure the + // paste has successfully happened. + if ( !data ) + return; + + var dataTransfer = {}; + dataTransfer[ mode ] = data; + editor.fire( 'paste', dataTransfer ); + } ); + }); + + body.on( 'beforecut', function() { !depressBeforeEvent && fixCut( editor ); } ); + }); + + // If the "contextmenu" plugin is loaded, register the listeners. + if ( editor.contextMenu ) + { + var depressBeforeEvent; + function stateFromNamedCommand( command ) + { + // IE Bug: queryCommandEnabled('paste') fires also 'beforepaste(copy/cut)', + // guard to distinguish from the ordinary sources( either + // keyboard paste or execCommand ) (#4874). + CKEDITOR.env.ie && ( depressBeforeEvent = 1 ); + + var retval = editor.document.$.queryCommandEnabled( command ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED; + depressBeforeEvent = 0; + return retval; + } + + editor.contextMenu.addListener( function() + { + return { + cut : stateFromNamedCommand( 'Cut' ), + + // Browser bug: 'Cut' has the correct states for both Copy and Cut. + copy : stateFromNamedCommand( 'Cut' ), + paste : CKEDITOR.env.webkit ? CKEDITOR.TRISTATE_OFF : stateFromNamedCommand( 'Paste' ) + }; + }); + } + } + }); +})(); + +/** + * Fired when a clipboard operation is about to be taken into the editor. + * Listeners can manipulate the data to be pasted before having it effectively + * inserted into the document. + * @name CKEDITOR.editor#paste + * @since 3.1 + * @event + * @param {String} [data.html] The HTML data to be pasted. If not available, e.data.text will be defined. + * @param {String} [data.text] The plain text data to be pasted, available when plain text operations are to used. If not available, e.data.html will be defined. + */ diff --git a/rte/_source/plugins/colorbutton/plugin.js b/rte/_source/plugins/colorbutton/plugin.js new file mode 100644 index 0000000..dd127f9 --- /dev/null +++ b/rte/_source/plugins/colorbutton/plugin.js @@ -0,0 +1,248 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'colorbutton', +{ + requires : [ 'panelbutton', 'floatpanel', 'styles' ], + + init : function( editor ) + { + var config = editor.config, + lang = editor.lang.colorButton; + + var clickFn; + + if ( !CKEDITOR.env.hc ) + { + addButton( 'TextColor', 'fore', lang.textColorTitle ); + addButton( 'BGColor', 'back', lang.bgColorTitle ); + } + + function addButton( name, type, title ) + { + editor.ui.add( name, CKEDITOR.UI_PANELBUTTON, + { + label : title, + title : title, + className : 'cke_button_' + name.toLowerCase(), + modes : { wysiwyg : 1 }, + + panel : + { + css : editor.skin.editor.css, + attributes : { role : 'listbox', 'aria-label' : lang.panelTitle } + }, + + onBlock : function( panel, block ) + { + block.autoSize = true; + block.element.addClass( 'cke_colorblock' ); + block.element.setHtml( renderColors( panel, type ) ); + + var keys = block.keys; + keys[ 39 ] = 'next'; // ARROW-RIGHT + keys[ 40 ] = 'next'; // ARROW-DOWN + keys[ 9 ] = 'next'; // TAB + keys[ 37 ] = 'prev'; // ARROW-LEFT + keys[ 38 ] = 'prev'; // ARROW-UP + keys[ CKEDITOR.SHIFT + 9 ] = 'prev'; // SHIFT + TAB + keys[ 32 ] = 'click'; // SPACE + } + }); + } + + + function renderColors( panel, type ) + { + var output = [], + colors = config.colorButton_colors.split( ',' ), + total = colors.length + ( config.colorButton_enableMore ? 2 : 1 ); + + var clickFn = CKEDITOR.tools.addFunction( function( color, type ) + { + if ( color == '?' ) + { + var applyColorStyle = arguments.callee; + function onColorDialogClose( evt ) + { + this.removeListener( 'ok', onColorDialogClose ); + this.removeListener( 'cancel', onColorDialogClose ); + + evt.name == 'ok' && applyColorStyle( this.getContentElement( 'picker', 'selectedColor' ).getValue(), type ); + } + + editor.openDialog( 'colordialog', function() + { + this.on( 'ok', onColorDialogClose ); + this.on( 'cancel', onColorDialogClose ); + } ); + + return; + } + + editor.focus(); + + panel.hide(); + + + editor.fire( 'saveSnapshot' ); + + // Clean up any conflicting style within the range. + new CKEDITOR.style( config['colorButton_' + type + 'Style'], { color : 'inherit' } ).remove( editor.document ); + + if ( color ) + { + var colorStyle = config['colorButton_' + type + 'Style']; + + colorStyle.childRule = type == 'back' ? + // It's better to apply background color as the innermost style. (#3599) + function(){ return false; } : + // Fore color style must be applied inside links instead of around it. + function( element ){ return element.getName() != 'a'; }; + + new CKEDITOR.style( colorStyle, { color : color } ).apply( editor.document ); + } + + editor.fire( 'saveSnapshot' ); + }); + + // Render the "Automatic" button. + output.push( + '' + + '' + + '' + + '' + + '' + + '' + + '
        ' + + '' + + '', + lang.auto, + '
        ' + + '
        ' + + '' ); + + // Render the color boxes. + for ( var i = 0 ; i < colors.length ; i++ ) + { + if ( ( i % 8 ) === 0 ) + output.push( '' ); + + var parts = colors[ i ].split( '/' ), + colorName = parts[ 0 ], + colorCode = parts[ 1 ] || colorName; + + // The data can be only a color code (without #) or colorName + color code + // If only a color code is provided, then the colorName is the color with the hash + // Convert the color from RGB to RRGGBB for better compatibility with IE and . See #5676 + if (!parts[1]) + colorName = '#' + colorName.replace( /^(.)(.)(.)$/, '$1$1$2$2$3$3' ); + + var colorLabel = editor.lang.colors[ colorCode ] || colorCode; + output.push( + '' ); + } + + // Render the "More Colors" button. + if ( config.colorButton_enableMore ) + { + output.push( + '' + + '' + + '' ); // It is later in the code. + } + + output.push( '
        ' + + '' + + '' + + '' + + '
        ' + + '', + lang.more, + '' + + '
        ' ); + + return output.join( '' ); + } + } +}); + +/** + * Whether to enable the "More Colors..." button in the color selectors. + * @default false + * @type Boolean + * @example + * config.colorButton_enableMore = false; + */ +CKEDITOR.config.colorButton_enableMore = true; + +/** + * Defines the colors to be displayed in the color selectors. It's a string + * containing the hexadecimal notation for HTML colors, without the "#" prefix. + * + * Since 3.3: A name may be optionally defined by prefixing the entries with the + * name and the slash character. For example, "FontColor1/FF9900" will be + * displayed as the color #FF9900 in the selector, but will be outputted as "FontColor1". + * @type String + * @default '000,800000,8B4513,2F4F4F,008080,000080,4B0082,696969,B22222,A52A2A,DAA520,006400,40E0D0,0000CD,800080,808080,F00,FF8C00,FFD700,008000,0FF,00F,EE82EE,A9A9A9,FFA07A,FFA500,FFFF00,00FF00,AFEEEE,ADD8E6,DDA0DD,D3D3D3,FFF0F5,FAEBD7,FFFFE0,F0FFF0,F0FFFF,F0F8FF,E6E6FA,FFF' + * @example + * // Brazil colors only. + * config.colorButton_colors = '00923E,F8C100,28166F'; + * @example + * config.colorButton_colors = 'FontColor1/FF9900,FontColor2/0066CC,FontColor3/F00' + */ +CKEDITOR.config.colorButton_colors = + '000,800000,8B4513,2F4F4F,008080,000080,4B0082,696969,' + + 'B22222,A52A2A,DAA520,006400,40E0D0,0000CD,800080,808080,' + + 'F00,FF8C00,FFD700,008000,0FF,00F,EE82EE,A9A9A9,' + + 'FFA07A,FFA500,FFFF00,00FF00,AFEEEE,ADD8E6,DDA0DD,D3D3D3,' + + 'FFF0F5,FAEBD7,FFFFE0,F0FFF0,F0FFFF,F0F8FF,E6E6FA,FFF'; + +/** + * Holds the style definition to be used to apply the text foreground color. + * @type Object + * @example + * // This is basically the default setting value. + * config.colorButton_foreStyle = + * { + * element : 'span', + * styles : { 'color' : '#(color)' } + * }; + */ +CKEDITOR.config.colorButton_foreStyle = + { + element : 'span', + styles : { 'color' : '#(color)' }, + overrides : [ { element : 'font', attributes : { 'color' : null } } ] + }; + +/** + * Holds the style definition to be used to apply the text background color. + * @type Object + * @example + * // This is basically the default setting value. + * config.colorButton_backStyle = + * { + * element : 'span', + * styles : { 'background-color' : '#(color)' } + * }; + */ +CKEDITOR.config.colorButton_backStyle = + { + element : 'span', + styles : { 'background-color' : '#(color)' } + }; diff --git a/rte/_source/plugins/colordialog/dialogs/colordialog.js b/rte/_source/plugins/colordialog/dialogs/colordialog.js new file mode 100644 index 0000000..9eb2261 --- /dev/null +++ b/rte/_source/plugins/colordialog/dialogs/colordialog.js @@ -0,0 +1,339 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.dialog.add( 'colordialog', function( editor ) + { + // Define some shorthands. + var $el = CKEDITOR.dom.element, + $doc = CKEDITOR.document, + $tools = CKEDITOR.tools, + lang = editor.lang.colordialog; + + // Reference the dialog. + var dialog; + + function spacer() + { + return { + type : 'html', + html : ' ' + }; + } + + function clearSelected() + { + $doc.getById( selHiColorId ).removeStyle( 'background-color' ); + dialog.getContentElement( 'picker', 'selectedColor' ).setValue( '' ); + } + + function updateSelected( evt ) + { + if ( ! (evt instanceof CKEDITOR.dom.event ) ) + evt = new CKEDITOR.dom.event( evt ); + + var target = evt.getTarget(), + color; + + if ( target.getName() == 'a' && ( color = target.getChild( 0 ).getHtml() ) ) + dialog.getContentElement( 'picker', 'selectedColor' ).setValue( color ); + } + + function updateHighlight( event ) + { + if ( ! (event instanceof CKEDITOR.dom.event ) ) + event = event.data; + + var target = event.getTarget(), + color; + + if ( target.getName() == 'a' && ( color = target.getChild( 0 ).getHtml() ) ) + { + $doc.getById( hicolorId ).setStyle( 'background-color', color ); + $doc.getById( hicolorTextId ).setHtml( color ); + } + } + + function clearHighlight() + { + $doc.getById( hicolorId ).removeStyle( 'background-color' ); + $doc.getById( hicolorTextId ).setHtml( ' ' ); + } + + var onMouseout = $tools.addFunction( clearHighlight ); + + var onClick = updateSelected, + onClickHandler = CKEDITOR.tools.addFunction( onClick ); + + var onFocus = updateHighlight, + onBlur = clearHighlight; + + var onKeydownHandler = CKEDITOR.tools.addFunction( function( ev ) + { + ev = new CKEDITOR.dom.event( ev ); + var element = ev.getTarget(); + var relative, nodeToMove; + var keystroke = ev.getKeystroke(); + var rtl = editor.lang.dir == 'rtl'; + + switch ( keystroke ) + { + // UP-ARROW + case 38 : + // relative is TR + if ( ( relative = element.getParent().getParent().getPrevious() ) ) + { + nodeToMove = relative.getChild( [element.getParent().getIndex(), 0] ); + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + } + ev.preventDefault(); + break; + // DOWN-ARROW + case 40 : + // relative is TR + if ( ( relative = element.getParent().getParent().getNext() ) ) + { + nodeToMove = relative.getChild( [ element.getParent().getIndex(), 0 ] ); + if ( nodeToMove && nodeToMove.type == 1 ) + { + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + } + } + ev.preventDefault(); + break; + // SPACE + // ENTER is already handled as onClick + case 32 : + onClick( ev ); + ev.preventDefault(); + break; + + // RIGHT-ARROW + case rtl ? 37 : 39 : + // relative is TD + if ( ( relative = element.getParent().getNext() ) ) + { + nodeToMove = relative.getChild( 0 ); + if ( nodeToMove.type == 1 ) + { + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + ev.preventDefault( true ); + } + else + onBlur( null, element ); + } + // relative is TR + else if ( ( relative = element.getParent().getParent().getNext() ) ) + { + nodeToMove = relative.getChild( [ 0, 0 ] ); + if ( nodeToMove && nodeToMove.type == 1 ) + { + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + ev.preventDefault( true ); + } + else + onBlur( null, element ); + } + break; + + // LEFT-ARROW + case rtl ? 39 : 37 : + // relative is TD + if ( ( relative = element.getParent().getPrevious() ) ) + { + nodeToMove = relative.getChild( 0 ); + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + ev.preventDefault( true ); + } + // relative is TR + else if ( ( relative = element.getParent().getParent().getPrevious() ) ) + { + nodeToMove = relative.getLast().getChild( 0 ); + nodeToMove.focus(); + onBlur( ev, element ); + onFocus( ev, nodeToMove ); + ev.preventDefault( true ); + } + else + onBlur( null, element ); + break; + default : + // Do not stop not handled events. + return; + } + }); + + function createColorTable() + { + // Create the base colors array. + var aColors = ['00','33','66','99','cc','ff']; + + // This function combines two ranges of three values from the color array into a row. + function appendColorRow( rangeA, rangeB ) + { + for ( var i = rangeA ; i < rangeA + 3 ; i++ ) + { + var row = table.$.insertRow(-1); + + for ( var j = rangeB ; j < rangeB + 3 ; j++ ) + { + for ( var n = 0 ; n < 6 ; n++ ) + { + appendColorCell( row, '#' + aColors[j] + aColors[n] + aColors[i] ); + } + } + } + } + + // This function create a single color cell in the color table. + function appendColorCell( targetRow, color ) + { + var cell = new $el( targetRow.insertCell( -1 ) ); + cell.setAttribute( 'class', 'ColorCell' ); + cell.setStyle( 'background-color', color ); + + cell.setStyle( 'width', '15px' ); + cell.setStyle( 'height', '15px' ); + + var index = cell.$.cellIndex + 1 + 18 * targetRow.rowIndex; + cell.append( CKEDITOR.dom.element.createFromHtml( + '' + color + ' ', CKEDITOR.document ) ); + } + + appendColorRow( 0, 0 ); + appendColorRow( 3, 0 ); + appendColorRow( 0, 3 ); + appendColorRow( 3, 3 ); + + // Create the last row. + var oRow = table.$.insertRow(-1) ; + + // Create the gray scale colors cells. + for ( var n = 0 ; n < 6 ; n++ ) + { + appendColorCell( oRow, '#' + aColors[n] + aColors[n] + aColors[n] ) ; + } + + // Fill the row with black cells. + for ( var i = 0 ; i < 12 ; i++ ) + { + appendColorCell( oRow, '#000000' ) ; + } + } + + var table = new $el( 'table' ); + createColorTable(); + + var numbering = function( id ) + { + return id + CKEDITOR.tools.getNextNumber(); + }, + hicolorId = numbering( 'hicolor' ), + hicolorTextId = numbering( 'hicolortext' ), + selHiColorId = numbering( 'selhicolor' ); + + return { + title : lang.title, + minWidth : 360, + minHeight : 220, + onLoad : function() + { + // Update reference. + dialog = this; + }, + contents : [ + { + id : 'picker', + label : lang.title, + accessKey : 'I', + elements : + [ + { + type : 'hbox', + padding : 0, + widths : [ '70%', '10%', '30%' ], + children : + [ + { + type : 'html', + html : '' + table.getHtml() + '
        ' + + '' + lang.options +'', + onLoad : function() + { + var table = CKEDITOR.document.getById( this.domId ); + table.on( 'mouseover', updateHighlight ); + }, + focus: function() + { + var firstColor = this.getElement().getElementsByTag( 'a' ).getItem( 0 ); + firstColor.focus(); + } + }, + spacer(), + { + type : 'vbox', + padding : 0, + widths : [ '70%', '5%', '25%' ], + children : + [ + { + type : 'html', + html : '' + lang.highlight +'\ +
        \ +
         
        ' + lang.selected + '\ +
        ' + }, + { + type : 'text', + label : lang.selected, + labelStyle: 'display:none', + id : 'selectedColor', + style : 'width: 74px', + onChange : function() + { + // Try to update color preview with new value. If fails, then set it no none. + try + { + $doc.getById( selHiColorId ).setStyle( 'background-color', this.getValue() ); + } + catch ( e ) + { + clearSelected(); + } + } + }, + spacer(), + { + type : 'button', + id : 'clear', + style : 'margin-top: 5px', + label : lang.clear, + onClick : clearSelected + } + ] + } + ] + } + ] + } + ] + }; + } + ); diff --git a/rte/_source/plugins/colordialog/plugin.js b/rte/_source/plugins/colordialog/plugin.js new file mode 100644 index 0000000..e320aa6 --- /dev/null +++ b/rte/_source/plugins/colordialog/plugin.js @@ -0,0 +1,13 @@ +( function() +{ + CKEDITOR.plugins.colordialog = + { + init : function( editor ) + { + editor.addCommand( 'colordialog', new CKEDITOR.dialogCommand( 'colordialog' ) ); + CKEDITOR.dialog.add( 'colordialog', this.path + 'dialogs/colordialog.js' ); + } + }; + + CKEDITOR.plugins.add( 'colordialog', CKEDITOR.plugins.colordialog ); +} )(); diff --git a/rte/_source/plugins/contextmenu/plugin.js b/rte/_source/plugins/contextmenu/plugin.js new file mode 100644 index 0000000..4516167 --- /dev/null +++ b/rte/_source/plugins/contextmenu/plugin.js @@ -0,0 +1,276 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +CKEDITOR.plugins.add( 'contextmenu', +{ + requires : [ 'menu' ], + + beforeInit : function( editor ) + { + editor.contextMenu = new CKEDITOR.plugins.contextMenu( editor ); + + editor.addCommand( 'contextMenu', + { + exec : function() + { + editor.contextMenu.show( editor.document.getBody() ); + } + }); + } +}); + +CKEDITOR.plugins.contextMenu = CKEDITOR.tools.createClass( +{ + $ : function( editor ) + { + this.id = 'cke_' + CKEDITOR.tools.getNextNumber(); + this.editor = editor; + this._.listeners = []; + this._.functionId = CKEDITOR.tools.addFunction( function( commandName ) + { + this._.panel.hide(); + editor.focus(); + editor.execCommand( commandName ); + }, + this); + + this.definition = + { + panel: + { + className : editor.skinClass + ' cke_contextmenu', + attributes : + { + 'aria-label' : editor.lang.contextmenu.options + } + } + }; + }, + + _ : + { + onMenu : function( offsetParent, corner, offsetX, offsetY ) + { + var menu = this._.menu, + editor = this.editor; + + if ( menu ) + { + menu.hide(); + menu.removeAll(); + } + else + { + menu = this._.menu = new CKEDITOR.menu( editor, this.definition ); + menu.onClick = CKEDITOR.tools.bind( function( item ) + { + menu.hide(); + + if ( item.onClick ) + item.onClick(); + else if ( item.command ) + editor.execCommand( item.command ); + + }, this ); + + menu.onEscape = function( keystroke ) + { + var parent = this.parent; + // 1. If it's sub-menu, restore the last focused item + // of upper level menu. + // 2. In case of a top-menu, close it. + if ( parent ) + { + parent._.panel.hideChild(); + // Restore parent block item focus. + var parentBlock = parent._.panel._.panel._.currentBlock, + parentFocusIndex = parentBlock._.focusIndex; + parentBlock._.markItem( parentFocusIndex ); + } + else if ( keystroke == 27 ) + { + this.hide(); + editor.focus(); + } + return false; + }; + } + + var listeners = this._.listeners, + includedItems = []; + + var selection = this.editor.getSelection(), + element = selection && selection.getStartElement(); + + menu.onHide = CKEDITOR.tools.bind( function() + { + menu.onHide = null; + + if ( CKEDITOR.env.ie ) + { + var selection = editor.getSelection(); + selection && selection.unlock(); + } + + this.onHide && this.onHide(); + }, + this ); + + // Call all listeners, filling the list of items to be displayed. + for ( var i = 0 ; i < listeners.length ; i++ ) + { + var listenerItems = listeners[ i ]( element, selection ); + + if ( listenerItems ) + { + for ( var itemName in listenerItems ) + { + var item = this.editor.getMenuItem( itemName ); + + if ( item ) + { + item.state = listenerItems[ itemName ]; + menu.add( item ); + } + } + } + } + + // Don't show context menu with zero items. + menu.items.length && menu.show( offsetParent, corner || ( editor.lang.dir == 'rtl' ? 2 : 1 ), offsetX, offsetY ); + } + }, + + proto : + { + addTarget : function( element, nativeContextMenuOnCtrl ) + { + // Opera doesn't support 'contextmenu' event, we have duo approaches employed here: + // 1. Inherit the 'button override' hack we introduced in v2 (#4530), while this require the Opera browser + // option 'Allow script to detect context menu/right click events' to be always turned on. + // 2. Considering the fact that ctrl/meta key is not been occupied + // for multiple range selecting (like Gecko), we use this key + // combination as a fallback for triggering context-menu. (#4530) + if ( CKEDITOR.env.opera ) + { + var contextMenuOverrideButton; + element.on( 'mousedown', function( evt ) + { + evt = evt.data; + if ( evt.$.button != 2 ) + { + if ( evt.getKeystroke() == CKEDITOR.CTRL + 1 ) + element.fire( 'contextmenu', evt ); + return; + } + + if ( nativeContextMenuOnCtrl + && ( CKEDITOR.env.mac ? evt.$.metaKey : evt.$.ctrlKey ) ) + return; + + var target = evt.getTarget(); + + if ( !contextMenuOverrideButton ) + { + var ownerDoc = target.getDocument(); + contextMenuOverrideButton = ownerDoc.createElement( 'input' ) ; + contextMenuOverrideButton.$.type = 'button' ; + ownerDoc.getBody().append( contextMenuOverrideButton ) ; + } + + contextMenuOverrideButton.setAttribute( 'style', 'position:absolute;top:' + ( evt.$.clientY - 2 ) + + 'px;left:' + ( evt.$.clientX - 2 ) + + 'px;width:5px;height:5px;opacity:0.01' ); + + } ); + + element.on( 'mouseup', function ( evt ) + { + if ( contextMenuOverrideButton ) + { + contextMenuOverrideButton.remove(); + contextMenuOverrideButton = undefined; + // Simulate 'contextmenu' event. + element.fire( 'contextmenu', evt.data ); + } + } ); + } + + element.on( 'contextmenu', function( event ) + { + var domEvent = event.data; + + if ( nativeContextMenuOnCtrl && + // Safari on Windows always show 'ctrlKey' as true in 'contextmenu' event, + // which make this property unreliable. (#4826) + ( CKEDITOR.env.webkit ? holdCtrlKey : ( CKEDITOR.env.mac ? domEvent.$.metaKey : domEvent.$.ctrlKey ) ) ) + return; + + + // Cancel the browser context menu. + domEvent.preventDefault(); + + var offsetParent = domEvent.getTarget().getDocument().getDocumentElement(), + offsetX = domEvent.$.clientX, + offsetY = domEvent.$.clientY; + + CKEDITOR.tools.setTimeout( function() + { + this.show( offsetParent, null, offsetX, offsetY ); + }, + 0, this ); + }, + this ); + + if ( CKEDITOR.env.webkit ) + { + var holdCtrlKey, + onKeyDown = function( event ) + { + holdCtrlKey = CKEDITOR.env.mac ? event.data.$.metaKey : event.data.$.ctrlKey ; + }, + resetOnKeyUp = function() + { + holdCtrlKey = 0; + }; + + element.on( 'keydown', onKeyDown ); + element.on( 'keyup', resetOnKeyUp ); + element.on( 'contextmenu', resetOnKeyUp ); + } + }, + + addListener : function( listenerFn ) + { + this._.listeners.push( listenerFn ); + }, + + show : function( offsetParent, corner, offsetX, offsetY ) + { + this.editor.focus(); + + // Selection will be unavailable after context menu shows up + // in IE, lock it now. + if ( CKEDITOR.env.ie ) + { + var selection = this.editor.getSelection(); + selection && selection.lock(); + } + + this._.onMenu( offsetParent || CKEDITOR.document.getDocumentElement(), corner, offsetX || 0, offsetY || 0 ); + } + } +}); + +/** + * Whether to show the browser native context menu when the CTRL or the + * META (Mac) key is pressed while opening the context menu. + * @name CKEDITOR.config.browserContextMenuOnCtrl + * @since 3.0.2 + * @type Boolean + * @default true + * @example + * config.browserContextMenuOnCtrl = false; + */ diff --git a/rte/_source/plugins/dialog/dialogDefinition.js b/rte/_source/plugins/dialog/dialogDefinition.js new file mode 100644 index 0000000..9dd252f --- /dev/null +++ b/rte/_source/plugins/dialog/dialogDefinition.js @@ -0,0 +1,315 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview Defines the "virtual" dialog, dialog content and dialog button + * definition classes. + */ + +/** + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create dialogs. + * @name CKEDITOR.dialog.dialogDefinition + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + * + * CKEDITOR.dialog.add( 'testOnly', function( editor ) + * { + * return { + * title : 'Test Dialog', + * resizable : CKEDITOR.DIALOG_RESIZE_BOTH, + * minWidth : 500, + * minHeight : 400, + * contents : [ + * { + * id : 'tab1', + * label : 'First Tab', + * title : 'First Tab Title', + * accessKey : 'Q', + * elements : [ + * { + * type : 'text', + * label : 'Test Text 1', + * id : 'testText1', + * 'default' : 'hello world!' + * } + * ] + * } + * ] + * }; + * }); + */ + +/** + * The dialog title, displayed in the dialog's header. Required. + * @name CKEDITOR.dialog.dialogDefinition.prototype.title + * @field + * @type String + * @example + */ + +/** + * How the dialog can be resized, must be one of the four contents defined below. + *

        + * CKEDITOR.DIALOG_RESIZE_NONE
        + * CKEDITOR.DIALOG_RESIZE_WIDTH
        + * CKEDITOR.DIALOG_RESIZE_HEIGHT
        + * CKEDITOR.DIALOG_RESIZE_BOTH
        + * @name CKEDITOR.dialog.dialogDefinition.prototype.resizable + * @field + * @type Number + * @default CKEDITOR.DIALOG_RESIZE_NONE + * @example + */ + +/** + * The minimum width of the dialog, in pixels. + * @name CKEDITOR.dialog.dialogDefinition.prototype.minWidth + * @field + * @type Number + * @default 600 + * @example + */ + +/** + * The minimum height of the dialog, in pixels. + * @name CKEDITOR.dialog.dialogDefinition.prototype.minHeight + * @field + * @type Number + * @default 400 + * @example + */ + +/** + * The buttons in the dialog, defined as an array of + * {@link CKEDITOR.dialog.buttonDefinition} objects. + * @name CKEDITOR.dialog.dialogDefinition.prototype.buttons + * @field + * @type Array + * @default [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ] + * @example + */ + +/** + * The contents in the dialog, defined as an array of + * {@link CKEDITOR.dialog.contentDefinition} objects. Required. + * @name CKEDITOR.dialog.dialogDefinition.prototype.contents + * @field + * @type Array + * @example + */ + +/** + * The function to execute when OK is pressed. + * @name CKEDITOR.dialog.dialogDefinition.prototype.onOk + * @field + * @type Function + * @example + */ + +/** + * The function to execute when Cancel is pressed. + * @name CKEDITOR.dialog.dialogDefinition.prototype.onCancel + * @field + * @type Function + * @example + */ + +/** + * The function to execute when the dialog is displayed for the first time. + * @name CKEDITOR.dialog.dialogDefinition.prototype.onLoad + * @field + * @type Function + * @example + */ + +/** + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create dialog content pages. + * @name CKEDITOR.dialog.contentDefinition + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + */ + +/** + * The id of the content page. + * @name CKEDITOR.dialog.contentDefinition.prototype.id + * @field + * @type String + * @example + */ + +/** + * The tab label of the content page. + * @name CKEDITOR.dialog.contentDefinition.prototype.label + * @field + * @type String + * @example + */ + +/** + * The popup message of the tab label. + * @name CKEDITOR.dialog.contentDefinition.prototype.title + * @field + * @type String + * @example + */ + +/** + * The CTRL hotkey for switching to the tab. + * @name CKEDITOR.dialog.contentDefinition.prototype.accessKey + * @field + * @type String + * @example + * contentDefinition.accessKey = 'Q'; // Switch to this page when CTRL-Q is pressed. + */ + +/** + * The UI elements contained in this content page, defined as an array of + * {@link CKEDITOR.dialog.uiElementDefinition} objects. + * @name CKEDITOR.dialog.contentDefinition.prototype.elements + * @field + * @type Array + * @example + */ + +/** + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create dialog buttons. + * @name CKEDITOR.dialog.buttonDefinition + * @constructor + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + */ + +/** + * The id of the dialog button. Required. + * @name CKEDITOR.dialog.buttonDefinition.prototype.id + * @type String + * @field + * @example + */ + +/** + * The label of the dialog button. Required. + * @name CKEDITOR.dialog.buttonDefinition.prototype.label + * @type String + * @field + * @example + */ + +/** + * The popup message of the dialog button. + * @name CKEDITOR.dialog.buttonDefinition.prototype.title + * @type String + * @field + * @example + */ + +/** + * The CTRL hotkey for the button. + * @name CKEDITOR.dialog.buttonDefinition.prototype.accessKey + * @type String + * @field + * @example + * exitButton.accessKey = 'X'; // Button will be pressed when user presses CTRL-X + */ + +/** + * Whether the button is disabled. + * @name CKEDITOR.dialog.buttonDefinition.prototype.disabled + * @type Boolean + * @field + * @default false + * @example + */ + +/** + * The function to execute when the button is clicked. + * @name CKEDITOR.dialog.buttonDefinition.prototype.onClick + * @type Function + * @field + * @example + */ + +/** + * This class is not really part of the API. It just illustrates the properties + * that developers can use to define and create dialog UI elements. + * @name CKEDITOR.dialog.uiElementDefinition + * @constructor + * @see CKEDITOR.ui.dialog.uiElement + * @example + * // There is no constructor for this class, the user just has to define an + * // object with the appropriate properties. + */ + +/** + * The id of the UI element. + * @name CKEDITOR.dialog.uiElementDefinition.prototype.id + * @field + * @type String + * @example + */ + +/** + * The type of the UI element. Required. + * @name CKEDITOR.dialog.uiElementDefinition.prototype.type + * @field + * @type String + * @example + */ + +/** + * The popup label of the UI element. + * @name CKEDITOR.dialog.uiElementDefinition.prototype.title + * @field + * @type String + * @example + */ + +/** + * CSS class names to append to the UI element. + * @name CKEDITOR.dialog.uiElementDefinition.prototype.className + * @field + * @type String + * @example + */ + +/** + * Inline CSS classes to append to the UI element. + * @name CKEDITOR.dialog.uiElementDefinition.prototype.style + * @field + * @type String + * @example + */ + +/** + * Function to execute the first time the UI element is displayed. + * @name CKEDITOR.dialog.uiElementDefinition.prototype.onLoad + * @field + * @type Function + * @example + */ + +/** + * Function to execute whenever the UI element's parent dialog is displayed. + * @name CKEDITOR.dialog.uiElementDefinition.prototype.onShow + * @field + * @type Function + * @example + */ + +/** + * Function to execute whenever the UI element's parent dialog is closed. + * @name CKEDITOR.dialog.uiElementDefinition.prototype.onHide + * @field + * @type Function + * @example + */ diff --git a/rte/_source/plugins/dialog/plugin.js b/rte/_source/plugins/dialog/plugin.js new file mode 100644 index 0000000..8f48a2e --- /dev/null +++ b/rte/_source/plugins/dialog/plugin.js @@ -0,0 +1,2950 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** + * @fileOverview The floating dialog plugin. + */ + +/** + * No resize for this dialog. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_NONE = 0; + +/** + * Only allow horizontal resizing for this dialog, disable vertical resizing. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_WIDTH = 1; + +/** + * Only allow vertical resizing for this dialog, disable horizontal resizing. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_HEIGHT = 2; + +/* + * Allow the dialog to be resized in both directions. + * @constant + */ +CKEDITOR.DIALOG_RESIZE_BOTH = 3; + +(function() +{ + function isTabVisible( tabId ) + { + return !!this._.tabs[ tabId ][ 0 ].$.offsetHeight; + } + + function getPreviousVisibleTab() + { + var tabId = this._.currentTabId, + length = this._.tabIdList.length, + tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ) + length; + + for ( var i = tabIndex - 1 ; i > tabIndex - length ; i-- ) + { + if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) ) + return this._.tabIdList[ i % length ]; + } + + return null; + } + + function getNextVisibleTab() + { + var tabId = this._.currentTabId, + length = this._.tabIdList.length, + tabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, tabId ); + + for ( var i = tabIndex + 1 ; i < tabIndex + length ; i++ ) + { + if ( isTabVisible.call( this, this._.tabIdList[ i % length ] ) ) + return this._.tabIdList[ i % length ]; + } + + return null; + } + + /** + * This is the base class for runtime dialog objects. An instance of this + * class represents a single named dialog for a single editor instance. + * @param {Object} editor The editor which created the dialog. + * @param {String} dialogName The dialog's registered name. + * @constructor + * @example + * var dialogObj = new CKEDITOR.dialog( editor, 'smiley' ); + */ + CKEDITOR.dialog = function( editor, dialogName ) + { + // Load the dialog definition. + var definition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ]; + + // Completes the definition with the default values. + definition = CKEDITOR.tools.extend( definition( editor ), defaultDialogDefinition ); + + // Clone a functionally independent copy for this dialog. + definition = CKEDITOR.tools.clone( definition ); + + // Create a complex definition object, extending it with the API + // functions. + definition = new definitionObject( this, definition ); + + + var doc = CKEDITOR.document; + + var themeBuilt = editor.theme.buildDialog( editor ); + + // Initialize some basic parameters. + this._ = + { + editor : editor, + element : themeBuilt.element, + name : dialogName, + contentSize : { width : 0, height : 0 }, + size : { width : 0, height : 0 }, + updateSize : false, + contents : {}, + buttons : {}, + accessKeyMap : {}, + + // Initialize the tab and page map. + tabs : {}, + tabIdList : [], + currentTabId : null, + currentTabIndex : null, + pageCount : 0, + lastTab : null, + tabBarMode : false, + + // Initialize the tab order array for input widgets. + focusList : [], + currentFocusIndex : 0, + hasFocus : false + }; + + this.parts = themeBuilt.parts; + + CKEDITOR.tools.setTimeout( function() + { + editor.fire( 'ariaWidget', this.parts.contents ); + }, + 0, this ); + + // Set the startup styles for the dialog, avoiding it enlarging the + // page size on the dialog creation. + this.parts.dialog.setStyles( + { + position : CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed', + top : 0, + left: 0, + visibility : 'hidden' + }); + + // Call the CKEDITOR.event constructor to initialize this instance. + CKEDITOR.event.call( this ); + + // Fire the "dialogDefinition" event, making it possible to customize + // the dialog definition. + this.definition = definition = CKEDITOR.fire( 'dialogDefinition', + { + name : dialogName, + definition : definition + } + , editor ).definition; + // Initialize load, show, hide, ok and cancel events. + if ( definition.onLoad ) + this.on( 'load', definition.onLoad ); + + if ( definition.onShow ) + this.on( 'show', definition.onShow ); + + if ( definition.onHide ) + this.on( 'hide', definition.onHide ); + + if ( definition.onOk ) + { + this.on( 'ok', function( evt ) + { + if ( definition.onOk.call( this, evt ) === false ) + evt.data.hide = false; + }); + } + + if ( definition.onCancel ) + { + this.on( 'cancel', function( evt ) + { + if ( definition.onCancel.call( this, evt ) === false ) + evt.data.hide = false; + }); + } + + var me = this; + + // Iterates over all items inside all content in the dialog, calling a + // function for each of them. + var iterContents = function( func ) + { + var contents = me._.contents, + stop = false; + + for ( var i in contents ) + { + for ( var j in contents[i] ) + { + stop = func.call( this, contents[i][j] ); + if ( stop ) + return; + } + } + }; + + this.on( 'ok', function( evt ) + { + iterContents( function( item ) + { + if ( item.validate ) + { + var isValid = item.validate( this ); + + if ( typeof isValid == 'string' ) + { + alert( isValid ); + isValid = false; + } + + if ( isValid === false ) + { + if ( item.select ) + item.select(); + else + item.focus(); + + evt.data.hide = false; + evt.stop(); + return true; + } + } + }); + }, this, null, 0 ); + + this.on( 'cancel', function( evt ) + { + iterContents( function( item ) + { + if ( item.isChanged() ) + { + if ( !confirm( editor.lang.common.confirmCancel ) ) + evt.data.hide = false; + return true; + } + }); + }, this, null, 0 ); + + this.parts.close.on( 'click', function( evt ) + { + if ( this.fire( 'cancel', { hide : true } ).hide !== false ) + this.hide(); + evt.data.preventDefault(); + }, this ); + + // Sort focus list according to tab order definitions. + function setupFocus() + { + var focusList = me._.focusList; + focusList.sort( function( a, b ) + { + // Mimics browser tab order logics; + if ( a.tabIndex != b.tabIndex ) + return b.tabIndex - a.tabIndex; + // Sort is not stable in some browsers, + // fall-back the comparator to 'focusIndex'; + else + return a.focusIndex - b.focusIndex; + }); + + var size = focusList.length; + for ( var i = 0; i < size; i++ ) + focusList[ i ].focusIndex = i; + } + + function changeFocus( forward ) + { + var focusList = me._.focusList, + offset = forward ? 1 : -1; + if ( focusList.length < 1 ) + return; + + var current = me._.currentFocusIndex; + + // Trigger the 'blur' event of any input element before anything, + // since certain UI updates may depend on it. + try + { + focusList[ current ].getInputElement().$.blur(); + } + catch( e ){} + + var startIndex = ( current + offset + focusList.length ) % focusList.length, + currentIndex = startIndex; + while ( !focusList[ currentIndex ].isFocusable() ) + { + currentIndex = ( currentIndex + offset + focusList.length ) % focusList.length; + if ( currentIndex == startIndex ) + break; + } + focusList[ currentIndex ].focus(); + + // Select whole field content. + if ( focusList[ currentIndex ].type == 'text' ) + focusList[ currentIndex ].select(); + } + + this.changeFocus = changeFocus; + + var processed; + + function focusKeydownHandler( evt ) + { + // If I'm not the top dialog, ignore. + if ( me != CKEDITOR.dialog._.currentTop ) + return; + + var keystroke = evt.data.getKeystroke(), + rtl = editor.lang.dir == 'rtl'; + + processed = 0; + if ( keystroke == 9 || keystroke == CKEDITOR.SHIFT + 9 ) + { + var shiftPressed = ( keystroke == CKEDITOR.SHIFT + 9 ); + + // Handling Tab and Shift-Tab. + if ( me._.tabBarMode ) + { + // Change tabs. + var nextId = shiftPressed ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ); + me.selectPage( nextId ); + me._.tabs[ nextId ][ 0 ].focus(); + } + else + { + // Change the focus of inputs. + changeFocus( !shiftPressed ); + } + + processed = 1; + } + else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode && me.getPageCount() > 1 ) + { + // Alt-F10 puts focus into the current tab item in the tab bar. + me._.tabBarMode = true; + me._.tabs[ me._.currentTabId ][ 0 ].focus(); + processed = 1; + } + else if ( ( keystroke == 37 || keystroke == 39 ) && me._.tabBarMode ) + { + // Arrow keys - used for changing tabs. + nextId = ( keystroke == ( rtl ? 39 : 37 ) ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ) ); + me.selectPage( nextId ); + me._.tabs[ nextId ][ 0 ].focus(); + processed = 1; + } + else if ( ( keystroke == 13 || keystroke == 32 ) && me._.tabBarMode ) + { + this.selectPage( this._.currentTabId ); + this._.tabBarMode = false; + this._.currentFocusIndex = -1; + changeFocus( true ); + processed = 1; + } + + if ( processed ) + { + evt.stop(); + evt.data.preventDefault(); + } + } + + function focusKeyPressHandler( evt ) + { + processed && evt.data.preventDefault(); + } + + var dialogElement = this._.element; + // Add the dialog keyboard handlers. + this.on( 'show', function() + { + dialogElement.on( 'keydown', focusKeydownHandler, this, null, 0 ); + // Some browsers instead, don't cancel key events in the keydown, but in the + // keypress. So we must do a longer trip in those cases. (#4531) + if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) + dialogElement.on( 'keypress', focusKeyPressHandler, this ); + + } ); + this.on( 'hide', function() + { + dialogElement.removeListener( 'keydown', focusKeydownHandler ); + if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) + dialogElement.removeListener( 'keypress', focusKeyPressHandler ); + } ); + this.on( 'iframeAdded', function( evt ) + { + var doc = new CKEDITOR.dom.document( evt.data.iframe.$.contentWindow.document ); + doc.on( 'keydown', focusKeydownHandler, this, null, 0 ); + } ); + + // Auto-focus logic in dialog. + this.on( 'show', function() + { + // Setup tabIndex on showing the dialog instead of on loading + // to allow dynamic tab order happen in dialog definition. + setupFocus(); + + if ( editor.config.dialog_startupFocusTab + && me._.tabIdList.length > 1 ) + { + me._.tabBarMode = true; + me._.tabs[ me._.currentTabId ][ 0 ].focus(); + } + else if ( !this._.hasFocus ) + { + this._.currentFocusIndex = -1; + + // Decide where to put the initial focus. + if ( definition.onFocus ) + { + var initialFocus = definition.onFocus.call( this ); + // Focus the field that the user specified. + initialFocus && initialFocus.focus(); + } + // Focus the first field in layout order. + else + changeFocus( true ); + + /* + * IE BUG: If the initial focus went into a non-text element (e.g. button), + * then IE would still leave the caret inside the editing area. + */ + if ( this._.editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) + { + var $selection = editor.document.$.selection, + $range = $selection.createRange(); + + if ( $range ) + { + if ( $range.parentElement && $range.parentElement().ownerDocument == editor.document.$ + || $range.item && $range.item( 0 ).ownerDocument == editor.document.$ ) + { + var $myRange = document.body.createTextRange(); + $myRange.moveToElementText( this.getElement().getFirst().$ ); + $myRange.collapse( true ); + $myRange.select(); + } + } + } + } + }, this, null, 0xffffffff ); + + // IE6 BUG: Text fields and text areas are only half-rendered the first time the dialog appears in IE6 (#2661). + // This is still needed after [2708] and [2709] because text fields in hidden TR tags are still broken. + if ( CKEDITOR.env.ie6Compat ) + { + this.on( 'load', function( evt ) + { + var outer = this.getElement(), + inner = outer.getFirst(); + inner.remove(); + inner.appendTo( outer ); + }, this ); + } + + initDragAndDrop( this ); + initResizeHandles( this ); + + // Insert the title. + ( new CKEDITOR.dom.text( definition.title, CKEDITOR.document ) ).appendTo( this.parts.title ); + + // Insert the tabs and contents. + for ( var i = 0 ; i < definition.contents.length ; i++ ) + this.addPage( definition.contents[i] ); + + this.parts['tabs'].on( 'click', function( evt ) + { + var target = evt.data.getTarget(); + // If we aren't inside a tab, bail out. + if ( target.hasClass( 'cke_dialog_tab' ) ) + { + var id = target.$.id; + this.selectPage( id.substr( 0, id.lastIndexOf( '_' ) ) ); + if ( this._.tabBarMode ) + { + this._.tabBarMode = false; + this._.currentFocusIndex = -1; + changeFocus( true ); + } + evt.data.preventDefault(); + } + }, this ); + + // Insert buttons. + var buttonsHtml = [], + buttons = CKEDITOR.dialog._.uiElementBuilders.hbox.build( this, + { + type : 'hbox', + className : 'cke_dialog_footer_buttons', + widths : [], + children : definition.buttons + }, buttonsHtml ).getChild(); + this.parts.footer.setHtml( buttonsHtml.join( '' ) ); + + for ( i = 0 ; i < buttons.length ; i++ ) + this._.buttons[ buttons[i].id ] = buttons[i]; + }; + + // Focusable interface. Use it via dialog.addFocusable. + function Focusable( dialog, element, index ) + { + this.element = element; + this.focusIndex = index; + // TODO: support tabIndex for focusables. + this.tabIndex = 0; + this.isFocusable = function() + { + return !element.getAttribute( 'disabled' ) && element.isVisible(); + }; + this.focus = function() + { + dialog._.currentFocusIndex = this.focusIndex; + this.element.focus(); + }; + // Bind events + element.on( 'keydown', function( e ) + { + if ( e.data.getKeystroke() in { 32:1, 13:1 } ) + this.fire( 'click' ); + } ); + element.on( 'focus', function() + { + this.fire( 'mouseover' ); + } ); + element.on( 'blur', function() + { + this.fire( 'mouseout' ); + } ); + } + + CKEDITOR.dialog.prototype = + { + destroy : function() + { + this.hide(); + this._.element.remove(); + }, + + /** + * Resizes the dialog. + * @param {Number} width The width of the dialog in pixels. + * @param {Number} height The height of the dialog in pixels. + * @function + * @example + * dialogObj.resize( 800, 640 ); + */ + resize : (function() + { + return function( width, height ) + { + if ( this._.contentSize && this._.contentSize.width == width && this._.contentSize.height == height ) + return; + + CKEDITOR.dialog.fire( 'resize', + { + dialog : this, + skin : this._.editor.skinName, + width : width, + height : height + }, this._.editor ); + + this._.contentSize = { width : width, height : height }; + this._.updateSize = true; + }; + })(), + + /** + * Gets the current size of the dialog in pixels. + * @returns {Object} An object with "width" and "height" properties. + * @example + * var width = dialogObj.getSize().width; + */ + getSize : function() + { + if ( !this._.updateSize ) + return this._.size; + var element = this._.element.getFirst(); + var size = this._.size = { width : element.$.offsetWidth || 0, height : element.$.offsetHeight || 0}; + + // If either the offsetWidth or offsetHeight is 0, the element isn't visible. + this._.updateSize = !size.width || !size.height; + + return size; + }, + + /** + * Moves the dialog to an (x, y) coordinate relative to the window. + * @function + * @param {Number} x The target x-coordinate. + * @param {Number} y The target y-coordinate. + * @example + * dialogObj.move( 10, 40 ); + */ + move : (function() + { + var isFixed; + return function( x, y ) + { + // The dialog may be fixed positioned or absolute positioned. Ask the + // browser what is the current situation first. + var element = this._.element.getFirst(); + if ( isFixed === undefined ) + isFixed = element.getComputedStyle( 'position' ) == 'fixed'; + + if ( isFixed && this._.position && this._.position.x == x && this._.position.y == y ) + return; + + // Save the current position. + this._.position = { x : x, y : y }; + + // If not fixed positioned, add scroll position to the coordinates. + if ( !isFixed ) + { + var scrollPosition = CKEDITOR.document.getWindow().getScrollPosition(); + x += scrollPosition.x; + y += scrollPosition.y; + } + + element.setStyles( + { + 'left' : ( x > 0 ? x : 0 ) + 'px', + 'top' : ( y > 0 ? y : 0 ) + 'px' + }); + }; + })(), + + /** + * Gets the dialog's position in the window. + * @returns {Object} An object with "x" and "y" properties. + * @example + * var dialogX = dialogObj.getPosition().x; + */ + getPosition : function(){ return CKEDITOR.tools.extend( {}, this._.position ); }, + + /** + * Shows the dialog box. + * @example + * dialogObj.show(); + */ + show : function() + { + var editor = this._.editor; + if ( editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) + { + var selection = editor.getSelection(); + selection && selection.lock(); + } + + // Insert the dialog's element to the root document. + var element = this._.element; + var definition = this.definition; + if ( !( element.getParent() && element.getParent().equals( CKEDITOR.document.getBody() ) ) ) + element.appendTo( CKEDITOR.document.getBody() ); + else + element.setStyle( 'display', 'block' ); + + // FIREFOX BUG: Fix vanishing caret for Firefox 2 or Gecko 1.8. + if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) + { + var dialogElement = this.parts.dialog; + dialogElement.setStyle( 'position', 'absolute' ); + setTimeout( function() + { + dialogElement.setStyle( 'position', 'fixed' ); + }, 0 ); + } + + + // First, set the dialog to an appropriate size. + this.resize( definition.minWidth, definition.minHeight ); + + // Select the first tab by default. + this.selectPage( this.definition.contents[0].id ); + + // Reset all inputs back to their default value. + this.reset(); + + // Set z-index. + if ( CKEDITOR.dialog._.currentZIndex === null ) + CKEDITOR.dialog._.currentZIndex = this._.editor.config.baseFloatZIndex; + this._.element.getFirst().setStyle( 'z-index', CKEDITOR.dialog._.currentZIndex += 10 ); + + // Maintain the dialog ordering and dialog cover. + // Also register key handlers if first dialog. + if ( CKEDITOR.dialog._.currentTop === null ) + { + CKEDITOR.dialog._.currentTop = this; + this._.parentDialog = null; + showCover( this._.editor ); + + element.on( 'keydown', accessKeyDownHandler ); + element.on( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler ); + + // Prevent some keys from bubbling up. (#4269) + for ( var event in { keyup :1, keydown :1, keypress :1 } ) + element.on( event, preventKeyBubbling ); + } + else + { + this._.parentDialog = CKEDITOR.dialog._.currentTop; + var parentElement = this._.parentDialog.getElement().getFirst(); + parentElement.$.style.zIndex -= Math.floor( this._.editor.config.baseFloatZIndex / 2 ); + CKEDITOR.dialog._.currentTop = this; + } + + // Register the Esc hotkeys. + registerAccessKey( this, this, '\x1b', null, function() + { + this.getButton( 'cancel' ) && this.getButton( 'cancel' ).click(); + } ); + + // Reset the hasFocus state. + this._.hasFocus = false; + + // Rearrange the dialog to the middle of the window. + CKEDITOR.tools.setTimeout( function() + { + var viewSize = CKEDITOR.document.getWindow().getViewPaneSize(); + var dialogSize = this.getSize(); + + // We're using definition size for initial position because of + // offten corrupted data in offsetWidth at this point. (#4084) + this.move( ( viewSize.width - definition.minWidth ) / 2, ( viewSize.height - dialogSize.height ) / 2 ); + + this.parts.dialog.setStyle( 'visibility', '' ); + + // Execute onLoad for the first show. + this.fireOnce( 'load', {} ); + this.fire( 'show', {} ); + this._.editor.fire( 'dialogShow', this ); + + // Save the initial values of the dialog. + this.foreach( function( contentObj ) { contentObj.setInitValue && contentObj.setInitValue(); } ); + + }, + 100, this ); + }, + + /** + * Executes a function for each UI element. + * @param {Function} fn Function to execute for each UI element. + * @returns {CKEDITOR.dialog} The current dialog object. + */ + foreach : function( fn ) + { + for ( var i in this._.contents ) + { + for ( var j in this._.contents[i] ) + fn( this._.contents[i][j]); + } + return this; + }, + + /** + * Resets all input values in the dialog. + * @example + * dialogObj.reset(); + * @returns {CKEDITOR.dialog} The current dialog object. + */ + reset : (function() + { + var fn = function( widget ){ if ( widget.reset ) widget.reset(); }; + return function(){ this.foreach( fn ); return this; }; + })(), + + setupContent : function() + { + var args = arguments; + this.foreach( function( widget ) + { + if ( widget.setup ) + widget.setup.apply( widget, args ); + }); + }, + + commitContent : function() + { + var args = arguments; + this.foreach( function( widget ) + { + if ( widget.commit ) + widget.commit.apply( widget, args ); + }); + }, + + /** + * Hides the dialog box. + * @example + * dialogObj.hide(); + */ + hide : function() + { + if ( !this.parts.dialog.isVisible() ) + return; + + this.fire( 'hide', {} ); + this._.editor.fire( 'dialogHide', this ); + var element = this._.element; + element.setStyle( 'display', 'none' ); + this.parts.dialog.setStyle( 'visibility', 'hidden' ); + // Unregister all access keys associated with this dialog. + unregisterAccessKey( this ); + + // Close any child(top) dialogs first. + while( CKEDITOR.dialog._.currentTop != this ) + CKEDITOR.dialog._.currentTop.hide(); + + // Maintain dialog ordering and remove cover if needed. + if ( !this._.parentDialog ) + hideCover(); + else + { + var parentElement = this._.parentDialog.getElement().getFirst(); + parentElement.setStyle( 'z-index', parseInt( parentElement.$.style.zIndex, 10 ) + Math.floor( this._.editor.config.baseFloatZIndex / 2 ) ); + } + CKEDITOR.dialog._.currentTop = this._.parentDialog; + + // Deduct or clear the z-index. + if ( !this._.parentDialog ) + { + CKEDITOR.dialog._.currentZIndex = null; + + // Remove access key handlers. + element.removeListener( 'keydown', accessKeyDownHandler ); + element.removeListener( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler ); + + // Remove bubbling-prevention handler. (#4269) + for ( var event in { keyup :1, keydown :1, keypress :1 } ) + element.removeListener( event, preventKeyBubbling ); + + var editor = this._.editor; + editor.focus(); + + if ( editor.mode == 'wysiwyg' && CKEDITOR.env.ie ) + { + var selection = editor.getSelection(); + selection && selection.unlock( true ); + } + } + else + CKEDITOR.dialog._.currentZIndex -= 10; + + delete this._.parentDialog; + // Reset the initial values of the dialog. + this.foreach( function( contentObj ) { contentObj.resetInitValue && contentObj.resetInitValue(); } ); + }, + + /** + * Adds a tabbed page into the dialog. + * @param {Object} contents Content definition. + * @example + */ + addPage : function( contents ) + { + var pageHtml = [], + titleHtml = contents.label ? ' title="' + CKEDITOR.tools.htmlEncode( contents.label ) + '"' : '', + elements = contents.elements, + vbox = CKEDITOR.dialog._.uiElementBuilders.vbox.build( this, + { + type : 'vbox', + className : 'cke_dialog_page_contents', + children : contents.elements, + expand : !!contents.expand, + padding : contents.padding, + style : contents.style || 'width: 100%; height: 100%;' + }, pageHtml ); + + // Create the HTML for the tab and the content block. + var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) ); + page.setAttribute( 'role', 'tabpanel' ); + + var env = CKEDITOR.env; + var tabId = contents.id + '_' + CKEDITOR.tools.getNextNumber(), + tab = CKEDITOR.dom.element.createFromHtml( [ + ' 0 ? ' cke_last' : 'cke_first' ), + titleHtml, + ( !!contents.hidden ? ' style="display:none"' : '' ), + ' id="', tabId, '"', + env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(0)"', + ' tabIndex="-1"', + ' hidefocus="true"', + ' role="tab">', + contents.label, + '' + ].join( '' ) ); + + page.setAttribute( 'aria-labelledby', tabId ); + + // Take records for the tabs and elements created. + this._.tabs[ contents.id ] = [ tab, page ]; + this._.tabIdList.push( contents.id ); + !contents.hidden && this._.pageCount++; + this._.lastTab = tab; + this.updateStyle(); + + var contentMap = this._.contents[ contents.id ] = {}, + cursor, + children = vbox.getChild(); + + while ( ( cursor = children.shift() ) ) + { + contentMap[ cursor.id ] = cursor; + if ( typeof( cursor.getChild ) == 'function' ) + children.push.apply( children, cursor.getChild() ); + } + + // Attach the DOM nodes. + + page.setAttribute( 'name', contents.id ); + page.appendTo( this.parts.contents ); + + tab.unselectable(); + this.parts.tabs.append( tab ); + + // Add access key handlers if access key is defined. + if ( contents.accessKey ) + { + registerAccessKey( this, this, 'CTRL+' + contents.accessKey, + tabAccessKeyDown, tabAccessKeyUp ); + this._.accessKeyMap[ 'CTRL+' + contents.accessKey ] = contents.id; + } + }, + + /** + * Activates a tab page in the dialog by its id. + * @param {String} id The id of the dialog tab to be activated. + * @example + * dialogObj.selectPage( 'tab_1' ); + */ + selectPage : function( id ) + { + // Hide the non-selected tabs and pages. + for ( var i in this._.tabs ) + { + var tab = this._.tabs[i][0], + page = this._.tabs[i][1]; + if ( i != id ) + { + tab.removeClass( 'cke_dialog_tab_selected' ); + page.hide(); + } + page.setAttribute( 'aria-hidden', i != id ); + } + + var selected = this._.tabs[id]; + selected[0].addClass( 'cke_dialog_tab_selected' ); + selected[1].show(); + this._.currentTabId = id; + this._.currentTabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, id ); + }, + + // Dialog state-specific style updates. + updateStyle : function() + { + // If only a single page shown, a different style is used in the central pane. + this.parts.dialog[ ( this._.pageCount === 1 ? 'add' : 'remove' ) + 'Class' ]( 'cke_single_page' ); + }, + + /** + * Hides a page's tab away from the dialog. + * @param {String} id The page's Id. + * @example + * dialog.hidePage( 'tab_3' ); + */ + hidePage : function( id ) + { + var tab = this._.tabs[id] && this._.tabs[id][0]; + if ( !tab || this._.pageCount == 1 ) + return; + // Switch to other tab first when we're hiding the active tab. + else if ( id == this._.currentTabId ) + this.selectPage( getPreviousVisibleTab.call( this ) ); + + tab.hide(); + this._.pageCount--; + this.updateStyle(); + }, + + /** + * Unhides a page's tab. + * @param {String} id The page's Id. + * @example + * dialog.showPage( 'tab_2' ); + */ + showPage : function( id ) + { + var tab = this._.tabs[id] && this._.tabs[id][0]; + if ( !tab ) + return; + tab.show(); + this._.pageCount++; + this.updateStyle(); + }, + + /** + * Gets the root DOM element of the dialog. + * @returns {CKEDITOR.dom.element} The <span> element containing this dialog. + * @example + * var dialogElement = dialogObj.getElement().getFirst(); + * dialogElement.setStyle( 'padding', '5px' ); + */ + getElement : function() + { + return this._.element; + }, + + /** + * Gets the name of the dialog. + * @returns {String} The name of this dialog. + * @example + * var dialogName = dialogObj.getName(); + */ + getName : function() + { + return this._.name; + }, + + /** + * Gets a dialog UI element object from a dialog page. + * @param {String} pageId id of dialog page. + * @param {String} elementId id of UI element. + * @example + * @returns {CKEDITOR.ui.dialog.uiElement} The dialog UI element. + */ + getContentElement : function( pageId, elementId ) + { + var page = this._.contents[ pageId ]; + return page && page[ elementId ]; + }, + + /** + * Gets the value of a dialog UI element. + * @param {String} pageId id of dialog page. + * @param {String} elementId id of UI element. + * @example + * @returns {Object} The value of the UI element. + */ + getValueOf : function( pageId, elementId ) + { + return this.getContentElement( pageId, elementId ).getValue(); + }, + + /** + * Sets the value of a dialog UI element. + * @param {String} pageId id of the dialog page. + * @param {String} elementId id of the UI element. + * @param {Object} value The new value of the UI element. + * @example + */ + setValueOf : function( pageId, elementId, value ) + { + return this.getContentElement( pageId, elementId ).setValue( value ); + }, + + /** + * Gets the UI element of a button in the dialog's button row. + * @param {String} id The id of the button. + * @example + * @returns {CKEDITOR.ui.dialog.button} The button object. + */ + getButton : function( id ) + { + return this._.buttons[ id ]; + }, + + /** + * Simulates a click to a dialog button in the dialog's button row. + * @param {String} id The id of the button. + * @example + * @returns The return value of the dialog's "click" event. + */ + click : function( id ) + { + return this._.buttons[ id ].click(); + }, + + /** + * Disables a dialog button. + * @param {String} id The id of the button. + * @example + */ + disableButton : function( id ) + { + return this._.buttons[ id ].disable(); + }, + + /** + * Enables a dialog button. + * @param {String} id The id of the button. + * @example + */ + enableButton : function( id ) + { + return this._.buttons[ id ].enable(); + }, + + /** + * Gets the number of pages in the dialog. + * @returns {Number} Page count. + */ + getPageCount : function() + { + return this._.pageCount; + }, + + /** + * Gets the editor instance which opened this dialog. + * @returns {CKEDITOR.editor} Parent editor instances. + */ + getParentEditor : function() + { + return this._.editor; + }, + + /** + * Gets the element that was selected when opening the dialog, if any. + * @returns {CKEDITOR.dom.element} The element that was selected, or null. + */ + getSelectedElement : function() + { + return this.getParentEditor().getSelection().getSelectedElement(); + }, + + /** + * Adds element to dialog's focusable list. + * + * @param {CKEDITOR.dom.element} element + * @param {Number} [index] + */ + addFocusable: function( element, index ) { + if ( typeof index == 'undefined' ) + { + index = this._.focusList.length; + this._.focusList.push( new Focusable( this, element, index ) ); + } + else + { + this._.focusList.splice( index, 0, new Focusable( this, element, index ) ); + for ( var i = index + 1 ; i < this._.focusList.length ; i++ ) + this._.focusList[ i ].focusIndex++; + } + } + }; + + CKEDITOR.tools.extend( CKEDITOR.dialog, + /** + * @lends CKEDITOR.dialog + */ + { + /** + * Registers a dialog. + * @param {String} name The dialog's name. + * @param {Function|String} dialogDefinition + * A function returning the dialog's definition, or the URL to the .js file holding the function. + * The function should accept an argument "editor" which is the current editor instance, and + * return an object conforming to {@link CKEDITOR.dialog.dialogDefinition}. + * @example + * @see CKEDITOR.dialog.dialogDefinition + */ + add : function( name, dialogDefinition ) + { + // Avoid path registration from multiple instances override definition. + if ( !this._.dialogDefinitions[name] + || typeof dialogDefinition == 'function' ) + this._.dialogDefinitions[name] = dialogDefinition; + }, + + exists : function( name ) + { + return !!this._.dialogDefinitions[ name ]; + }, + + getCurrent : function() + { + return CKEDITOR.dialog._.currentTop; + }, + + /** + * The default OK button for dialogs. Fires the "ok" event and closes the dialog if the event succeeds. + * @static + * @field + * @example + * @type Function + */ + okButton : (function() + { + var retval = function( editor, override ) + { + override = override || {}; + return CKEDITOR.tools.extend( { + id : 'ok', + type : 'button', + label : editor.lang.common.ok, + 'class' : 'cke_dialog_ui_button_ok', + onClick : function( evt ) + { + var dialog = evt.data.dialog; + if ( dialog.fire( 'ok', { hide : true } ).hide !== false ) + dialog.hide(); + } + }, override, true ); + }; + retval.type = 'button'; + retval.override = function( override ) + { + return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); }, + { type : 'button' }, true ); + }; + return retval; + })(), + + /** + * The default cancel button for dialogs. Fires the "cancel" event and closes the dialog if no UI element value changed. + * @static + * @field + * @example + * @type Function + */ + cancelButton : (function() + { + var retval = function( editor, override ) + { + override = override || {}; + return CKEDITOR.tools.extend( { + id : 'cancel', + type : 'button', + label : editor.lang.common.cancel, + 'class' : 'cke_dialog_ui_button_cancel', + onClick : function( evt ) + { + var dialog = evt.data.dialog; + if ( dialog.fire( 'cancel', { hide : true } ).hide !== false ) + dialog.hide(); + } + }, override, true ); + }; + retval.type = 'button'; + retval.override = function( override ) + { + return CKEDITOR.tools.extend( function( editor ){ return retval( editor, override ); }, + { type : 'button' }, true ); + }; + return retval; + })(), + + /** + * Registers a dialog UI element. + * @param {String} typeName The name of the UI element. + * @param {Function} builder The function to build the UI element. + * @example + */ + addUIElement : function( typeName, builder ) + { + this._.uiElementBuilders[ typeName ] = builder; + } + }); + + CKEDITOR.dialog._ = + { + uiElementBuilders : {}, + + dialogDefinitions : {}, + + currentTop : null, + + currentZIndex : null + }; + + // "Inherit" (copy actually) from CKEDITOR.event. + CKEDITOR.event.implementOn( CKEDITOR.dialog ); + CKEDITOR.event.implementOn( CKEDITOR.dialog.prototype, true ); + + var defaultDialogDefinition = + { + resizable : CKEDITOR.DIALOG_RESIZE_BOTH, + minWidth : 600, + minHeight : 400, + buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ] + }; + + // The buttons in MacOS Apps are in reverse order #4750 + CKEDITOR.env.mac && defaultDialogDefinition.buttons.reverse(); + + // Tool function used to return an item from an array based on its id + // property. + var getById = function( array, id, recurse ) + { + for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) + { + if ( item.id == id ) + return item; + if ( recurse && item[ recurse ] ) + { + var retval = getById( item[ recurse ], id, recurse ) ; + if ( retval ) + return retval; + } + } + return null; + }; + + // Tool function used to add an item into an array. + var addById = function( array, newItem, nextSiblingId, recurse, nullIfNotFound ) + { + if ( nextSiblingId ) + { + for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) + { + if ( item.id == nextSiblingId ) + { + array.splice( i, 0, newItem ); + return newItem; + } + + if ( recurse && item[ recurse ] ) + { + var retval = addById( item[ recurse ], newItem, nextSiblingId, recurse, true ); + if ( retval ) + return retval; + } + } + + if ( nullIfNotFound ) + return null; + } + + array.push( newItem ); + return newItem; + }; + + // Tool function used to remove an item from an array based on its id. + var removeById = function( array, id, recurse ) + { + for ( var i = 0, item ; ( item = array[ i ] ) ; i++ ) + { + if ( item.id == id ) + return array.splice( i, 1 ); + if ( recurse && item[ recurse ] ) + { + var retval = removeById( item[ recurse ], id, recurse ); + if ( retval ) + return retval; + } + } + return null; + }; + + /** + * This class is not really part of the API. It is the "definition" property value + * passed to "dialogDefinition" event handlers. + * @constructor + * @name CKEDITOR.dialog.dialogDefinitionObject + * @extends CKEDITOR.dialog.dialogDefinition + * @example + * CKEDITOR.on( 'dialogDefinition', function( evt ) + * { + * var definition = evt.data.definition; + * var content = definition.getContents( 'page1' ); + * ... + * } ); + */ + var definitionObject = function( dialog, dialogDefinition ) + { + // TODO : Check if needed. + this.dialog = dialog; + + // Transform the contents entries in contentObjects. + var contents = dialogDefinition.contents; + for ( var i = 0, content ; ( content = contents[i] ) ; i++ ) + contents[ i ] = new contentObject( dialog, content ); + + CKEDITOR.tools.extend( this, dialogDefinition ); + }; + + definitionObject.prototype = + /** @lends CKEDITOR.dialog.dialogDefinitionObject.prototype */ + { + /** + * Gets a content definition. + * @param {String} id The id of the content definition. + * @returns {CKEDITOR.dialog.contentDefinition} The content definition + * matching id. + */ + getContents : function( id ) + { + return getById( this.contents, id ); + }, + + /** + * Gets a button definition. + * @param {String} id The id of the button definition. + * @returns {CKEDITOR.dialog.buttonDefinition} The button definition + * matching id. + */ + getButton : function( id ) + { + return getById( this.buttons, id ); + }, + + /** + * Adds a content definition object under this dialog definition. + * @param {CKEDITOR.dialog.contentDefinition} contentDefinition The + * content definition. + * @param {String} [nextSiblingId] The id of an existing content + * definition which the new content definition will be inserted + * before. Omit if the new content definition is to be inserted as + * the last item. + * @returns {CKEDITOR.dialog.contentDefinition} The inserted content + * definition. + */ + addContents : function( contentDefinition, nextSiblingId ) + { + return addById( this.contents, contentDefinition, nextSiblingId ); + }, + + /** + * Adds a button definition object under this dialog definition. + * @param {CKEDITOR.dialog.buttonDefinition} buttonDefinition The + * button definition. + * @param {String} [nextSiblingId] The id of an existing button + * definition which the new button definition will be inserted + * before. Omit if the new button definition is to be inserted as + * the last item. + * @returns {CKEDITOR.dialog.buttonDefinition} The inserted button + * definition. + */ + addButton : function( buttonDefinition, nextSiblingId ) + { + return addById( this.buttons, buttonDefinition, nextSiblingId ); + }, + + /** + * Removes a content definition from this dialog definition. + * @param {String} id The id of the content definition to be removed. + * @returns {CKEDITOR.dialog.contentDefinition} The removed content + * definition. + */ + removeContents : function( id ) + { + removeById( this.contents, id ); + }, + + /** + * Removes a button definition from the dialog definition. + * @param {String} id The id of the button definition to be removed. + * @returns {CKEDITOR.dialog.buttonDefinition} The removed button + * definition. + */ + removeButton : function( id ) + { + removeById( this.buttons, id ); + } + }; + + /** + * This class is not really part of the API. It is the template of the + * objects representing content pages inside the + * CKEDITOR.dialog.dialogDefinitionObject. + * @constructor + * @name CKEDITOR.dialog.contentDefinitionObject + * @example + * CKEDITOR.on( 'dialogDefinition', function( evt ) + * { + * var definition = evt.data.definition; + * var content = definition.getContents( 'page1' ); + * content.remove( 'textInput1' ); + * ... + * } ); + */ + function contentObject( dialog, contentDefinition ) + { + this._ = + { + dialog : dialog + }; + + CKEDITOR.tools.extend( this, contentDefinition ); + } + + contentObject.prototype = + /** @lends CKEDITOR.dialog.contentDefinitionObject.prototype */ + { + /** + * Gets a UI element definition under the content definition. + * @param {String} id The id of the UI element definition. + * @returns {CKEDITOR.dialog.uiElementDefinition} + */ + get : function( id ) + { + return getById( this.elements, id, 'children' ); + }, + + /** + * Adds a UI element definition to the content definition. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition The + * UI elemnet definition to be added. + * @param {String} nextSiblingId The id of an existing UI element + * definition which the new UI element definition will be inserted + * before. Omit if the new button definition is to be inserted as + * the last item. + * @returns {CKEDITOR.dialog.uiElementDefinition} The element + * definition inserted. + */ + add : function( elementDefinition, nextSiblingId ) + { + return addById( this.elements, elementDefinition, nextSiblingId, 'children' ); + }, + + /** + * Removes a UI element definition from the content definition. + * @param {String} id The id of the UI element definition to be + * removed. + * @returns {CKEDITOR.dialog.uiElementDefinition} The element + * definition removed. + * @example + */ + remove : function( id ) + { + removeById( this.elements, id, 'children' ); + } + }; + + function initDragAndDrop( dialog ) + { + var lastCoords = null, + abstractDialogCoords = null, + element = dialog.getElement().getFirst(), + editor = dialog.getParentEditor(), + magnetDistance = editor.config.dialog_magnetDistance, + margins = editor.skin.margins || [ 0, 0, 0, 0 ]; + + if ( typeof magnetDistance == 'undefined' ) + magnetDistance = 20; + + function mouseMoveHandler( evt ) + { + var dialogSize = dialog.getSize(), + viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(), + x = evt.data.$.screenX, + y = evt.data.$.screenY, + dx = x - lastCoords.x, + dy = y - lastCoords.y, + realX, realY; + + lastCoords = { x : x, y : y }; + abstractDialogCoords.x += dx; + abstractDialogCoords.y += dy; + + if ( abstractDialogCoords.x + margins[3] < magnetDistance ) + realX = - margins[3]; + else if ( abstractDialogCoords.x - margins[1] > viewPaneSize.width - dialogSize.width - magnetDistance ) + realX = viewPaneSize.width - dialogSize.width + margins[1]; + else + realX = abstractDialogCoords.x; + + if ( abstractDialogCoords.y + margins[0] < magnetDistance ) + realY = - margins[0]; + else if ( abstractDialogCoords.y - margins[2] > viewPaneSize.height - dialogSize.height - magnetDistance ) + realY = viewPaneSize.height - dialogSize.height + margins[2]; + else + realY = abstractDialogCoords.y; + + dialog.move( realX, realY ); + + evt.data.preventDefault(); + } + + function mouseUpHandler( evt ) + { + CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); + CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.removeListener( 'mousemove', mouseMoveHandler ); + coverDoc.removeListener( 'mouseup', mouseUpHandler ); + } + } + + dialog.parts.title.on( 'mousedown', function( evt ) + { + dialog._.updateSize = true; + + lastCoords = { x : evt.data.$.screenX, y : evt.data.$.screenY }; + + CKEDITOR.document.on( 'mousemove', mouseMoveHandler ); + CKEDITOR.document.on( 'mouseup', mouseUpHandler ); + abstractDialogCoords = dialog.getPosition(); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.on( 'mousemove', mouseMoveHandler ); + coverDoc.on( 'mouseup', mouseUpHandler ); + } + + evt.data.preventDefault(); + }, dialog ); + } + + function initResizeHandles( dialog ) + { + var definition = dialog.definition, + minWidth = definition.minWidth || 0, + minHeight = definition.minHeight || 0, + resizable = definition.resizable, + margins = dialog.getParentEditor().skin.margins || [ 0, 0, 0, 0 ]; + + function topSizer( coords, dy ) + { + coords.y += dy; + } + + function rightSizer( coords, dx ) + { + coords.x2 += dx; + } + + function bottomSizer( coords, dy ) + { + coords.y2 += dy; + } + + function leftSizer( coords, dx ) + { + coords.x += dx; + } + + var lastCoords = null, + abstractDialogCoords = null, + magnetDistance = dialog._.editor.config.magnetDistance, + parts = [ 'tl', 't', 'tr', 'l', 'r', 'bl', 'b', 'br' ]; + + function mouseDownHandler( evt ) + { + var partName = evt.listenerData.part, size = dialog.getSize(); + abstractDialogCoords = dialog.getPosition(); + CKEDITOR.tools.extend( abstractDialogCoords, + { + x2 : abstractDialogCoords.x + size.width, + y2 : abstractDialogCoords.y + size.height + } ); + lastCoords = { x : evt.data.$.screenX, y : evt.data.$.screenY }; + + CKEDITOR.document.on( 'mousemove', mouseMoveHandler, dialog, { part : partName } ); + CKEDITOR.document.on( 'mouseup', mouseUpHandler, dialog, { part : partName } ); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.on( 'mousemove', mouseMoveHandler, dialog, { part : partName } ); + coverDoc.on( 'mouseup', mouseUpHandler, dialog, { part : partName } ); + } + + evt.data.preventDefault(); + } + + function mouseMoveHandler( evt ) + { + var x = evt.data.$.screenX, + y = evt.data.$.screenY, + dx = x - lastCoords.x, + dy = y - lastCoords.y, + viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(), + partName = evt.listenerData.part; + + if ( partName.search( 't' ) != -1 ) + topSizer( abstractDialogCoords, dy ); + if ( partName.search( 'l' ) != -1 ) + leftSizer( abstractDialogCoords, dx ); + if ( partName.search( 'b' ) != -1 ) + bottomSizer( abstractDialogCoords, dy ); + if ( partName.search( 'r' ) != -1 ) + rightSizer( abstractDialogCoords, dx ); + + lastCoords = { x : x, y : y }; + + var realX, realY, realX2, realY2; + + if ( abstractDialogCoords.x + margins[3] < magnetDistance ) + realX = - margins[3]; + else if ( partName.search( 'l' ) != -1 && abstractDialogCoords.x2 - abstractDialogCoords.x < minWidth + magnetDistance ) + realX = abstractDialogCoords.x2 - minWidth; + else + realX = abstractDialogCoords.x; + + if ( abstractDialogCoords.y + margins[0] < magnetDistance ) + realY = - margins[0]; + else if ( partName.search( 't' ) != -1 && abstractDialogCoords.y2 - abstractDialogCoords.y < minHeight + magnetDistance ) + realY = abstractDialogCoords.y2 - minHeight; + else + realY = abstractDialogCoords.y; + + if ( abstractDialogCoords.x2 - margins[1] > viewPaneSize.width - magnetDistance ) + realX2 = viewPaneSize.width + margins[1] ; + else if ( partName.search( 'r' ) != -1 && abstractDialogCoords.x2 - abstractDialogCoords.x < minWidth + magnetDistance ) + realX2 = abstractDialogCoords.x + minWidth; + else + realX2 = abstractDialogCoords.x2; + + if ( abstractDialogCoords.y2 - margins[2] > viewPaneSize.height - magnetDistance ) + realY2= viewPaneSize.height + margins[2] ; + else if ( partName.search( 'b' ) != -1 && abstractDialogCoords.y2 - abstractDialogCoords.y < minHeight + magnetDistance ) + realY2 = abstractDialogCoords.y + minHeight; + else + realY2 = abstractDialogCoords.y2 ; + + dialog.move( realX, realY ); + dialog.resize( realX2 - realX, realY2 - realY ); + + evt.data.preventDefault(); + } + + function mouseUpHandler( evt ) + { + CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); + CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); + + if ( CKEDITOR.env.ie6Compat ) + { + var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); + coverDoc.removeListener( 'mouseup', mouseUpHandler ); + coverDoc.removeListener( 'mousemove', mouseMoveHandler ); + } + } + +// TODO : Simplify the resize logic, having just a single resize grip
        . +// var widthTest = /[lr]/, +// heightTest = /[tb]/; +// for ( var i = 0 ; i < parts.length ; i++ ) +// { +// var element = dialog.parts[ parts[i] + '_resize' ]; +// if ( resizable == CKEDITOR.DIALOG_RESIZE_NONE || +// resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT && widthTest.test( parts[i] ) || +// resizable == CKEDITOR.DIALOG_RESIZE_WIDTH && heightTest.test( parts[i] ) ) +// { +// element.hide(); +// continue; +// } +// element.on( 'mousedown', mouseDownHandler, dialog, { part : parts[i] } ); +// } + } + + var resizeCover; + // Caching resuable covers and allowing only one cover + // on screen. + var covers = {}, + currentCover; + + function showCover( editor ) + { + var win = CKEDITOR.document.getWindow(); + var backgroundColorStyle = editor.config.dialog_backgroundCoverColor || 'white', + backgroundCoverOpacity = editor.config.dialog_backgroundCoverOpacity, + baseFloatZIndex = editor.config.baseFloatZIndex, + coverKey = CKEDITOR.tools.genKey( + backgroundColorStyle, + backgroundCoverOpacity, + baseFloatZIndex ), + coverElement = covers[ coverKey ]; + + if ( !coverElement ) + { + var html = [ + '
        ' + ]; + + if ( CKEDITOR.env.ie6Compat ) + { + // Support for custom document.domain in IE. + var isCustomDomain = CKEDITOR.env.isCustomDomain(), + iframeHtml = ''; + + html.push( + '' + + '' ); + } + + html.push( '
        ' ); + + coverElement = CKEDITOR.dom.element.createFromHtml( html.join( '' ) ); + coverElement.setOpacity( backgroundCoverOpacity != undefined ? backgroundCoverOpacity : 0.5 ); + + coverElement.appendTo( CKEDITOR.document.getBody() ); + covers[ coverKey ] = coverElement; + } + else + coverElement. show(); + + currentCover = coverElement; + var resizeFunc = function() + { + var size = win.getViewPaneSize(); + coverElement.setStyles( + { + width : size.width + 'px', + height : size.height + 'px' + } ); + }; + + var scrollFunc = function() + { + var pos = win.getScrollPosition(), + cursor = CKEDITOR.dialog._.currentTop; + coverElement.setStyles( + { + left : pos.x + 'px', + top : pos.y + 'px' + }); + + do + { + var dialogPos = cursor.getPosition(); + cursor.move( dialogPos.x, dialogPos.y ); + } while ( ( cursor = cursor._.parentDialog ) ); + }; + + resizeCover = resizeFunc; + win.on( 'resize', resizeFunc ); + resizeFunc(); + if ( CKEDITOR.env.ie6Compat ) + { + // IE BUG: win.$.onscroll assignment doesn't work.. it must be window.onscroll. + // So we need to invent a really funny way to make it work. + var myScrollHandler = function() + { + scrollFunc(); + arguments.callee.prevScrollHandler.apply( this, arguments ); + }; + win.$.setTimeout( function() + { + myScrollHandler.prevScrollHandler = window.onscroll || function(){}; + window.onscroll = myScrollHandler; + }, 0 ); + scrollFunc(); + } + } + + function hideCover() + { + if ( !currentCover ) + return; + + var win = CKEDITOR.document.getWindow(); + currentCover.hide(); + win.removeListener( 'resize', resizeCover ); + + if ( CKEDITOR.env.ie6Compat ) + { + win.$.setTimeout( function() + { + var prevScrollHandler = window.onscroll && window.onscroll.prevScrollHandler; + window.onscroll = prevScrollHandler || null; + }, 0 ); + } + resizeCover = null; + } + + function removeCovers() + { + for ( var coverId in covers ) + covers[ coverId ].remove(); + covers = {}; + } + + var accessKeyProcessors = {}; + + var accessKeyDownHandler = function( evt ) + { + var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey, + alt = evt.data.$.altKey, + shift = evt.data.$.shiftKey, + key = String.fromCharCode( evt.data.$.keyCode ), + keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key]; + + if ( !keyProcessor || !keyProcessor.length ) + return; + + keyProcessor = keyProcessor[keyProcessor.length - 1]; + keyProcessor.keydown && keyProcessor.keydown.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); + evt.data.preventDefault(); + }; + + var accessKeyUpHandler = function( evt ) + { + var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey, + alt = evt.data.$.altKey, + shift = evt.data.$.shiftKey, + key = String.fromCharCode( evt.data.$.keyCode ), + keyProcessor = accessKeyProcessors[( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '') + ( shift ? 'SHIFT+' : '' ) + key]; + + if ( !keyProcessor || !keyProcessor.length ) + return; + + keyProcessor = keyProcessor[keyProcessor.length - 1]; + if ( keyProcessor.keyup ) + { + keyProcessor.keyup.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); + evt.data.preventDefault(); + } + }; + + var registerAccessKey = function( uiElement, dialog, key, downFunc, upFunc ) + { + var procList = accessKeyProcessors[key] || ( accessKeyProcessors[key] = [] ); + procList.push( { + uiElement : uiElement, + dialog : dialog, + key : key, + keyup : upFunc || uiElement.accessKeyUp, + keydown : downFunc || uiElement.accessKeyDown + } ); + }; + + var unregisterAccessKey = function( obj ) + { + for ( var i in accessKeyProcessors ) + { + var list = accessKeyProcessors[i]; + for ( var j = list.length - 1 ; j >= 0 ; j-- ) + { + if ( list[j].dialog == obj || list[j].uiElement == obj ) + list.splice( j, 1 ); + } + if ( list.length === 0 ) + delete accessKeyProcessors[i]; + } + }; + + var tabAccessKeyUp = function( dialog, key ) + { + if ( dialog._.accessKeyMap[key] ) + dialog.selectPage( dialog._.accessKeyMap[key] ); + }; + + var tabAccessKeyDown = function( dialog, key ) + { + }; + + // ESC, ENTER + var preventKeyBubblingKeys = { 27 :1, 13 :1 }; + var preventKeyBubbling = function( e ) + { + if ( e.data.getKeystroke() in preventKeyBubblingKeys ) + e.data.stopPropagation(); + }; + + (function() + { + CKEDITOR.ui.dialog = + { + /** + * The base class of all dialog UI elements. + * @constructor + * @param {CKEDITOR.dialog} dialog Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition Element + * definition. Accepted fields: + *
          + *
        • id (Required) The id of the UI element. See {@link + * CKEDITOR.dialog#getContentElement}
        • + *
        • type (Required) The type of the UI element. The + * value to this field specifies which UI element class will be used to + * generate the final widget.
        • + *
        • title (Optional) The popup tooltip for the UI + * element.
        • + *
        • hidden (Optional) A flag that tells if the element + * should be initially visible.
        • + *
        • className (Optional) Additional CSS class names + * to add to the UI element. Separated by space.
        • + *
        • style (Optional) Additional CSS inline styles + * to add to the UI element. A semicolon (;) is required after the last + * style declaration.
        • + *
        • accessKey (Optional) The alphanumeric access key + * for this element. Access keys are automatically prefixed by CTRL.
        • + *
        • on* (Optional) Any UI element definition field that + * starts with on followed immediately by a capital letter and + * probably more letters is an event handler. Event handlers may be further + * divided into registered event handlers and DOM event handlers. Please + * refer to {@link CKEDITOR.ui.dialog.uiElement#registerEvents} and + * {@link CKEDITOR.ui.dialog.uiElement#eventProcessors} for more + * information.
        • + *
        + * @param {Array} htmlList + * List of HTML code to be added to the dialog's content area. + * @param {Function|String} nodeNameArg + * A function returning a string, or a simple string for the node name for + * the root DOM node. Default is 'div'. + * @param {Function|Object} stylesArg + * A function returning an object, or a simple object for CSS styles applied + * to the DOM node. Default is empty object. + * @param {Function|Object} attributesArg + * A fucntion returning an object, or a simple object for attributes applied + * to the DOM node. Default is empty object. + * @param {Function|String} contentsArg + * A function returning a string, or a simple string for the HTML code inside + * the root DOM node. Default is empty string. + * @example + */ + uiElement : function( dialog, elementDefinition, htmlList, nodeNameArg, stylesArg, attributesArg, contentsArg ) + { + if ( arguments.length < 4 ) + return; + + var nodeName = ( nodeNameArg.call ? nodeNameArg( elementDefinition ) : nodeNameArg ) || 'div', + html = [ '<', nodeName, ' ' ], + styles = ( stylesArg && stylesArg.call ? stylesArg( elementDefinition ) : stylesArg ) || {}, + attributes = ( attributesArg && attributesArg.call ? attributesArg( elementDefinition ) : attributesArg ) || {}, + innerHTML = ( contentsArg && contentsArg.call ? contentsArg.call( this, dialog, elementDefinition ) : contentsArg ) || '', + domId = this.domId = attributes.id || CKEDITOR.tools.getNextNumber() + '_uiElement', + id = this.id = elementDefinition.id, + i; + + // Set the id, a unique id is required for getElement() to work. + attributes.id = domId; + + // Set the type and definition CSS class names. + var classes = {}; + if ( elementDefinition.type ) + classes[ 'cke_dialog_ui_' + elementDefinition.type ] = 1; + if ( elementDefinition.className ) + classes[ elementDefinition.className ] = 1; + var attributeClasses = ( attributes['class'] && attributes['class'].split ) ? attributes['class'].split( ' ' ) : []; + for ( i = 0 ; i < attributeClasses.length ; i++ ) + { + if ( attributeClasses[i] ) + classes[ attributeClasses[i] ] = 1; + } + var finalClasses = []; + for ( i in classes ) + finalClasses.push( i ); + attributes['class'] = finalClasses.join( ' ' ); + + // Set the popup tooltop. + if ( elementDefinition.title ) + attributes.title = elementDefinition.title; + + // Write the inline CSS styles. + var styleStr = ( elementDefinition.style || '' ).split( ';' ); + for ( i in styles ) + styleStr.push( i + ':' + styles[i] ); + if ( elementDefinition.hidden ) + styleStr.push( 'display:none' ); + for ( i = styleStr.length - 1 ; i >= 0 ; i-- ) + { + if ( styleStr[i] === '' ) + styleStr.splice( i, 1 ); + } + if ( styleStr.length > 0 ) + attributes.style = ( attributes.style ? ( attributes.style + '; ' ) : '' ) + styleStr.join( '; ' ); + + // Write the attributes. + for ( i in attributes ) + html.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[i] ) + '" '); + + // Write the content HTML. + html.push( '>', innerHTML, '' ); + + // Add contents to the parent HTML array. + htmlList.push( html.join( '' ) ); + + ( this._ || ( this._ = {} ) ).dialog = dialog; + + // Override isChanged if it is defined in element definition. + if ( typeof( elementDefinition.isChanged ) == 'boolean' ) + this.isChanged = function(){ return elementDefinition.isChanged; }; + if ( typeof( elementDefinition.isChanged ) == 'function' ) + this.isChanged = elementDefinition.isChanged; + + // Add events. + CKEDITOR.event.implementOn( this ); + + this.registerEvents( elementDefinition ); + if ( this.accessKeyUp && this.accessKeyDown && elementDefinition.accessKey ) + registerAccessKey( this, dialog, 'CTRL+' + elementDefinition.accessKey ); + + var me = this; + dialog.on( 'load', function() + { + if ( me.getInputElement() ) + { + me.getInputElement().on( 'focus', function() + { + dialog._.tabBarMode = false; + dialog._.hasFocus = true; + me.fire( 'focus' ); + }, me ); + } + } ); + + // Register the object as a tab focus if it can be included. + if ( this.keyboardFocusable ) + { + this.tabIndex = elementDefinition.tabIndex || 0; + + this.focusIndex = dialog._.focusList.push( this ) - 1; + this.on( 'focus', function() + { + dialog._.currentFocusIndex = me.focusIndex; + } ); + } + + // Completes this object with everything we have in the + // definition. + CKEDITOR.tools.extend( this, elementDefinition ); + }, + + /** + * Horizontal layout box for dialog UI elements, auto-expends to available width of container. + * @constructor + * @extends CKEDITOR.ui.dialog.uiElement + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {Array} childObjList + * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this + * container. + * @param {Array} childHtmlList + * Array of HTML code that correspond to the HTML output of all the + * objects in childObjList. + * @param {Array} htmlList + * Array of HTML code that this element will output to. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + *
          + *
        • widths (Optional) The widths of child cells.
        • + *
        • height (Optional) The height of the layout.
        • + *
        • padding (Optional) The padding width inside child + * cells.
        • + *
        • align (Optional) The alignment of the whole layout + *
        • + *
        + * @example + */ + hbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) + { + if ( arguments.length < 4 ) + return; + + this._ || ( this._ = {} ); + + var children = this._.children = childObjList, + widths = elementDefinition && elementDefinition.widths || null, + height = elementDefinition && elementDefinition.height || null, + styles = {}, + i; + /** @ignore */ + var innerHTML = function() + { + var html = [ '' ]; + for ( i = 0 ; i < childHtmlList.length ; i++ ) + { + var className = 'cke_dialog_ui_hbox_child', + styles = []; + if ( i === 0 ) + className = 'cke_dialog_ui_hbox_first'; + if ( i == childHtmlList.length - 1 ) + className = 'cke_dialog_ui_hbox_last'; + html.push( ' 0 ) + html.push( 'style="' + styles.join('; ') + '" ' ); + html.push( '>', childHtmlList[i], '' ); + } + html.push( '' ); + return html.join( '' ); + }; + + var attribs = { role : 'presentation' }; + elementDefinition && elementDefinition.align && ( attribs.align = elementDefinition.align ); + + CKEDITOR.ui.dialog.uiElement.call( + this, + dialog, + elementDefinition || { type : 'hbox' }, + htmlList, + 'table', + styles, + attribs, + innerHTML ); + }, + + /** + * Vertical layout box for dialog UI elements. + * @constructor + * @extends CKEDITOR.ui.dialog.hbox + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {Array} childObjList + * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this + * container. + * @param {Array} childHtmlList + * Array of HTML code that correspond to the HTML output of all the + * objects in childObjList. + * @param {Array} htmlList + * Array of HTML code that this element will output to. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + *
          + *
        • width (Optional) The width of the layout.
        • + *
        • heights (Optional) The heights of individual cells. + *
        • + *
        • align (Optional) The alignment of the layout.
        • + *
        • padding (Optional) The padding width inside child + * cells.
        • + *
        • expand (Optional) Whether the layout should expand + * vertically to fill its container.
        • + *
        + * @example + */ + vbox : function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) + { + if (arguments.length < 3 ) + return; + + this._ || ( this._ = {} ); + + var children = this._.children = childObjList, + width = elementDefinition && elementDefinition.width || null, + heights = elementDefinition && elementDefinition.heights || null; + /** @ignore */ + var innerHTML = function() + { + var html = [ '' ); + for ( var i = 0 ; i < childHtmlList.length ; i++ ) + { + var styles = []; + html.push( '' ); + } + html.push( '
        0 ) + html.push( 'style="', styles.join( '; ' ), '" ' ); + html.push( ' class="cke_dialog_ui_vbox_child">', childHtmlList[i], '
        ' ); + return html.join( '' ); + }; + CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type : 'vbox' }, htmlList, 'div', null, { role : 'presentation' }, innerHTML ); + } + }; + })(); + + CKEDITOR.ui.dialog.uiElement.prototype = + { + /** + * Gets the root DOM element of this dialog UI object. + * @returns {CKEDITOR.dom.element} Root DOM element of UI object. + * @example + * uiElement.getElement().hide(); + */ + getElement : function() + { + return CKEDITOR.document.getById( this.domId ); + }, + + /** + * Gets the DOM element that the user inputs values. + * This function is used by setValue(), getValue() and focus(). It should + * be overrided in child classes where the input element isn't the root + * element. + * @returns {CKEDITOR.dom.element} The element where the user input values. + * @example + * var rawValue = textInput.getInputElement().$.value; + */ + getInputElement : function() + { + return this.getElement(); + }, + + /** + * Gets the parent dialog object containing this UI element. + * @returns {CKEDITOR.dialog} Parent dialog object. + * @example + * var dialog = uiElement.getDialog(); + */ + getDialog : function() + { + return this._.dialog; + }, + + /** + * Sets the value of this dialog UI object. + * @param {Object} value The new value. + * @returns {CKEDITOR.dialog.uiElement} The current UI element. + * @example + * uiElement.setValue( 'Dingo' ); + */ + setValue : function( value ) + { + this.getInputElement().setValue( value ); + this.fire( 'change', { value : value } ); + return this; + }, + + /** + * Gets the current value of this dialog UI object. + * @returns {Object} The current value. + * @example + * var myValue = uiElement.getValue(); + */ + getValue : function() + { + return this.getInputElement().getValue(); + }, + + /** + * Tells whether the UI object's value has changed. + * @returns {Boolean} true if changed, false if not changed. + * @example + * if ( uiElement.isChanged() ) + *   confirm( 'Value changed! Continue?' ); + */ + isChanged : function() + { + // Override in input classes. + return false; + }, + + /** + * Selects the parent tab of this element. Usually called by focus() or overridden focus() methods. + * @returns {CKEDITOR.dialog.uiElement} The current UI element. + * @example + * focus : function() + * { + * this.selectParentTab(); + * // do something else. + * } + */ + selectParentTab : function() + { + var element = this.getInputElement(), + cursor = element, + tabId; + while ( ( cursor = cursor.getParent() ) && cursor.$.className.search( 'cke_dialog_page_contents' ) == -1 ) + { /*jsl:pass*/ } + + // Some widgets don't have parent tabs (e.g. OK and Cancel buttons). + if ( !cursor ) + return this; + + tabId = cursor.getAttribute( 'name' ); + // Avoid duplicate select. + if ( this._.dialog._.currentTabId != tabId ) + this._.dialog.selectPage( tabId ); + return this; + }, + + /** + * Puts the focus to the UI object. Switches tabs if the UI object isn't in the active tab page. + * @returns {CKEDITOR.dialog.uiElement} The current UI element. + * @example + * uiElement.focus(); + */ + focus : function() + { + this.selectParentTab().getInputElement().focus(); + return this; + }, + + /** + * Registers the on* event handlers defined in the element definition. + * The default behavior of this function is: + *
          + *
        1. + * If the on* event is defined in the class's eventProcesors list, + * then the registration is delegated to the corresponding function + * in the eventProcessors list. + *
        2. + *
        3. + * If the on* event is not defined in the eventProcessors list, then + * register the event handler under the corresponding DOM event of + * the UI element's input DOM element (as defined by the return value + * of {@link CKEDITOR.ui.dialog.uiElement#getInputElement}). + *
        4. + *
        + * This function is only called at UI element instantiation, but can + * be overridded in child classes if they require more flexibility. + * @param {CKEDITOR.dialog.uiElementDefinition} definition The UI element + * definition. + * @returns {CKEDITOR.dialog.uiElement} The current UI element. + * @example + */ + registerEvents : function( definition ) + { + var regex = /^on([A-Z]\w+)/, + match; + + var registerDomEvent = function( uiElement, dialog, eventName, func ) + { + dialog.on( 'load', function() + { + uiElement.getInputElement().on( eventName, func, uiElement ); + }); + }; + + for ( var i in definition ) + { + if ( !( match = i.match( regex ) ) ) + continue; + if ( this.eventProcessors[i] ) + this.eventProcessors[i].call( this, this._.dialog, definition[i] ); + else + registerDomEvent( this, this._.dialog, match[1].toLowerCase(), definition[i] ); + } + + return this; + }, + + /** + * The event processor list used by + * {@link CKEDITOR.ui.dialog.uiElement#getInputElement} at UI element + * instantiation. The default list defines three on* events: + *
          + *
        1. onLoad - Called when the element's parent dialog opens for the + * first time
        2. + *
        3. onShow - Called whenever the element's parent dialog opens.
        4. + *
        5. onHide - Called whenever the element's parent dialog closes.
        6. + *
        + * @field + * @type Object + * @example + * // This connects the 'click' event in CKEDITOR.ui.dialog.button to onClick + * // handlers in the UI element's definitions. + * CKEDITOR.ui.dialog.button.eventProcessors = CKEDITOR.tools.extend( {}, + *   CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, + *   { onClick : function( dialog, func ) { this.on( 'click', func ); } }, + *   true ); + */ + eventProcessors : + { + onLoad : function( dialog, func ) + { + dialog.on( 'load', func, this ); + }, + + onShow : function( dialog, func ) + { + dialog.on( 'show', func, this ); + }, + + onHide : function( dialog, func ) + { + dialog.on( 'hide', func, this ); + } + }, + + /** + * The default handler for a UI element's access key down event, which + * tries to put focus to the UI element.
        + * Can be overridded in child classes for more sophisticaed behavior. + * @param {CKEDITOR.dialog} dialog The parent dialog object. + * @param {String} key The key combination pressed. Since access keys + * are defined to always include the CTRL key, its value should always + * include a 'CTRL+' prefix. + * @example + */ + accessKeyDown : function( dialog, key ) + { + this.focus(); + }, + + /** + * The default handler for a UI element's access key up event, which + * does nothing.
        + * Can be overridded in child classes for more sophisticated behavior. + * @param {CKEDITOR.dialog} dialog The parent dialog object. + * @param {String} key The key combination pressed. Since access keys + * are defined to always include the CTRL key, its value should always + * include a 'CTRL+' prefix. + * @example + */ + accessKeyUp : function( dialog, key ) + { + }, + + /** + * Disables a UI element. + * @example + */ + disable : function() + { + var element = this.getInputElement(); + element.setAttribute( 'disabled', 'true' ); + element.addClass( 'cke_disabled' ); + }, + + /** + * Enables a UI element. + * @example + */ + enable : function() + { + var element = this.getInputElement(); + element.removeAttribute( 'disabled' ); + element.removeClass( 'cke_disabled' ); + }, + + /** + * Determines whether an UI element is enabled or not. + * @returns {Boolean} Whether the UI element is enabled. + * @example + */ + isEnabled : function() + { + return !this.getInputElement().getAttribute( 'disabled' ); + }, + + /** + * Determines whether an UI element is visible or not. + * @returns {Boolean} Whether the UI element is visible. + * @example + */ + isVisible : function() + { + return this.getInputElement().isVisible(); + }, + + /** + * Determines whether an UI element is focus-able or not. + * Focus-able is defined as being both visible and enabled. + * @returns {Boolean} Whether the UI element can be focused. + * @example + */ + isFocusable : function() + { + if ( !this.isEnabled() || !this.isVisible() ) + return false; + return true; + } + }; + + CKEDITOR.ui.dialog.hbox.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement, + /** + * @lends CKEDITOR.ui.dialog.hbox.prototype + */ + { + /** + * Gets a child UI element inside this container. + * @param {Array|Number} indices An array or a single number to indicate the child's + * position in the container's descendant tree. Omit to get all the children in an array. + * @returns {Array|CKEDITOR.ui.dialog.uiElement} Array of all UI elements in the container + * if no argument given, or the specified UI element if indices is given. + * @example + * var checkbox = hbox.getChild( [0,1] ); + * checkbox.setValue( true ); + */ + getChild : function( indices ) + { + // If no arguments, return a clone of the children array. + if ( arguments.length < 1 ) + return this._.children.concat(); + + // If indices isn't array, make it one. + if ( !indices.splice ) + indices = [ indices ]; + + // Retrieve the child element according to tree position. + if ( indices.length < 2 ) + return this._.children[ indices[0] ]; + else + return ( this._.children[ indices[0] ] && this._.children[ indices[0] ].getChild ) ? + this._.children[ indices[0] ].getChild( indices.slice( 1, indices.length ) ) : + null; + } + }, true ); + + CKEDITOR.ui.dialog.vbox.prototype = new CKEDITOR.ui.dialog.hbox(); + + + + (function() + { + var commonBuilder = { + build : function( dialog, elementDefinition, output ) + { + var children = elementDefinition.children, + child, + childHtmlList = [], + childObjList = []; + for ( var i = 0 ; ( i < children.length && ( child = children[i] ) ) ; i++ ) + { + var childHtml = []; + childHtmlList.push( childHtml ); + childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) ); + } + return new CKEDITOR.ui.dialog[elementDefinition.type]( dialog, childObjList, childHtmlList, output, elementDefinition ); + } + }; + + CKEDITOR.dialog.addUIElement( 'hbox', commonBuilder ); + CKEDITOR.dialog.addUIElement( 'vbox', commonBuilder ); + })(); + + /** + * Generic dialog command. It opens a specific dialog when executed. + * @constructor + * @augments CKEDITOR.commandDefinition + * @param {string} dialogName The name of the dialog to open when executing + * this command. + * @example + * // Register the "link" command, which opens the "link" dialog. + * editor.addCommand( 'link', new CKEDITOR.dialogCommand( 'link' ) ); + */ + CKEDITOR.dialogCommand = function( dialogName ) + { + this.dialogName = dialogName; + }; + + CKEDITOR.dialogCommand.prototype = + { + /** @ignore */ + exec : function( editor ) + { + editor.openDialog( this.dialogName ); + }, + + // Dialog commands just open a dialog ui, thus require no undo logic, + // undo support should dedicate to specific dialog implementation. + canUndo: false, + + editorFocus : CKEDITOR.env.ie || CKEDITOR.env.webkit + }; + + (function() + { + var notEmptyRegex = /^([a]|[^a])+$/, + integerRegex = /^\d*$/, + numberRegex = /^\d*(?:\.\d+)?$/; + + CKEDITOR.VALIDATE_OR = 1; + CKEDITOR.VALIDATE_AND = 2; + + CKEDITOR.dialog.validate = + { + functions : function() + { + return function() + { + /** + * It's important for validate functions to be able to accept the value + * as argument in addition to this.getValue(), so that it is possible to + * combine validate functions together to make more sophisticated + * validators. + */ + var value = this && this.getValue ? this.getValue() : arguments[0]; + + var msg = undefined, + relation = CKEDITOR.VALIDATE_AND, + functions = [], i; + + for ( i = 0 ; i < arguments.length ; i++ ) + { + if ( typeof( arguments[i] ) == 'function' ) + functions.push( arguments[i] ); + else + break; + } + + if ( i < arguments.length && typeof( arguments[i] ) == 'string' ) + { + msg = arguments[i]; + i++; + } + + if ( i < arguments.length && typeof( arguments[i]) == 'number' ) + relation = arguments[i]; + + var passed = ( relation == CKEDITOR.VALIDATE_AND ? true : false ); + for ( i = 0 ; i < functions.length ; i++ ) + { + if ( relation == CKEDITOR.VALIDATE_AND ) + passed = passed && functions[i]( value ); + else + passed = passed || functions[i]( value ); + } + + if ( !passed ) + { + if ( msg !== undefined ) + alert( msg ); + if ( this && ( this.select || this.focus ) ) + ( this.select || this.focus )(); + return false; + } + + return true; + }; + }, + + regex : function( regex, msg ) + { + /* + * Can be greatly shortened by deriving from functions validator if code size + * turns out to be more important than performance. + */ + return function() + { + var value = this && this.getValue ? this.getValue() : arguments[0]; + if ( !regex.test( value ) ) + { + if ( msg !== undefined ) + alert( msg ); + if ( this && ( this.select || this.focus ) ) + { + if ( this.select ) + this.select(); + else + this.focus(); + } + return false; + } + return true; + }; + }, + + notEmpty : function( msg ) + { + return this.regex( notEmptyRegex, msg ); + }, + + integer : function( msg ) + { + return this.regex( integerRegex, msg ); + }, + + 'number' : function( msg ) + { + return this.regex( numberRegex, msg ); + }, + + equals : function( value, msg ) + { + return this.functions( function( val ){ return val == value; }, msg ); + }, + + notEqual : function( value, msg ) + { + return this.functions( function( val ){ return val != value; }, msg ); + } + }; + + CKEDITOR.on( 'instanceDestroyed', function( evt ) + { + // Remove dialog cover on last instance destroy. + if ( CKEDITOR.tools.isEmpty( CKEDITOR.instances ) ) + { + var currentTopDialog; + while ( ( currentTopDialog = CKEDITOR.dialog._.currentTop ) ) + currentTopDialog.hide(); + removeCovers(); + } + + var dialogs = evt.editor._.storedDialogs; + for ( var name in dialogs ) + dialogs[ name ].destroy(); + + }); + + })(); +})(); + +// Extend the CKEDITOR.editor class with dialog specific functions. +CKEDITOR.tools.extend( CKEDITOR.editor.prototype, + /** @lends CKEDITOR.editor.prototype */ + { + /** + * Loads and opens a registered dialog. + * @param {String} dialogName The registered name of the dialog. + * @param {Function} callback The function to be invoked after dialog instance created. + * @see CKEDITOR.dialog.add + * @example + * CKEDITOR.instances.editor1.openDialog( 'smiley' ); + * @returns {CKEDITOR.dialog} The dialog object corresponding to the dialog displayed. null if the dialog name is not registered. + */ + openDialog : function( dialogName, callback ) + { + var dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ], + dialogSkin = this.skin.dialog; + + // If the dialogDefinition is already loaded, open it immediately. + if ( typeof dialogDefinitions == 'function' && dialogSkin._isLoaded ) + { + var storedDialogs = this._.storedDialogs || + ( this._.storedDialogs = {} ); + + var dialog = storedDialogs[ dialogName ] || + ( storedDialogs[ dialogName ] = new CKEDITOR.dialog( this, dialogName ) ); + + callback && callback.call( dialog, dialog ); + dialog.show(); + + return dialog; + } + else if ( dialogDefinitions == 'failed' ) + throw new Error( '[CKEDITOR.dialog.openDialog] Dialog "' + dialogName + '" failed when loading definition.' ); + + // Not loaded? Load the .js file first. + var body = CKEDITOR.document.getBody(), + cursor = body.$.style.cursor, + me = this; + + body.setStyle( 'cursor', 'wait' ); + + function onDialogFileLoaded( success ) + { + var dialogDefinition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ], + skin = me.skin.dialog; + + // Check if both skin part and definition is loaded. + if ( !skin._isLoaded || loadDefinition && typeof success == 'undefined' ) + return; + + // In case of plugin error, mark it as loading failed. + if ( typeof dialogDefinition != 'function' ) + CKEDITOR.dialog._.dialogDefinitions[ dialogName ] = 'failed'; + + me.openDialog( dialogName, callback ); + body.setStyle( 'cursor', cursor ); + } + + if ( typeof dialogDefinitions == 'string' ) + { + var loadDefinition = 1; + CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( dialogDefinitions ), onDialogFileLoaded ); + } + + CKEDITOR.skins.load( this, 'dialog', onDialogFileLoaded ); + + return null; + } + }); + +CKEDITOR.plugins.add( 'dialog', + { + requires : [ 'dialogui' ] + }); + +// Dialog related configurations. + +/** + * The color of the dialog background cover. It should be a valid CSS color + * string. + * @name CKEDITOR.config.dialog_backgroundCoverColor + * @type String + * @default 'white' + * @example + * config.dialog_backgroundCoverColor = 'rgb(255, 254, 253)'; + */ + +/** + * The opacity of the dialog background cover. It should be a number within the + * range [0.0, 1.0]. + * @name CKEDITOR.config.dialog_backgroundCoverOpacity + * @type Number + * @default 0.5 + * @example + * config.dialog_backgroundCoverOpacity = 0.7; + */ + +/** + * If the dialog has more than one tab, put focus into the first tab as soon as dialog is opened. + * @name CKEDITOR.config.dialog_startupFocusTab + * @type Boolean + * @default false + * @example + * config.dialog_startupFocusTab = true; + */ + +/** + * The distance of magnetic borders used in moving and resizing dialogs, + * measured in pixels. + * @name CKEDITOR.config.dialog_magnetDistance + * @type Number + * @default 20 + * @example + * config.dialog_magnetDistance = 30; + */ + +/** + * Fired when a dialog definition is about to be used to create a dialog into + * an editor instance. This event makes it possible to customize the definition + * before creating it. + *

        Note that this event is called only the first time a specific dialog is + * opened. Successive openings will use the cached dialog, and this event will + * not get fired.

        + * @name CKEDITOR#dialogDefinition + * @event + * @param {CKEDITOR.dialog.dialogDefinition} data The dialog defination that + * is being loaded. + * @param {CKEDITOR.editor} editor The editor instance that will use the + * dialog. + */ diff --git a/rte/_source/plugins/dialogui/plugin.js b/rte/_source/plugins/dialogui/plugin.js new file mode 100644 index 0000000..fd4df91 --- /dev/null +++ b/rte/_source/plugins/dialogui/plugin.js @@ -0,0 +1,1415 @@ +/* +Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.html or http://ckeditor.com/license +*/ + +/** @fileoverview The "dialogui" plugin. */ + +CKEDITOR.plugins.add( 'dialogui' ); + +(function() +{ + var initPrivateObject = function( elementDefinition ) + { + this._ || ( this._ = {} ); + this._['default'] = this._.initValue = elementDefinition['default'] || ''; + this._.required = elementDefinition[ 'required' ] || false; + var args = [ this._ ]; + for ( var i = 1 ; i < arguments.length ; i++ ) + args.push( arguments[i] ); + args.push( true ); + CKEDITOR.tools.extend.apply( CKEDITOR.tools, args ); + return this._; + }, + textBuilder = + { + build : function( dialog, elementDefinition, output ) + { + return new CKEDITOR.ui.dialog.textInput( dialog, elementDefinition, output ); + } + }, + commonBuilder = + { + build : function( dialog, elementDefinition, output ) + { + return new CKEDITOR.ui.dialog[elementDefinition.type]( dialog, elementDefinition, output ); + } + }, + containerBuilder = + { + build : function( dialog, elementDefinition, output ) + { + var children = elementDefinition.children, + child, + childHtmlList = [], + childObjList = []; + for ( var i = 0 ; ( i < children.length && ( child = children[i] ) ) ; i++ ) + { + var childHtml = []; + childHtmlList.push( childHtml ); + childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) ); + } + return new CKEDITOR.ui.dialog[ elementDefinition.type ]( dialog, childObjList, childHtmlList, output, elementDefinition ); + } + }, + commonPrototype = + { + isChanged : function() + { + return this.getValue() != this.getInitValue(); + }, + + reset : function() + { + this.setValue( this.getInitValue() ); + }, + + setInitValue : function() + { + this._.initValue = this.getValue(); + }, + + resetInitValue : function() + { + this._.initValue = this._['default']; + }, + + getInitValue : function() + { + return this._.initValue; + } + }, + commonEventProcessors = CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, + { + onChange : function( dialog, func ) + { + if ( !this._.domOnChangeRegistered ) + { + dialog.on( 'load', function() + { + this.getInputElement().on( 'change', function() + { + // Make sure 'onchange' doesn't get fired after dialog closed. (#5719) + if ( !dialog.parts.dialog.isVisible() ) + return; + + this.fire( 'change', { value : this.getValue() } ); + }, this ); + }, this ); + this._.domOnChangeRegistered = true; + } + + this.on( 'change', func ); + } + }, true ), + eventRegex = /^on([A-Z]\w+)/, + cleanInnerDefinition = function( def ) + { + // An inner UI element should not have the parent's type, title or events. + for ( var i in def ) + { + if ( eventRegex.test( i ) || i == 'title' || i == 'type' ) + delete def[i]; + } + return def; + }; + + CKEDITOR.tools.extend( CKEDITOR.ui.dialog, + /** @lends CKEDITOR.ui.dialog */ + { + /** + * Base class for all dialog elements with a textual label on the left. + * @constructor + * @example + * @extends CKEDITOR.ui.dialog.uiElement + * @param {CKEDITOR.dialog} dialog + * Parent dialog object. + * @param {CKEDITOR.dialog.uiElementDefinition} elementDefinition + * The element definition. Accepted fields: + *
          + *
        • label (Required) The label string.
        • + *
        • labelLayout (Optional) Put 'horizontal' here if the + * label element is to be layed out horizontally. Otherwise a vertical + * layout will be used.
        • + *
        • widths (Optional) This applies only for horizontal + * layouts - an 2-element array of lengths to specify the widths of the + * label and the content element.
        • + *
        + * @param {Array} htmlList + * List of HTML code to output to. + * @param {Function} contentHtml + * A function returning the HTML code string to be added inside the content + * cell. + */ + labeledElement : function( dialog, elementDefinition, htmlList, contentHtml ) + { + if ( arguments.length < 4 ) + return; + + var _ = initPrivateObject.call( this, elementDefinition ); + _.labelId = CKEDITOR.tools.getNextNumber() + '_label'; + var children = this._.children = []; + /** @ignore */ + var innerHTML = function() + { + var html = []; + if ( elementDefinition.labelLayout != 'horizontal' ) + html.push( '', + '' ); + else + { + var hboxDefinition = { + type : 'hbox', + widths : elementDefinition.widths, + padding : 0, + children : + [ + { + type : 'html', + html : '