From 665e26cc8a83839af4e18e9870a199cfa1a600e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 29 Sep 2014 10:04:16 +0200 Subject: [PATCH 001/157] initial commit --- Makefile | 177 + build/doctrees/environment.pickle | Bin 0 -> 4179 bytes build/doctrees/index.doctree | Bin 0 -> 5520 bytes .../installation/installation.doctree | Bin 0 -> 3011 bytes build/html/.buildinfo | 4 + build/html/_sources/index.txt | 28 + .../_sources/installation/installation.txt | 14 + build/html/_static/ajax-loader.gif | Bin 0 -> 673 bytes build/html/_static/basic.css | 537 + build/html/_static/comment-bright.png | Bin 0 -> 3500 bytes build/html/_static/comment-close.png | Bin 0 -> 3578 bytes build/html/_static/comment.png | Bin 0 -> 3445 bytes build/html/_static/default.css | 256 + build/html/_static/doctools.js | 238 + build/html/_static/down-pressed.png | Bin 0 -> 368 bytes build/html/_static/down.png | Bin 0 -> 363 bytes build/html/_static/file.png | Bin 0 -> 392 bytes build/html/_static/jquery.js | 9404 +++++++++++++++++ build/html/_static/minus.png | Bin 0 -> 199 bytes build/html/_static/plus.png | Bin 0 -> 199 bytes build/html/_static/pygments.css | 62 + build/html/_static/searchtools.js | 622 ++ build/html/_static/sidebar.js | 159 + build/html/_static/underscore.js | 1226 +++ build/html/_static/up-pressed.png | Bin 0 -> 372 bytes build/html/_static/up.png | Bin 0 -> 363 bytes build/html/_static/websupport.js | 808 ++ build/html/genindex.html | 92 + build/html/index.html | 122 + build/html/installation/installation.html | 106 + build/html/objects.inv | Bin 0 -> 205 bytes build/html/search.html | 99 + build/html/searchindex.js | 1 + source/conf.py | 332 + source/index.rst | 28 + source/index.rst~ | 26 + source/installation/installation.rst | 14 + source/installation/installation.rst~ | 13 + 38 files changed, 14368 insertions(+) create mode 100644 Makefile create mode 100644 build/doctrees/environment.pickle create mode 100644 build/doctrees/index.doctree create mode 100644 build/doctrees/installation/installation.doctree create mode 100644 build/html/.buildinfo create mode 100644 build/html/_sources/index.txt create mode 100644 build/html/_sources/installation/installation.txt create mode 100644 build/html/_static/ajax-loader.gif create mode 100644 build/html/_static/basic.css create mode 100644 build/html/_static/comment-bright.png create mode 100644 build/html/_static/comment-close.png create mode 100644 build/html/_static/comment.png create mode 100644 build/html/_static/default.css create mode 100644 build/html/_static/doctools.js create mode 100644 build/html/_static/down-pressed.png create mode 100644 build/html/_static/down.png create mode 100644 build/html/_static/file.png create mode 100644 build/html/_static/jquery.js create mode 100644 build/html/_static/minus.png create mode 100644 build/html/_static/plus.png create mode 100644 build/html/_static/pygments.css create mode 100644 build/html/_static/searchtools.js create mode 100644 build/html/_static/sidebar.js create mode 100644 build/html/_static/underscore.js create mode 100644 build/html/_static/up-pressed.png create mode 100644 build/html/_static/up.png create mode 100644 build/html/_static/websupport.js create mode 100644 build/html/genindex.html create mode 100644 build/html/index.html create mode 100644 build/html/installation/installation.html create mode 100644 build/html/objects.inv create mode 100644 build/html/search.html create mode 100644 build/html/searchindex.js create mode 100644 source/conf.py create mode 100644 source/index.rst create mode 100644 source/index.rst~ create mode 100644 source/installation/installation.rst create mode 100644 source/installation/installation.rst~ diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..ec37e687c --- /dev/null +++ b/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/chill-doc.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/chill-doc.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/chill-doc" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/chill-doc" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/build/doctrees/environment.pickle b/build/doctrees/environment.pickle new file mode 100644 index 0000000000000000000000000000000000000000..407635202d0c3905900bf84fe3ae59120799dba1 GIT binary patch literal 4179 zcmbtXTW=gm6?WqL_@0GDfFJ|BOdLFu4KV^g}$! zJyIg&Wy404w5z0o79nvHi+JT9An_aaKOlh+KY{O5_e{oF7?&J zsb@c0op68VT%HP<95;D#C^MDxc%rRYeY22pxHU5L)LwXCpV$YsVdiqKwM@FX`}$%K zD|XN0Jtkw1g<;0?oagofv&wru54~LXW1b7nwS8>9rZd^|I!ft8Y3^mb!!w=)+{;rI za0pDo3@fJ0>m3<$PbN9fv{Z@56XwS}gxZxZcIU_13set_G=|RQJkWXJYpJp5xTCTj zR4rzm!1Gzoy&{XP`Kr)5{Xwgxm5TGG} zg!LI`J!{q~lnO25W}-rPXqFC2q1&a*C_=QnQzU`iHIwktnzQtnb2h^<`_!6?cN&cw zTW`K|^ZxekTYL8puI=w`@7}z>wZFf&fB*K@Pq+5BF0|ggv~lI`oeQ>Ie`NP<;hSZd zq=g0?feK-tHS>k;Y?l`7 zhNznfKobZg9CE?lwW4XJ-bRE8rSk_oMxc3JtNL|_#C7;sP?9=BzG2_T_AV{Ol~)U! znUrAyYEihdj#>@j$So>tHfV-B|gaE>+XkW*HwIYA`8K@fl%h=8!6-Y2-%Z z=Cx02=l@}!{7bBtug^Z+LDJ+|;B342U)eTDm!%9%gM}W8k67Osu;L|Br!u(b$!?-h zYA|Z8v<4k$)YKW-W8{Zf7%6DX0sFu82na ztXOZKDa;otgs58tDhVYO2-?qTwJn-h5Es-i9$3*p5}i^>EGH~O1=nJj9V(ZB^MNe0 z-61>4(iClFUh`h+RfTF+sa@t+f@b4FNSi%$S@BVBW-AkI`=VKN6@9`JQUZ zu~fXc_}8vxu)*+vcv0pLRb73a4k3Ps>iRQP*N*t4tgw;lGBen&$Rxr6nzdI!UG&cIrD(vUB zQECCt>nsWDn#e(ZqN%=Y9>M?j{~CPr+P6?SwBrVL+^BZ^ya{o7TL(6$g9mhRx|~cZ zI$g#9jb1-9G2w6;!!-KJU}6l`=<9=tQw}O|Ag)H=gy5?&TpuRq|5re-#Orhc;DvKC zRN$t`kkd@&I&{bVLYk?rN~vGF&I(geBFyKp>w-9*ILiWoDyd$?<|C9ukC+#+BD0Y8 zH-=y3WS4_B(qwd-5XAx9!o$9q%CEZchDlVh8 z_7Ee$8_0oLf$I{2831M|&tNE3q4O3kKqTeHE=1W8Q z`hYK_*>+)m5in?b?KIJ2DSAE)xk?MQ$~0S}NI{mlvIm zL~TtqDQV1d+V47l%X=i%qPwnI@e4EgM)MLlIc}qcV?;fcz+qx{<90`%z`DDok*s}& z_M6RsBrb%!3m_ivCD@EDeLx zL@2Fu+Qr4p0Ga}%g&bcaHwBN&d!ZvXW^uUE*)jh-Rij^$=6MAMnaPsG;ug9HYQfP# LlAxr+h2Q)S3vy=~ literal 0 HcmV?d00001 diff --git a/build/doctrees/index.doctree b/build/doctrees/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..43b8361b325a6a9fa3c809b7a90370ce339c8660 GIT binary patch literal 5520 zcmds5&yO5O72dV?$IPzB>)qfaUOQ~(V0(!?GsZIDtP&w`1d3K#gdB)CSiLpfHB+_Q z)79>(-kp(RDH2Msr8*#J#9zRHQx1rK00{{cfsjB*ApQWja6#PpUUg4T&u;9*5fKtA z^=i87Rn@EazW2TN>fME(ymxWV{n_)O3Nj-j-HBDmb#5EaYwoIrqHw~l!fs3G@^@&X~F$eT=JMymMbe9vykIFtd`9*aZIuwI0zdzo#dJW;9P zDMZdEEY;i}a;@0_!}oLB>?>trWr{Zl_i1C=)HZ0NFy%4iw#A%Xh61P5OxdwkSsL)% z{@y9phGMhIc|)eLzt@)$zu^TbXNHGfZ@i)tA>&c|K_-JkZCGkN6?^wGJUsDk-SKYS z+PQgq=O%vK{NnA-%J)>}1uXVh$P(ijA+=l5gIY<%4UfiToiViWB6%p|!40pM8BZEd z3zbEo$BY+o=&};1*f1Gm4eq5lzqfBxU{cQay-=n+F!G3ZaxpI!>=H~yuA!fkgMO(i z;EL`+7D*m=-zdGOyCFa7RyODs(p`9kk2OlS+8UA#EEWkR=7~0LkSbVp;`;8g zxF%k6$7uqHY>=`<%<6fgP!7Ssygnf8Y1KE!0Ik2jf#~ts@ zJ_WRQ7^`DX7awo9FJ(xBxx60tBBf1dGRqT;jB&)%u{TgEoMj_tgVhr@_}?m2w`l;) z*r&1S)?JVqm`Z!9Uy$QWhS(w3w@w`UW9QU0)0q6=lWI1H>;YO$_{v;rms!)MIu9c^5A0x;U}4PnQ%W?R`*rVe2Es z>d5&ey!Hz^`b75`%}S<9n zMK{HtUGCqqjfpmS-9A^b@81e&{N?%IGX8J9jwgH&B1k>V)LLuKg+Bx{xn^& zF4L;Z{Pm>F=D|9RQ=JB1O*A;^tAmRWdZN3;-#?kB#6M4YNc^jCjrezM^Jj3#!ONuB zXW^6cMJ#E~(m?F5JT;VTYetyz&SiM#^}6Nj-g$!t3Yo9hW$NB>GMfkAr18Q#ONB0{ ze6tIY&)7GUt)Im+ZxGXkGi&zXokx3hq*l$WGQt| zpmNmxd-iiE;d(0atEC|767NdL1Cnw%zxn!`}awez#nbG!JRx4zN7gNYnvM~HpK zC~O1=!#}obeke8F3i#-L`*J|%g(5P+yKC#|AS*@F>St+$x0h6AlFU3&za^R)e#(9to=IU8GE)Ai^k<@Z7AhZYA@n~7 zOah43&V3^zkuL;%dg1TRaKRW1gRVuwCQj*EO0rRXZD8O07rl?J^#84N9_ z!KVv)jGKg!FXTW(cwmc#NX3KPwouLaq#A7oATLtII7Sxb@e!$3+{*Y7KjIP9qOH;` zepQUF;%Wt^CzqjqoVOcQ)J&Jf=jU)=q0$gXrD_FGqOug86f8pA70WN$bv}}YBG+Vk z`n84Vl5rpQaxTMa=Ve^?IMAP&e)~>a12Fa?1rg~-`0!D10hGpd-aKhvy?GnKvUUP8 zIJjFNx0egB@rctMl14-s;ZHjKJ=-D(p~hDBv9MS!kA>I+ciqz22OI*@2GUp6=oQ9;k7a%$_ogJbzBV5|byG$1> z1zm58hWNQ?9=t=j$~IB|xxjH^?!pE46mC#_0J&E)d+{Wlms-r()?_lVqDuuEGr6L; w)xn7nKrA_+GOD~7U!tKtp@*$#G zcfv`wfCInfx1KEK9-`rI@_qgmzZ0xr)OyO!19mGXe*$n3OMZ*rhe@v(QGcB}XT+!o z$OM?Li8KP%PK&&P2<4JZbq&MTeV?#!T;rUe5Cn{?gnhG zBN(!5#8TWdlgx6)T=~-9B&o1;BpFGx8jDHD7lDWz?^&2vA(88qz7x<9ZwIe2J~e;hrimH|)!xbj6^C@iFw0SnP{uScaIay~rCWS{%>+87NHqsz&|U)Os4k}xT&&!$`(1V} zTwt>+?B&-k7$?*ic*Qcp+qO`PT>wTDZrnfWTQrg155x-bg`OdpEof91m67HB2ai4g zfo`s}5fYCMN`I$nKqgsiK(NelG9TAN-wkc0itI9!yUU@h5QhKe*HbnsLT|qjW5RG| zCaTjMv=@LGWF1kEGjzGX6Gjduj?)@ToM3Kz7zPe^nH->GHAU(bB9LEW!@^qAwE0#* z;L_jQwZG|fdYI1~*DCr8YZMMz_>8nm961wKR7Bpg86=f_U&I~nC1xd6lLG4>8XyIj zSFzs}Bj0VR9d#1|ySpUJb2V*)cyp1)x^&y`H8QufSMCUv3bcwNP;@kMY_ZL-{#5$S z+x)rpokXBTgjVtAxME$D{5jvZet%WZ>svAH=SD#m<1E-)8Wz&1u}A`t?OF7Z|1gRivOgXi&7IyQd1Pl zGfOfQ60;I3a`F>X^fL3(@);C=vM_KlFfb_o=k{|A33hf2a5d61U}gjg=>Rd%XaNQW zW@Cw{|b%Y*pl8F?4B9 zlo4Fz*0kZGJabY|>}Okf0}CCg{u4`zEPY^pV?j2@h+|igy0+Kz6p;@SpM4s6)XEMg z#3Y4GX>Hjlml5ftdH$4x0JGdn8~MX(U~_^d!Hi)=HU{V%g+mi8#UGbE-*ao8f#h+S z2a0-5+vc7MU$e-NhmBjLIC1v|)9+Im8x1yacJ7{^tLX(ZhYi^rpmXm0`@ku9b53aN zEXH@Y3JaztblgpxbJt{AtE1ad1Ca>{v$rwwvK(>{m~Gf_=-Ro7Fk{#;i~+{{>QtvI yb2P8Zac~?~=sRA>$6{!(^3;ZP0TPFR(G_-UDU(8Jl0?(IXu$~#4A!880|o%~Al1tN literal 0 HcmV?d00001 diff --git a/build/html/_static/basic.css b/build/html/_static/basic.css new file mode 100644 index 000000000..967e36ce0 --- /dev/null +++ b/build/html/_static/basic.css @@ -0,0 +1,537 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + width: 30px; +} + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/build/html/_static/comment-bright.png b/build/html/_static/comment-bright.png new file mode 100644 index 0000000000000000000000000000000000000000..551517b8c83b76f734ff791f847829a760ad1903 GIT binary patch literal 3500 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 A2LJ#7 literal 0 HcmV?d00001 diff --git a/build/html/_static/comment.png b/build/html/_static/comment.png new file mode 100644 index 0000000000000000000000000000000000000000..92feb52b8824c6b0f59b658b1196c61de9162a95 GIT binary patch literal 3445 zcmV-*4T|!KP)Oz@Z0f2-7z;ux~O9+4z06=<WDR*FRcSTFz- zW=q650N5=6FiBTtNC2?60Km==3$g$R3;-}uh=nNt1bYBr$Ri_o0EC$U6h`t_Jn<{8 z5a%iY0C<_QJh>z}MS)ugEpZ1|S1ukX&Pf+56gFW3VVXcL!g-k)GJ!M?;PcD?0HBc- z5#WRK{dmp}uFlRjj{U%*%WZ25jX z{P*?XzTzZ-GF^d31o+^>%=Ap99M6&ogks$0k4OBs3;+Bb(;~!4V!2o<6ys46agIcq zjPo+3B8fthDa9qy|77CdEc*jK-!%ZRYCZvbku9iQV*~a}ClFY4z~c7+0P?$U!PF=S z1Au6Q;m>#f??3%Vpd|o+W=WE9003S@Bra6Svp>fO002awfhw>;8}z{#EWidF!3EsG z3;bXU&9EIRU@z1_9W=mEXoiz;4lcq~xDGvV5BgyU zp1~-*fe8db$Osc*A=-!mVv1NJjtCc-h4>-CNCXm#Bp}I%6j35eku^v$Qi@a{RY)E3 zJ#qp$hg?Rwkvqr$GJ^buyhkyVfwECO)C{#lxu`c9ghrwZ&}4KmnvWKso6vH!8a<3Q zq36)6Xb;+tK10Vaz~~qUGsJ8#F2=(`u{bOVlVi)VBCHIn#u~6ztOL7=^<&SmcLWlF zMZgI*1b0FpVIDz9SWH+>*hr`#93(Um+6gxa1B6k+CnA%mOSC4s5&6UzVlpv@SV$}* z))J2sFA#f(L&P^E5{W}HC%KRUNwK6<(h|}}(r!{C=`5+6G)NjFlgZj-YqAG9lq?`C z$c5yc>d>VnA`E_*3F2Qp##d8RZb=H01_mm@+|Cqnc9PsG(F5HIG_C zt)aG3uTh7n6Et<2In9F>NlT@zqLtGcXcuVrX|L#Xx)I%#9!{6gSJKPrN9dR61N3(c z4Tcqi$B1Vr8Jidf7-t!G7_XR2rWwr)$3XQ?}=hpK0&Z&W{| zep&sA23f;Q!%st`QJ}G3cbou<7-yIK2z4nfCCCtN2-XOGSWo##{8Q{ATurxr~;I`ytDs%xbip}RzP zziy}Qn4Z2~fSycmr`~zJ=lUFdFa1>gZThG6M+{g7vkW8#+YHVaJjFF}Z#*3@$J_By zLtVo_L#1JrVVB{Ak-5=4qt!-@Mh}c>#$4kh<88)m#-k<%CLtzEP3leVno>={htGUuD;o7bD)w_sX$S}eAxwzy?UvgBH(S?;#HZiQMoS*2K2 zT3xe7t(~nU*1N5{rxB;QPLocnp4Ml>u<^FZwyC!nu;thW+pe~4wtZn|Vi#w(#jeBd zlf9FDx_yoPJqHbk*$%56S{;6Kv~mM9!g3B(KJ}#RZ#@)!hR|78Dq|Iq-afF%KE1Brn_fm;Im z_u$xr8UFki1L{Ox>G0o)(&RAZ;=|I=wN2l97;cLaHH6leTB-XXa*h%dBOEvi`+x zi?=Txl?TadvyiL>SuF~-LZ;|cS}4~l2eM~nS7yJ>iOM;atDY;(?aZ^v+mJV$@1Ote z62cPUlD4IWOIIx&SmwQ~YB{nzae3Pc;}r!fhE@iwJh+OsDs9zItL;~pu715HdQEGA zUct(O!LkCy1<%NCg+}G`0PgpNm-?d@-hMgNe6^V+j6x$b<6@S<$+<4_1hi}Ti zncS4LsjI}fWY1>OX6feMEuLErma3QLmkw?X+1j)X-&VBk_4Y;EFPF_I+q;9dL%E~B zJh;4Nr^(LEJ3myURP{Rblsw%57T)g973R8o)DE9*xN#~;4_o$q%o z4K@u`jhx2fBXC4{U8Qn{*%*B$Ge=nny$HAYq{=vy|sI0 z_vss+H_qMky?OB#|JK!>IX&II^LlUh#rO5!7TtbwC;iULyV-Xq?ybB}ykGP{?LpZ? z-G|jbTmIbG@7#ZCz;~eY(cDM(28Dyq{*m>M4?_iynUBkc4TkHUI6gT!;y-fz>HMcd z&t%Ugo)`Y2{>!cx7B7DI)$7;J(U{Spm-3gBzioV_{p!H$8L!*M!p0uH$#^p{Ui4P` z?ZJ24cOCDe-w#jZd?0@)|7iKK^;6KN`;!@ylm7$*nDhK&GcDTy000JJOGiWi{{a60 z|De66lK=n!32;bRa{vGf6951U69E94oEQKA00(qQO+^RV2nzr)JMUJvzW@LNr%6OX zR5;6Zk;`k`RTRfR-*ac2G}PGmXsUu>6ce?Lsn$m^3Q`48f|TwQ+_-Qh=t8Ra7nE)y zf@08(pjZ@22^EVjG*%30TJRMkBUC$WqZ73uoiv&J=APqX;!v%AH}`Vx`999MVjXwy z{f1-vh8P<=plv&cZ>p5jjX~Vt&W0e)wpw1RFRuRdDkwlKb01tp5 zP=trFN0gH^|L4jJkB{6sCV;Q!ewpg-D&4cza%GQ*b>R*=34#dW;ek`FEiB(vnw+U# zpOX5UMJBhIN&;D1!yQoIAySC!9zqJmmfoJqmQp}p&h*HTfMh~u9rKic2oz3sNM^#F zBIq*MRLbsMt%y{EHj8}LeqUUvoxf0=kqji62>ne+U`d#%J)abyK&Y`=eD%oA!36<)baZyK zXJh5im6umkS|_CSGXips$nI)oBHXojzBzyY_M5K*uvb0_9viuBVyV%5VtJ*Am1ag# zczbv4B?u8j68iOz<+)nDu^oWnL+$_G{PZOCcOGQ?!1VCefves~rfpaEZs-PdVYMiV z98ElaJ2}7f;htSXFY#Zv?__sQeckE^HV{ItO=)2hMQs=(_ Xn!ZpXD%P(H00000NkvXXu0mjf= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/build/html/_static/down-pressed.png b/build/html/_static/down-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..6f7ad782782e4f8e39b0c6e15c7344700cdd2527 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*yM+OLB!qm#z$3ZNi+iKnkC`z>}Z23@f-Ava~9&<9T!#}JFtXD=!G zGdl{fK6ro2OGiOl+hKvH6i=D3%%Y^j`yIkRn!8O>@bG)IQR0{Kf+mxNd=_WScA8u_ z3;8(7x2){m9`nt+U(Nab&1G)!{`SPVpDX$w8McLTzAJ39wprG3p4XLq$06M`%}2Yk zRPPsbES*dnYm1wkGL;iioAUB*Or2kz6(-M_r_#Me-`{mj$Z%( literal 0 HcmV?d00001 diff --git a/build/html/_static/down.png b/build/html/_static/down.png new file mode 100644 index 0000000000000000000000000000000000000000..3003a88770de3977d47a2ba69893436a2860f9e7 GIT binary patch literal 363 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*yM+OLB!qm#z$3ZNi+iKnkC`z>}xaV3tUZ$qnrLa#kt978NlpS`ru z&)HFc^}^>{UOEce+71h5nn>6&w6A!ieNbu1wh)UGh{8~et^#oZ1# z>T7oM=FZ~xXWnTo{qnXm$ZLOlqGswI_m2{XwVK)IJmBjW{J3-B3x@C=M{ShWt#fYS9M?R;8K$~YwlIqwf>VA7q=YKcwf2DS4Zj5inDKXXB1zl=(YO3ST6~rDq)&z z*o>z)=hxrfG-cDBW0G$!?6{M<$@{_4{m1o%Ub!naEtn|@^frU1tDnm{r-UW|!^@B8 literal 0 HcmV?d00001 diff --git a/build/html/_static/file.png b/build/html/_static/file.png new file mode 100644 index 0000000000000000000000000000000000000000..d18082e397e7e54f20721af768c4c2983258f1b4 GIT binary patch literal 392 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP$HyOL$D9)yc9|lc|nKf<9@eUiWd>3GuTC!a5vdfWYEazjncPj5ZQX%+1 zt8B*4=d)!cdDz4wr^#OMYfqGz$1LDFF>|#>*O?AGil(WEs?wLLy{Gj2J_@opDm%`dlax3yA*@*N$G&*ukFv>P8+2CBWO(qz zD0k1@kN>hhb1_6`&wrCswzINE(evt-5C1B^STi2@PmdKI;Vst0PQB6!2kdN literal 0 HcmV?d00001 diff --git a/build/html/_static/jquery.js b/build/html/_static/jquery.js new file mode 100644 index 000000000..e2efc335e --- /dev/null +++ b/build/html/_static/jquery.js @@ -0,0 +1,9404 @@ +/*! + * jQuery JavaScript Library v1.7.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Fri Jul 5 14:07:58 UTC 2013 + */ +(function( window, undefined ) { + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document, + navigator = window.navigator, + location = window.location; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Matches dashed string for camelizing + rdashAlpha = /-([a-z]|[0-9])/ig, + rmsPrefix = /^-ms-/, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec( selector ); + } + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context ? context.ownerDocument || context : document ); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.7.2", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.add( fn ); + + return this; + }, + + eq: function( i ) { + i = +i; + return i === -1 ? + this.slice( i ) : + this.slice( i, i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.fireWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).off( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyList ) { + return; + } + + readyList = jQuery.Callbacks( "once memory" ); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + var xml, tmp; + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction( object ); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type( array ); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array, i ) { + var len; + + if ( array ) { + if ( indexOf ) { + return indexOf.call( array, elem, i ); + } + + len = array.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in array && array[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + if ( typeof context === "string" ) { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + return fn.apply( context, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, pass ) { + var exec, + bulk = key == null, + i = 0, + length = elems.length; + + // Sets many values + if ( key && typeof key === "object" ) { + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], 1, emptyGet, value ); + } + chainable = 1; + + // Sets one value + } else if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = pass === undefined && jQuery.isFunction( value ); + + if ( bulk ) { + // Bulk operations only iterate when executing function values + if ( exec ) { + exec = fn; + fn = function( elem, key, value ) { + return exec.call( jQuery( elem ), value ); + }; + + // Otherwise they run against the entire set + } else { + fn.call( elems, value ); + fn = null; + } + } + + if ( fn ) { + for (; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + } + + chainable = 1; + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +return jQuery; + +})(); + + +// String to Object flags format cache +var flagsCache = {}; + +// Convert String-formatted flags into Object-formatted ones and store in cache +function createFlags( flags ) { + var object = flagsCache[ flags ] = {}, + i, length; + flags = flags.split( /\s+/ ); + for ( i = 0, length = flags.length; i < length; i++ ) { + object[ flags[i] ] = true; + } + return object; +} + +/* + * Create a callback list using the following parameters: + * + * flags: an optional list of space-separated flags that will change how + * the callback list behaves + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible flags: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( flags ) { + + // Convert flags from String-formatted to Object-formatted + // (we check in cache first) + flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; + + var // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = [], + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Add one or several callbacks to the list + add = function( args ) { + var i, + length, + elem, + type, + actual; + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + // Inspect recursively + add( elem ); + } else if ( type === "function" ) { + // Add if not in unique mode and callback is not in + if ( !flags.unique || !self.has( elem ) ) { + list.push( elem ); + } + } + } + }, + // Fire callbacks + fire = function( context, args ) { + args = args || []; + memory = !flags.memory || [ context, args ]; + fired = true; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { + memory = true; // Mark as halted + break; + } + } + firing = false; + if ( list ) { + if ( !flags.once ) { + if ( stack && stack.length ) { + memory = stack.shift(); + self.fireWith( memory[ 0 ], memory[ 1 ] ); + } + } else if ( memory === true ) { + self.disable(); + } else { + list = []; + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + var length = list.length; + add( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away, unless previous + // firing was halted (stopOnFalse) + } else if ( memory && memory !== true ) { + firingStart = length; + fire( memory[ 0 ], memory[ 1 ] ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + var args = arguments, + argIndex = 0, + argLength = args.length; + for ( ; argIndex < argLength ; argIndex++ ) { + for ( var i = 0; i < list.length; i++ ) { + if ( args[ argIndex ] === list[ i ] ) { + // Handle firingIndex and firingLength + if ( firing ) { + if ( i <= firingLength ) { + firingLength--; + if ( i <= firingIndex ) { + firingIndex--; + } + } + } + // Remove the element + list.splice( i--, 1 ); + // If we have some unicity property then + // we only need to do this once + if ( flags.unique ) { + break; + } + } + } + } + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + if ( list ) { + var i = 0, + length = list.length; + for ( ; i < length; i++ ) { + if ( fn === list[ i ] ) { + return true; + } + } + } + return false; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory || memory === true ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( stack ) { + if ( firing ) { + if ( !flags.once ) { + stack.push( [ context, args ] ); + } + } else if ( !( flags.once && memory ) ) { + fire( context, args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + + + +var // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + + Deferred: function( func ) { + var doneList = jQuery.Callbacks( "once memory" ), + failList = jQuery.Callbacks( "once memory" ), + progressList = jQuery.Callbacks( "memory" ), + state = "pending", + lists = { + resolve: doneList, + reject: failList, + notify: progressList + }, + promise = { + done: doneList.add, + fail: failList.add, + progress: progressList.add, + + state: function() { + return state; + }, + + // Deprecated + isResolved: doneList.fired, + isRejected: failList.fired, + + then: function( doneCallbacks, failCallbacks, progressCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); + return this; + }, + always: function() { + deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); + return this; + }, + pipe: function( fnDone, fnFail, fnProgress ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ], + progress: [ fnProgress, "notify" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + obj = promise; + } else { + for ( var key in promise ) { + obj[ key ] = promise[ key ]; + } + } + return obj; + } + }, + deferred = promise.promise({}), + key; + + for ( key in lists ) { + deferred[ key ] = lists[ key ].fire; + deferred[ key + "With" ] = lists[ key ].fireWith; + } + + // Handle state + deferred.done( function() { + state = "resolved"; + }, failList.disable, progressList.lock ).fail( function() { + state = "rejected"; + }, doneList.disable, progressList.lock ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = sliceDeferred.call( arguments, 0 ), + i = 0, + length = args.length, + pValues = new Array( length ), + count = length, + pCount = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(), + promise = deferred.promise(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + deferred.resolveWith( deferred, args ); + } + }; + } + function progressFunc( i ) { + return function( value ) { + pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + deferred.notifyWith( promise, pValues ); + }; + } + if ( length > 1 ) { + for ( ; i < length; i++ ) { + if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return promise; + } +}); + + + + +jQuery.support = (function() { + + var support, + all, + a, + select, + opt, + input, + fragment, + tds, + events, + eventName, + i, + isSupported, + div = document.createElement( "div" ), + documentElement = document.documentElement; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = "
a"; + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Tests for enctype support on a form(#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + pixelMargin: true + }; + + // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead + jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat"); + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.lastChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild( input ); + fragment.appendChild( div ); + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for ( i in { + submit: 1, + change: 1, + focusin: 1 + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + fragment.removeChild( div ); + + // Null elements to avoid leaks in IE + fragment = select = opt = div = input = null; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, outer, inner, table, td, offsetSupport, + marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight, + paddingMarginBorderVisibility, paddingMarginBorder, + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + conMarginTop = 1; + paddingMarginBorder = "padding:0;margin:0;border:"; + positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;"; + paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;"; + style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;"; + html = "
" + + "" + + "
"; + + container = document.createElement("div"); + container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + div.innerHTML = "
t
"; + tds = div.getElementsByTagName( "td" ); + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE <= 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( window.getComputedStyle ) { + div.innerHTML = ""; + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.style.width = "2px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + if ( typeof div.style.zoom !== "undefined" ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.innerHTML = ""; + div.style.width = div.style.padding = "1px"; + div.style.border = 0; + div.style.overflow = "hidden"; + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = "block"; + div.style.overflow = "visible"; + div.innerHTML = "
"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + } + + div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility; + div.innerHTML = html; + + outer = div.firstChild; + inner = outer.firstChild; + td = outer.nextSibling.firstChild.firstChild; + + offsetSupport = { + doesNotAddBorder: ( inner.offsetTop !== 5 ), + doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) + }; + + inner.style.position = "fixed"; + inner.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); + inner.style.position = inner.style.top = ""; + + outer.style.overflow = "hidden"; + outer.style.position = "relative"; + + offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); + offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + + if ( window.getComputedStyle ) { + div.style.marginTop = "1%"; + support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%"; + } + + if ( typeof container.style.zoom !== "undefined" ) { + container.style.zoom = 1; + } + + body.removeChild( container ); + marginDiv = div = container = null; + + jQuery.extend( support, offsetSupport ); + }); + + return support; +})(); + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var privateCache, thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, + isEvents = name === "events"; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = ++jQuery.uuid; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + privateCache = thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Users should not attempt to inspect the internal events object using jQuery.data, + // it is undocumented and subject to change. But does anyone listen? No. + if ( isEvents && !thisCache[ name ] ) { + return privateCache.events; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + // Reference to internal data cache key + internalKey = jQuery.expando, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ internalKey ] : internalKey; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split( " " ); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + // Ensure that `cache` is not a window object #10080 + if ( jQuery.support.deleteExpando || !cache.setInterval ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the cache and need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ internalKey ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); + } else { + elem[ internalKey ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var parts, part, attr, name, l, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attr = elem.attributes; + for ( l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + parts = key.split( ".", 2 ); + parts[1] = parts[1] ? "." + parts[1] : ""; + part = parts[1] + "!"; + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + data = this.triggerHandler( "getData" + part, [ parts[0] ] ); + + // Try to fetch any internally stored data first + if ( data === undefined && elem ) { + data = jQuery.data( elem, key ); + data = dataAttr( elem, key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } + + parts[1] = value; + this.each(function() { + var self = jQuery( this ); + + self.triggerHandler( "setData" + part, parts ); + jQuery.data( this, key, value ); + self.triggerHandler( "changeData" + part, parts ); + }); + }, null, value, arguments.length > 1, null, false ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + jQuery.isNumeric( data ) ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery._data( elem, deferDataKey ); + if ( defer && + ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && + ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout( function() { + if ( !jQuery._data( elem, queueDataKey ) && + !jQuery._data( elem, markDataKey ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.fire(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = ( type || "fx" ) + "mark"; + jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); + } + }, + + _unmark: function( force, elem, type ) { + if ( force !== true ) { + type = elem; + elem = force; + force = false; + } + if ( elem ) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); + if ( count ) { + jQuery._data( elem, key, count ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + var q; + if ( elem ) { + type = ( type || "fx" ) + "queue"; + q = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !q || jQuery.isArray(data) ) { + q = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + q.push( data ); + } + } + return q || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + hooks = {}; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + jQuery._data( elem, type + ".run", hooks ); + fn.call( elem, function() { + jQuery.dequeue( elem, type ); + }, hooks ); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue " + type + ".run", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || + jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && + jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { + count++; + tmp.add( resolve ); + } + } + resolve(); + return defer.promise( object ); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + nodeHook, boolHook, fixSpecified; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classNames, i, l, elem, className, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + classNames = ( value || "" ).split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + className = (" " + elem.className + " ").replace( rclass, " " ); + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[ c ] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var self = jQuery(this), val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, i, max, option, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + i = one ? index : 0; + max = one ? index + 1 : options.length; + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var propName, attrNames, name, l, isBool, + i = 0; + + if ( value && elem.nodeType === 1 ) { + attrNames = value.toLowerCase().split( rspace ); + l = attrNames.length; + + for ( ; i < l; i++ ) { + name = attrNames[ i ]; + + if ( name ) { + propName = jQuery.propFix[ name ] || name; + isBool = rboolean.test( name ); + + // See #9699 for explanation of this approach (setting first, then removal) + // Do not do this for boolean attributes (see #10870) + if ( !isBool ) { + jQuery.attr( elem, name, "" ); + } + elem.removeAttribute( getSetAttribute ? name : propName ); + + // Set corresponding property to false for boolean attributes + if ( isBool && propName in elem ) { + elem[ propName ] = false; + } + } + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) +jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode, + property = jQuery.prop( elem, name ); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + fixSpecified = { + name: true, + id: true, + coords: true + }; + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + ret = document.createAttribute( name ); + elem.setAttributeNode( ret ); + } + return ( ret.nodeValue = value + "" ); + } + }; + + // Apply the nodeHook to tabindex + jQuery.attrHooks.tabindex.set = nodeHook.set; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + if ( value === "" ) { + value = "false"; + } + nodeHook.set( elem, value, name ); + } + }; +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = "" + value ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); + + + + +var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, + rhoverHack = /(?:^|\s)hover(\.\S+)?\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, + quickParse = function( selector ) { + var quick = rquickIs.exec( selector ); + if ( quick ) { + // 0 1 2 3 + // [ _, tag, id, class ] + quick[1] = ( quick[1] || "" ).toLowerCase(); + quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); + } + return quick; + }, + quickIs = function( elem, m ) { + var attrs = elem.attributes || {}; + return ( + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || (attrs.id || {}).value === m[2]) && + (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) + ); + }, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); + }; + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, quick, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if ( !events ) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { + + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + quick: selector && quickParse( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if ( !handlers ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + t, tns, type, origType, namespaces, origCount, + j, events, special, handle, eventType, handleObj; + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + + // Remove matching events + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); + + if ( handleObj.selector ) { + eventType.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, [ "events", "handle" ], true ); + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + + // Event object or event type + var type = event.type || event, + namespaces = [], + cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "!" ) >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf( "." ) >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + + // Handle a global trigger + if ( !elem ) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + old = null; + for ( ; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old && old === elem.ownerDocument ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( old ) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event || window.event ); + + var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call( arguments, 0 ), + run_all = !event.exclusive && !event.namespace, + special = jQuery.event.special[ event.type ] || {}, + handlerQueue = [], + i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers that should run if there are delegated events + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !(event.button && event.type === "click") ) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this.ownerDocument || this; + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + + // Don't process events on disabled elements (#6911, #8165) + if ( cur.disabled !== true ) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = ( + handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) + ); + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = jQuery.Event( originalEvent ); + + for ( i = copy.length; i; ) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { + event.metaKey = event.ctrlKey; + } + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady + }, + + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector, + ret; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !form._submit_attached ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + form._submit_attached = true; + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + jQuery.event.simulate( "change", this, event, true ); + } + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + elem._change_attached = true; + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { // && selector != null + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + var handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( var type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); + + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + expando = "sizcache" + (Math.random() + '').replace('.', ''), + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rReturn = /\r\n/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context, seed ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set, seed ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set, i, len, match, type, left; + + if ( !expr ) { + return []; + } + + for ( i = 0, len = Expr.order.length; i < len; i++ ) { + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + type, found, item, filter, left, + i, pass, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + filter = Expr.filter[ type ]; + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + pass = not ^ found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Utility function for retreiving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +var getText = Sizzle.getText = function( elem ) { + var i, node, + nodeType = elem.nodeType, + ret = ""; + + if ( nodeType ) { + if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent || innerText for elements + if ( typeof elem.textContent === 'string' ) { + return elem.textContent; + } else if ( typeof elem.innerText === 'string' ) { + // Replace IE's carriage returns + return elem.innerText.replace( rReturn, '' ); + } else { + // Traverse it's children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + } else { + + // If no nodeType, this is expected to be an array + for ( i = 0; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + if ( node.nodeType !== 8 ) { + ret += getText( node ); + } + } + } + return ret; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + }, + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var first, last, + doneName, parent, cache, + count, diff, + type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + /* falls through */ + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + first = match[2]; + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + doneName = match[0]; + parent = elem.parentNode; + + if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { + count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent[ expando ] = doneName; + } + + diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Sizzle.attr ? + Sizzle.attr( elem, name ) : + Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + !type && Sizzle.attr ? + result != null : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} +// Expose origPOS +// "global" as in regardless of relation to brackets/parens +Expr.match.globalPOS = origPOS; + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context, seed ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet, seed ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +Sizzle.selectors.attrMap = {}; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.globalPOS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var self = this, + i, l; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + var ret = this.pushStack( "", "find", selector ), + length, n, r; + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + POS.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + // Array (deprecated as of jQuery 1.7) + if ( jQuery.isArray( selectors ) ) { + var level = 1; + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( i = 0; i < selectors.length; i++ ) { + + if ( jQuery( cur ).is( selectors[ i ] ) ) { + ret.push({ selector: selectors[ i ], elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + + return ret; + } + + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call( arguments ).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} + + + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /]", "i"), + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /\/(java|ecma)script/i, + rcleanScript = /^\s*", "" ], + legend: [ 1, "
", "
" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + col: [ 2, "", "
" ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }, + safeFragment = createSafeFragment( document ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and + + + + + + + + +
+
+
+
+ + +

Index

+ +
+ +
+ + +
+
+
+
+
+ + + + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/html/index.html b/build/html/index.html new file mode 100644 index 000000000..f04970de4 --- /dev/null +++ b/build/html/index.html @@ -0,0 +1,122 @@ + + + + + + + + Welcome to Chill documentation! — chill-doc 1.0.0 documentation + + + + + + + + + + + + + + +
+
+
+
+ +
+

Welcome to Chill documentation!¶

+

Chill is a free software for social workers.

+

Chill rely on the php framework Symfony. If you know symfony, you should not be lost. Symfony has a very good documentation.

+

Contents of this documentation:

+
+ +
+
+
+

Indices and tables¶

+ +
+ + +
+
+
+
+
+

Table Of Contents

+ + +

Next topic

+

Installation

+

This Page

+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/html/installation/installation.html b/build/html/installation/installation.html new file mode 100644 index 000000000..fc7dc7453 --- /dev/null +++ b/build/html/installation/installation.html @@ -0,0 +1,106 @@ + + + + + + + + Installation — chill-doc 1.0.0 documentation + + + + + + + + + + + + + + +
+
+
+
+ +
+

Installation¶

+

Chill is installed with composer.

+

Install composer on your system :

+
curl -sS https://getcomposer.org/installer | php
+
+
+

move composer.phar to your system.

+
+ + +
+
+
+
+
+

Previous topic

+

Welcome to Chill documentation!

+

This Page

+ + + +
+
+
+
+ + + + \ No newline at end of file diff --git a/build/html/objects.inv b/build/html/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..03801778c6e665866f4a387483e3861df1e59e48 GIT binary patch literal 205 zcmY#Z2rkIT%&Sny%qvUHE6FdaR47X=D$dN$Q!wIERtPA{&q_@$u~JCR$jr&nP03FN z3Wh-xSSc9l834H<8L0|Iskw=nc`2zy3i)XYB^jB;3Tc@+sR}?kIX}0cD7CmaHASJc zI5RI@p(-acNsp`ImbRa-j@P*W4L#3|r#yYU&IVpE4bfohewjMu$ukXY7H1I_m1WC| zo<3Wi@@(mnumxKBE3UM9I(+@{Z26RDOSCGQT6SCt{=6Zhsne;{VG0w&x*LX)0I8Z$ ALjV8( literal 0 HcmV?d00001 diff --git a/build/html/search.html b/build/html/search.html new file mode 100644 index 000000000..fe0ece0c6 --- /dev/null +++ b/build/html/search.html @@ -0,0 +1,99 @@ + + + + + + + + Search — chill-doc 1.0.0 documentation + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +

Search

+
+ +

+ Please activate JavaScript to enable the search + functionality. +

+
+

+ From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list. +

+
+ + + +
+ +
+ +
+ +
+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/build/html/searchindex.js b/build/html/searchindex.js new file mode 100644 index 000000000..bf0f5f612 --- /dev/null +++ b/build/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({envversion:43,objects:{},titles:["Welcome to Chill documentation!","Installation"],objnames:{},filenames:["index","installation/installation"],titleterms:{indic:0,document:0,tabl:0,welcom:0,instal:1,doc:[],chill:0},terms:{framework:0,worker:0,thi:0,veri:0,http:1,softwar:0,search:0,page:0,system:1,know:0,lost:0,index:0,compos:1,symfoni:0,free:0,phar:1,curl:1,you:0,instal:0,php:[0,1],org:1,good:0,should:0,your:1,content:0,modul:0,social:0,move:1,reli:0,getcompos:1},objtypes:{}}) \ No newline at end of file diff --git a/source/conf.py b/source/conf.py new file mode 100644 index 000000000..517f70a82 --- /dev/null +++ b/source/conf.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# chill-doc documentation build configuration file, created by +# sphinx-quickstart on Sun Sep 28 22:04:08 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.todo', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'chill-doc' +copyright = '2014, Champs-Libres' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'chill-docdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'chill-doc.tex', 'chill-doc Documentation', + 'Champs-Libres', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'chill-doc', 'chill-doc Documentation', + ['Champs-Libres'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'chill-doc', 'chill-doc Documentation', + 'Champs-Libres', 'chill-doc', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +# -- Options for Epub output ---------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = 'chill-doc' +epub_author = 'Champs-Libres' +epub_publisher = 'Champs-Libres' +epub_copyright = '2014, Champs-Libres' + +# The basename for the epub file. It defaults to the project name. +#epub_basename = 'chill-doc' + +# The HTML theme for the epub output. Since the default themes are not optimized +# for small screen space, using the same theme for HTML and epub output is +# usually not wise. This defaults to 'epub', a theme designed to save visual +# space. +#epub_theme = 'epub' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# A sequence of (type, uri, title) tuples for the guide element of content.opf. +#epub_guide = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True + +# Choose between 'default' and 'includehidden'. +#epub_tocscope = 'default' + +# Fix unsupported image types using the PIL. +#epub_fix_images = False + +# Scale large images. +#epub_max_image_width = 0 + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#epub_show_urls = 'inline' + +# If false, no index is generated. +#epub_use_index = True diff --git a/source/index.rst b/source/index.rst new file mode 100644 index 000000000..dbe52a35b --- /dev/null +++ b/source/index.rst @@ -0,0 +1,28 @@ +.. chill-doc documentation master file, created by + sphinx-quickstart on Sun Sep 28 22:04:08 2014. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Chill documentation! +===================================== + +Chill is a free software for social workers. + +Chill rely on the php framework Symfony. If you know symfony, you should not be lost. Symfony has a very good documentation. + +Contents of this documentation: + +.. toctree:: + :maxdepth: 2 + + installation/installation.rst + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/source/index.rst~ b/source/index.rst~ new file mode 100644 index 000000000..24c416d03 --- /dev/null +++ b/source/index.rst~ @@ -0,0 +1,26 @@ +.. chill-doc documentation master file, created by + sphinx-quickstart on Sun Sep 28 22:04:08 2014. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Chill documentation! +===================================== + +Chill is a free software for social workers. + +Contents of this documentation: + +.. toctree:: + :maxdepth: 2 + + installation/installation.rst + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/source/installation/installation.rst b/source/installation/installation.rst new file mode 100644 index 000000000..bcea376ed --- /dev/null +++ b/source/installation/installation.rst @@ -0,0 +1,14 @@ + + +Installation +============= + +Chill is installed with composer. + +Install composer on your system : + +:: + + curl -sS https://getcomposer.org/installer | php + +move composer.phar to your system. diff --git a/source/installation/installation.rst~ b/source/installation/installation.rst~ new file mode 100644 index 000000000..924ec955f --- /dev/null +++ b/source/installation/installation.rst~ @@ -0,0 +1,13 @@ + + +Installation +============= + +Chill is installed with composer. + +Install composer on your system : + +:: + + curl -sS https://getcomposer.org/installer | php + From e6af0db5f256553cd0116c1c7b12db6017e302fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 29 Sep 2014 10:07:06 +0200 Subject: [PATCH 002/157] remove build files --- .gitignore | 3 + build/doctrees/environment.pickle | Bin 4179 -> 0 bytes build/doctrees/index.doctree | Bin 5520 -> 0 bytes .../installation/installation.doctree | Bin 3011 -> 0 bytes build/git.txt | 0 build/html/.buildinfo | 4 - build/html/_sources/index.txt | 28 - .../_sources/installation/installation.txt | 14 - build/html/_static/ajax-loader.gif | Bin 673 -> 0 bytes build/html/_static/basic.css | 537 - build/html/_static/comment-bright.png | Bin 3500 -> 0 bytes build/html/_static/comment-close.png | Bin 3578 -> 0 bytes build/html/_static/comment.png | Bin 3445 -> 0 bytes build/html/_static/default.css | 256 - build/html/_static/doctools.js | 238 - build/html/_static/down-pressed.png | Bin 368 -> 0 bytes build/html/_static/down.png | Bin 363 -> 0 bytes build/html/_static/file.png | Bin 392 -> 0 bytes build/html/_static/jquery.js | 9404 ----------------- build/html/_static/minus.png | Bin 199 -> 0 bytes build/html/_static/plus.png | Bin 199 -> 0 bytes build/html/_static/pygments.css | 62 - build/html/_static/searchtools.js | 622 -- build/html/_static/sidebar.js | 159 - build/html/_static/underscore.js | 1226 --- build/html/_static/up-pressed.png | Bin 372 -> 0 bytes build/html/_static/up.png | Bin 363 -> 0 bytes build/html/_static/websupport.js | 808 -- build/html/genindex.html | 92 - build/html/index.html | 122 - build/html/installation/installation.html | 106 - build/html/objects.inv | Bin 205 -> 0 bytes build/html/search.html | 99 - build/html/searchindex.js | 1 - 34 files changed, 3 insertions(+), 13778 deletions(-) create mode 100644 .gitignore delete mode 100644 build/doctrees/environment.pickle delete mode 100644 build/doctrees/index.doctree delete mode 100644 build/doctrees/installation/installation.doctree create mode 100644 build/git.txt delete mode 100644 build/html/.buildinfo delete mode 100644 build/html/_sources/index.txt delete mode 100644 build/html/_sources/installation/installation.txt delete mode 100644 build/html/_static/ajax-loader.gif delete mode 100644 build/html/_static/basic.css delete mode 100644 build/html/_static/comment-bright.png delete mode 100644 build/html/_static/comment-close.png delete mode 100644 build/html/_static/comment.png delete mode 100644 build/html/_static/default.css delete mode 100644 build/html/_static/doctools.js delete mode 100644 build/html/_static/down-pressed.png delete mode 100644 build/html/_static/down.png delete mode 100644 build/html/_static/file.png delete mode 100644 build/html/_static/jquery.js delete mode 100644 build/html/_static/minus.png delete mode 100644 build/html/_static/plus.png delete mode 100644 build/html/_static/pygments.css delete mode 100644 build/html/_static/searchtools.js delete mode 100644 build/html/_static/sidebar.js delete mode 100644 build/html/_static/underscore.js delete mode 100644 build/html/_static/up-pressed.png delete mode 100644 build/html/_static/up.png delete mode 100644 build/html/_static/websupport.js delete mode 100644 build/html/genindex.html delete mode 100644 build/html/index.html delete mode 100644 build/html/installation/installation.html delete mode 100644 build/html/objects.inv delete mode 100644 build/html/search.html delete mode 100644 build/html/searchindex.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d71828365 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/* +!build/git.txt + diff --git a/build/doctrees/environment.pickle b/build/doctrees/environment.pickle deleted file mode 100644 index 407635202d0c3905900bf84fe3ae59120799dba1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4179 zcmbtXTW=gm6?WqL_@0GDfFJ|BOdLFu4KV^g}$! zJyIg&Wy404w5z0o79nvHi+JT9An_aaKOlh+KY{O5_e{oF7?&J zsb@c0op68VT%HP<95;D#C^MDxc%rRYeY22pxHU5L)LwXCpV$YsVdiqKwM@FX`}$%K zD|XN0Jtkw1g<;0?oagofv&wru54~LXW1b7nwS8>9rZd^|I!ft8Y3^mb!!w=)+{;rI za0pDo3@fJ0>m3<$PbN9fv{Z@56XwS}gxZxZcIU_13set_G=|RQJkWXJYpJp5xTCTj zR4rzm!1Gzoy&{XP`Kr)5{Xwgxm5TGG} zg!LI`J!{q~lnO25W}-rPXqFC2q1&a*C_=QnQzU`iHIwktnzQtnb2h^<`_!6?cN&cw zTW`K|^ZxekTYL8puI=w`@7}z>wZFf&fB*K@Pq+5BF0|ggv~lI`oeQ>Ie`NP<;hSZd zq=g0?feK-tHS>k;Y?l`7 zhNznfKobZg9CE?lwW4XJ-bRE8rSk_oMxc3JtNL|_#C7;sP?9=BzG2_T_AV{Ol~)U! znUrAyYEihdj#>@j$So>tHfV-B|gaE>+XkW*HwIYA`8K@fl%h=8!6-Y2-%Z z=Cx02=l@}!{7bBtug^Z+LDJ+|;B342U)eTDm!%9%gM}W8k67Osu;L|Br!u(b$!?-h zYA|Z8v<4k$)YKW-W8{Zf7%6DX0sFu82na ztXOZKDa;otgs58tDhVYO2-?qTwJn-h5Es-i9$3*p5}i^>EGH~O1=nJj9V(ZB^MNe0 z-61>4(iClFUh`h+RfTF+sa@t+f@b4FNSi%$S@BVBW-AkI`=VKN6@9`JQUZ zu~fXc_}8vxu)*+vcv0pLRb73a4k3Ps>iRQP*N*t4tgw;lGBen&$Rxr6nzdI!UG&cIrD(vUB zQECCt>nsWDn#e(ZqN%=Y9>M?j{~CPr+P6?SwBrVL+^BZ^ya{o7TL(6$g9mhRx|~cZ zI$g#9jb1-9G2w6;!!-KJU}6l`=<9=tQw}O|Ag)H=gy5?&TpuRq|5re-#Orhc;DvKC zRN$t`kkd@&I&{bVLYk?rN~vGF&I(geBFyKp>w-9*ILiWoDyd$?<|C9ukC+#+BD0Y8 zH-=y3WS4_B(qwd-5XAx9!o$9q%CEZchDlVh8 z_7Ee$8_0oLf$I{2831M|&tNE3q4O3kKqTeHE=1W8Q z`hYK_*>+)m5in?b?KIJ2DSAE)xk?MQ$~0S}NI{mlvIm zL~TtqDQV1d+V47l%X=i%qPwnI@e4EgM)MLlIc}qcV?;fcz+qx{<90`%z`DDok*s}& z_M6RsBrb%!3m_ivCD@EDeLx zL@2Fu+Qr4p0Ga}%g&bcaHwBN&d!ZvXW^uUE*)jh-Rij^$=6MAMnaPsG;ug9HYQfP# LlAxr+h2Q)S3vy=~ diff --git a/build/doctrees/index.doctree b/build/doctrees/index.doctree deleted file mode 100644 index 43b8361b325a6a9fa3c809b7a90370ce339c8660..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5520 zcmds5&yO5O72dV?$IPzB>)qfaUOQ~(V0(!?GsZIDtP&w`1d3K#gdB)CSiLpfHB+_Q z)79>(-kp(RDH2Msr8*#J#9zRHQx1rK00{{cfsjB*ApQWja6#PpUUg4T&u;9*5fKtA z^=i87Rn@EazW2TN>fME(ymxWV{n_)O3Nj-j-HBDmb#5EaYwoIrqHw~l!fs3G@^@&X~F$eT=JMymMbe9vykIFtd`9*aZIuwI0zdzo#dJW;9P zDMZdEEY;i}a;@0_!}oLB>?>trWr{Zl_i1C=)HZ0NFy%4iw#A%Xh61P5OxdwkSsL)% z{@y9phGMhIc|)eLzt@)$zu^TbXNHGfZ@i)tA>&c|K_-JkZCGkN6?^wGJUsDk-SKYS z+PQgq=O%vK{NnA-%J)>}1uXVh$P(ijA+=l5gIY<%4UfiToiViWB6%p|!40pM8BZEd z3zbEo$BY+o=&};1*f1Gm4eq5lzqfBxU{cQay-=n+F!G3ZaxpI!>=H~yuA!fkgMO(i z;EL`+7D*m=-zdGOyCFa7RyODs(p`9kk2OlS+8UA#EEWkR=7~0LkSbVp;`;8g zxF%k6$7uqHY>=`<%<6fgP!7Ssygnf8Y1KE!0Ik2jf#~ts@ zJ_WRQ7^`DX7awo9FJ(xBxx60tBBf1dGRqT;jB&)%u{TgEoMj_tgVhr@_}?m2w`l;) z*r&1S)?JVqm`Z!9Uy$QWhS(w3w@w`UW9QU0)0q6=lWI1H>;YO$_{v;rms!)MIu9c^5A0x;U}4PnQ%W?R`*rVe2Es z>d5&ey!Hz^`b75`%}S<9n zMK{HtUGCqqjfpmS-9A^b@81e&{N?%IGX8J9jwgH&B1k>V)LLuKg+Bx{xn^& zF4L;Z{Pm>F=D|9RQ=JB1O*A;^tAmRWdZN3;-#?kB#6M4YNc^jCjrezM^Jj3#!ONuB zXW^6cMJ#E~(m?F5JT;VTYetyz&SiM#^}6Nj-g$!t3Yo9hW$NB>GMfkAr18Q#ONB0{ ze6tIY&)7GUt)Im+ZxGXkGi&zXokx3hq*l$WGQt| zpmNmxd-iiE;d(0atEC|767NdL1Cnw%zxn!`}awez#nbG!JRx4zN7gNYnvM~HpK zC~O1=!#}obeke8F3i#-L`*J|%g(5P+yKC#|AS*@F>St+$x0h6AlFU3&za^R)e#(9to=IU8GE)Ai^k<@Z7AhZYA@n~7 zOah43&V3^zkuL;%dg1TRaKRW1gRVuwCQj*EO0rRXZD8O07rl?J^#84N9_ z!KVv)jGKg!FXTW(cwmc#NX3KPwouLaq#A7oATLtII7Sxb@e!$3+{*Y7KjIP9qOH;` zepQUF;%Wt^CzqjqoVOcQ)J&Jf=jU)=q0$gXrD_FGqOug86f8pA70WN$bv}}YBG+Vk z`n84Vl5rpQaxTMa=Ve^?IMAP&e)~>a12Fa?1rg~-`0!D10hGpd-aKhvy?GnKvUUP8 zIJjFNx0egB@rctMl14-s;ZHjKJ=-D(p~hDBv9MS!kA>I+ciqz22OI*@2GUp6=oQ9;k7a%$_ogJbzBV5|byG$1> z1zm58hWNQ?9=t=j$~IB|xxjH^?!pE46mC#_0J&E)d+{Wlms-r()?_lVqDuuEGr6L; w)xn7nKrA_+GOD~7U!tKtp@*$#G zcfv`wfCInfx1KEK9-`rI@_qgmzZ0xr)OyO!19mGXe*$n3OMZ*rhe@v(QGcB}XT+!o z$OM?Li8KP%PK&&P2<4JZbq&MTeV?#!T;rUe5Cn{?gnhG zBN(!5#8TWdlgx6)T=~-9B&o1;BpFGx8jDHD7lDWz?^&2vA(88qz7x<9ZwIe2J~e;hrimH|)!xbj6^C@iFw0SnP{uScaIay~rCWS{%>+87NHqsz&|U)Os4k}xT&&!$`(1V} zTwt>+?B&-k7$?*ic*Qcp+qO`PT>wTDZrnfWTQrg155x-bg`OdpEof91m67HB2ai4g zfo`s}5fYCMN`I$nKqgsiK(NelG9TAN-wkc0itI9!yUU@h5QhKe*HbnsLT|qjW5RG| zCaTjMv=@LGWF1kEGjzGX6Gjduj?)@ToM3Kz7zPe^nH->GHAU(bB9LEW!@^qAwE0#* z;L_jQwZG|fdYI1~*DCr8YZMMz_>8nm961wKR7Bpg86=f_U&I~nC1xd6lLG4>8XyIj zSFzs}Bj0VR9d#1|ySpUJb2V*)cyp1)x^&y`H8QufSMCUv3bcwNP;@kMY_ZL-{#5$S z+x)rpokXBTgjVtAxME$D{5jvZet%WZ>svAH=SD#m<1E-)8Wz&1u}A`t?OF7Z|1gRivOgXi&7IyQd1Pl zGfOfQ60;I3a`F>X^fL3(@);C=vM_KlFfb_o=k{|A33hf2a5d61U}gjg=>Rd%XaNQW zW@Cw{|b%Y*pl8F?4B9 zlo4Fz*0kZGJabY|>}Okf0}CCg{u4`zEPY^pV?j2@h+|igy0+Kz6p;@SpM4s6)XEMg z#3Y4GX>Hjlml5ftdH$4x0JGdn8~MX(U~_^d!Hi)=HU{V%g+mi8#UGbE-*ao8f#h+S z2a0-5+vc7MU$e-NhmBjLIC1v|)9+Im8x1yacJ7{^tLX(ZhYi^rpmXm0`@ku9b53aN zEXH@Y3JaztblgpxbJt{AtE1ad1Ca>{v$rwwvK(>{m~Gf_=-Ro7Fk{#;i~+{{>QtvI yb2P8Zac~?~=sRA>$6{!(^3;ZP0TPFR(G_-UDU(8Jl0?(IXu$~#4A!880|o%~Al1tN diff --git a/build/html/_static/basic.css b/build/html/_static/basic.css deleted file mode 100644 index 967e36ce0..000000000 --- a/build/html/_static/basic.css +++ /dev/null @@ -1,537 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox input[type="text"] { - width: 170px; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - width: 30px; -} - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable dl, table.indextable dd { - margin-top: 0; - margin-bottom: 0; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- general body styles --------------------------------------------------- */ - -a.headerlink { - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.field-list ul { - padding-left: 1em; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px 7px 0 7px; - background-color: #ffe; - width: 40%; - float: right; -} - -p.sidebar-title { - font-weight: bold; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px 7px 0 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -div.admonition dl { - margin-bottom: 0; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - border: 0; - border-collapse: collapse; -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.field-list td, table.field-list th { - border: 0 !important; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -dl { - margin-bottom: 15px; -} - -dd p { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dt:target, .highlighted { - background-color: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.optional { - font-size: 1.3em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -td.linenos pre { - padding: 5px 0px; - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - margin-left: 0.5em; -} - -table.highlighttable td { - padding: 0 0.5em 0 0.5em; -} - -tt.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -tt.descclassname { - background-color: transparent; -} - -tt.xref, a tt { - background-color: transparent; - font-weight: bold; -} - -h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/build/html/_static/comment-bright.png b/build/html/_static/comment-bright.png deleted file mode 100644 index 551517b8c83b76f734ff791f847829a760ad1903..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3500 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 A2LJ#7 diff --git a/build/html/_static/comment.png b/build/html/_static/comment.png deleted file mode 100644 index 92feb52b8824c6b0f59b658b1196c61de9162a95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3445 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 Xn!ZpXD%P(H00000NkvXXu0mjf= 0 && !jQuery(node.parentNode).hasClass(className)) { - var span = document.createElement("span"); - span.className = className; - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this); - }); - } - } - return this.each(function() { - highlight(this); - }); -}; - -/** - * Small JavaScript module for the documentation. - */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initIndexTable(); - }, - - /** - * i18n support - */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, - LOCALE : 'unknown', - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated == 'undefined') - return string; - return (typeof translated == 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated == 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); - }, - - /** - * workaround a firefox stupidity - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash && $.browser.mozilla) - window.setTimeout(function() { - document.location.href += ''; - }, 10); - }, - - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - if (!body.length) { - body = $('body'); - } - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlighted'); - }); - }, 10); - $('') - .appendTo($('#searchbox')); - } - }, - - /** - * init the domain index toggle buttons - */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) == 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeClass('highlighted'); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this == '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); diff --git a/build/html/_static/down-pressed.png b/build/html/_static/down-pressed.png deleted file mode 100644 index 6f7ad782782e4f8e39b0c6e15c7344700cdd2527..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*yM+OLB!qm#z$3ZNi+iKnkC`z>}Z23@f-Ava~9&<9T!#}JFtXD=!G zGdl{fK6ro2OGiOl+hKvH6i=D3%%Y^j`yIkRn!8O>@bG)IQR0{Kf+mxNd=_WScA8u_ z3;8(7x2){m9`nt+U(Nab&1G)!{`SPVpDX$w8McLTzAJ39wprG3p4XLq$06M`%}2Yk zRPPsbES*dnYm1wkGL;iioAUB*Or2kz6(-M_r_#Me-`{mj$Z%( diff --git a/build/html/_static/down.png b/build/html/_static/down.png deleted file mode 100644 index 3003a88770de3977d47a2ba69893436a2860f9e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 363 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*yM+OLB!qm#z$3ZNi+iKnkC`z>}xaV3tUZ$qnrLa#kt978NlpS`ru z&)HFc^}^>{UOEce+71h5nn>6&w6A!ieNbu1wh)UGh{8~et^#oZ1# z>T7oM=FZ~xXWnTo{qnXm$ZLOlqGswI_m2{XwVK)IJmBjW{J3-B3x@C=M{ShWt#fYS9M?R;8K$~YwlIqwf>VA7q=YKcwf2DS4Zj5inDKXXB1zl=(YO3ST6~rDq)&z z*o>z)=hxrfG-cDBW0G$!?6{M<$@{_4{m1o%Ub!naEtn|@^frU1tDnm{r-UW|!^@B8 diff --git a/build/html/_static/file.png b/build/html/_static/file.png deleted file mode 100644 index d18082e397e7e54f20721af768c4c2983258f1b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 392 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYmNj^kiEpy*OmP$HyOL$D9)yc9|lc|nKf<9@eUiWd>3GuTC!a5vdfWYEazjncPj5ZQX%+1 zt8B*4=d)!cdDz4wr^#OMYfqGz$1LDFF>|#>*O?AGil(WEs?wLLy{Gj2J_@opDm%`dlax3yA*@*N$G&*ukFv>P8+2CBWO(qz zD0k1@kN>hhb1_6`&wrCswzINE(evt-5C1B^STi2@PmdKI;Vst0PQB6!2kdN diff --git a/build/html/_static/jquery.js b/build/html/_static/jquery.js deleted file mode 100644 index e2efc335e..000000000 --- a/build/html/_static/jquery.js +++ /dev/null @@ -1,9404 +0,0 @@ -/*! - * jQuery JavaScript Library v1.7.2 - * http://jquery.com/ - * - * Copyright 2011, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Fri Jul 5 14:07:58 UTC 2013 - */ -(function( window, undefined ) { - -// Use the correct document accordingly with window argument (sandbox) -var document = window.document, - navigator = window.navigator, - location = window.location; -var jQuery = (function() { - -// Define a local copy of jQuery -var jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context, rootjQuery ); - }, - - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - - // Map over the $ in case of overwrite - _$ = window.$, - - // A central reference to the root jQuery(document) - rootjQuery, - - // A simple way to check for HTML strings or ID strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, - - // Check if a string has a non-whitespace character in it - rnotwhite = /\S/, - - // Used for trimming whitespace - trimLeft = /^\s+/, - trimRight = /\s+$/, - - // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, - - // JSON RegExp - rvalidchars = /^[\],:{}\s]*$/, - rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, - rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, - rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, - - // Useragent RegExp - rwebkit = /(webkit)[ \/]([\w.]+)/, - ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, - rmsie = /(msie) ([\w.]+)/, - rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, - - // Matches dashed string for camelizing - rdashAlpha = /-([a-z]|[0-9])/ig, - rmsPrefix = /^-ms-/, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return ( letter + "" ).toUpperCase(); - }, - - // Keep a UserAgent string for use with jQuery.browser - userAgent = navigator.userAgent, - - // For matching the engine and version of the browser - browserMatch, - - // The deferred used on DOM ready - readyList, - - // The ready event handler - DOMContentLoaded, - - // Save a reference to some core methods - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - push = Array.prototype.push, - slice = Array.prototype.slice, - trim = String.prototype.trim, - indexOf = Array.prototype.indexOf, - - // [[Class]] -> type pairs - class2type = {}; - -jQuery.fn = jQuery.prototype = { - constructor: jQuery, - init: function( selector, context, rootjQuery ) { - var match, elem, ret, doc; - - // Handle $(""), $(null), or $(undefined) - if ( !selector ) { - return this; - } - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - } - - // The body element only exists once, optimize finding it - if ( selector === "body" && !context && document.body ) { - this.context = document; - this[0] = document.body; - this.selector = selector; - this.length = 1; - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - // Are we dealing with HTML string or an ID? - if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = quickExpr.exec( selector ); - } - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - context = context instanceof jQuery ? context[0] : context; - doc = ( context ? context.ownerDocument || context : document ); - - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - ret = rsingleTag.exec( selector ); - - if ( ret ) { - if ( jQuery.isPlainObject( context ) ) { - selector = [ document.createElement( ret[1] ) ]; - jQuery.fn.attr.call( selector, context, true ); - - } else { - selector = [ doc.createElement( ret[1] ) ]; - } - - } else { - ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); - selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; - } - - return jQuery.merge( this, selector ); - - // HANDLE: $("#id") - } else { - elem = document.getElementById( match[2] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id !== match[2] ) { - return rootjQuery.find( selector ); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || rootjQuery ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return rootjQuery.ready( selector ); - } - - if ( selector.selector !== undefined ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }, - - // Start with an empty selector - selector: "", - - // The current version of jQuery being used - jquery: "1.7.2", - - // The default length of a jQuery object is 0 - length: 0, - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - toArray: function() { - return slice.call( this, 0 ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == null ? - - // Return a 'clean' array - this.toArray() : - - // Return just the object - ( num < 0 ? this[ this.length + num ] : this[ num ] ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems, name, selector ) { - // Build a new jQuery matched element set - var ret = this.constructor(); - - if ( jQuery.isArray( elems ) ) { - push.apply( ret, elems ); - - } else { - jQuery.merge( ret, elems ); - } - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - ret.context = this.context; - - if ( name === "find" ) { - ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; - } else if ( name ) { - ret.selector = this.selector + "." + name + "(" + selector + ")"; - } - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - ready: function( fn ) { - // Attach the listeners - jQuery.bindReady(); - - // Add the callback - readyList.add( fn ); - - return this; - }, - - eq: function( i ) { - i = +i; - return i === -1 ? - this.slice( i ) : - this.slice( i, i + 1 ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ), - "slice", slice.call(arguments).join(",") ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function( elem, i ) { - return callback.call( elem, i, elem ); - })); - }, - - end: function() { - return this.prevObject || this.constructor(null); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: [].sort, - splice: [].splice -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if ( length === i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) { - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) { - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend({ - noConflict: function( deep ) { - if ( window.$ === jQuery ) { - window.$ = _$; - } - - if ( deep && window.jQuery === jQuery ) { - window.jQuery = _jQuery; - } - - return jQuery; - }, - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - - // Handle when the DOM is ready - ready: function( wait ) { - // Either a released hold or an DOMready/load event and not yet ready - if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( !document.body ) { - return setTimeout( jQuery.ready, 1 ); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.fireWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger( "ready" ).off( "ready" ); - } - } - }, - - bindReady: function() { - if ( readyList ) { - return; - } - - readyList = jQuery.Callbacks( "once memory" ); - - // Catch cases where $(document).ready() is called after the - // browser event has already occurred. - if ( document.readyState === "complete" ) { - // Handle it asynchronously to allow scripts the opportunity to delay ready - return setTimeout( jQuery.ready, 1 ); - } - - // Mozilla, Opera and webkit nightlies currently support this event - if ( document.addEventListener ) { - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", jQuery.ready, false ); - - // If IE event model is used - } else if ( document.attachEvent ) { - // ensure firing before onload, - // maybe late but safe also for iframes - document.attachEvent( "onreadystatechange", DOMContentLoaded ); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", jQuery.ready ); - - // If IE and not a frame - // continually check to see if the document is ready - var toplevel = false; - - try { - toplevel = window.frameElement == null; - } catch(e) {} - - if ( document.documentElement.doScroll && toplevel ) { - doScrollCheck(); - } - } - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return jQuery.type(obj) === "function"; - }, - - isArray: Array.isArray || function( obj ) { - return jQuery.type(obj) === "array"; - }, - - isWindow: function( obj ) { - return obj != null && obj == obj.window; - }, - - isNumeric: function( obj ) { - return !isNaN( parseFloat(obj) ) && isFinite( obj ); - }, - - type: function( obj ) { - return obj == null ? - String( obj ) : - class2type[ toString.call(obj) ] || "object"; - }, - - isPlainObject: function( obj ) { - // Must be an Object. - // Because of IE, we also have to check the presence of the constructor property. - // Make sure that DOM nodes and window objects don't pass through, as well - if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { - return false; - } - - try { - // Not own constructor property must be Object - if ( obj.constructor && - !hasOwn.call(obj, "constructor") && - !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { - return false; - } - } catch ( e ) { - // IE8,9 Will throw exceptions on certain host objects #9897 - return false; - } - - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own. - - var key; - for ( key in obj ) {} - - return key === undefined || hasOwn.call( obj, key ); - }, - - isEmptyObject: function( obj ) { - for ( var name in obj ) { - return false; - } - return true; - }, - - error: function( msg ) { - throw new Error( msg ); - }, - - parseJSON: function( data ) { - if ( typeof data !== "string" || !data ) { - return null; - } - - // Make sure leading/trailing whitespace is removed (IE can't handle it) - data = jQuery.trim( data ); - - // Attempt to parse using the native JSON parser first - if ( window.JSON && window.JSON.parse ) { - return window.JSON.parse( data ); - } - - // Make sure the incoming data is actual JSON - // Logic borrowed from http://json.org/json2.js - if ( rvalidchars.test( data.replace( rvalidescape, "@" ) - .replace( rvalidtokens, "]" ) - .replace( rvalidbraces, "")) ) { - - return ( new Function( "return " + data ) )(); - - } - jQuery.error( "Invalid JSON: " + data ); - }, - - // Cross-browser xml parsing - parseXML: function( data ) { - if ( typeof data !== "string" || !data ) { - return null; - } - var xml, tmp; - try { - if ( window.DOMParser ) { // Standard - tmp = new DOMParser(); - xml = tmp.parseFromString( data , "text/xml" ); - } else { // IE - xml = new ActiveXObject( "Microsoft.XMLDOM" ); - xml.async = "false"; - xml.loadXML( data ); - } - } catch( e ) { - xml = undefined; - } - if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; - }, - - noop: function() {}, - - // Evaluates a script in a global context - // Workarounds based on findings by Jim Driscoll - // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context - globalEval: function( data ) { - if ( data && rnotwhite.test( data ) ) { - // We use execScript on Internet Explorer - // We use an anonymous function so that context is window - // rather than jQuery in Firefox - ( window.execScript || function( data ) { - window[ "eval" ].call( window, data ); - } )( data ); - } - }, - - // Convert dashed to camelCase; used by the css and data modules - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); - }, - - // args is for internal usage only - each: function( object, callback, args ) { - var name, i = 0, - length = object.length, - isObj = length === undefined || jQuery.isFunction( object ); - - if ( args ) { - if ( isObj ) { - for ( name in object ) { - if ( callback.apply( object[ name ], args ) === false ) { - break; - } - } - } else { - for ( ; i < length; ) { - if ( callback.apply( object[ i++ ], args ) === false ) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if ( isObj ) { - for ( name in object ) { - if ( callback.call( object[ name ], name, object[ name ] ) === false ) { - break; - } - } - } else { - for ( ; i < length; ) { - if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { - break; - } - } - } - } - - return object; - }, - - // Use native String.trim function wherever possible - trim: trim ? - function( text ) { - return text == null ? - "" : - trim.call( text ); - } : - - // Otherwise use our own trimming functionality - function( text ) { - return text == null ? - "" : - text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); - }, - - // results is for internal usage only - makeArray: function( array, results ) { - var ret = results || []; - - if ( array != null ) { - // The window, strings (and functions) also have 'length' - // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 - var type = jQuery.type( array ); - - if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { - push.call( ret, array ); - } else { - jQuery.merge( ret, array ); - } - } - - return ret; - }, - - inArray: function( elem, array, i ) { - var len; - - if ( array ) { - if ( indexOf ) { - return indexOf.call( array, elem, i ); - } - - len = array.length; - i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; - - for ( ; i < len; i++ ) { - // Skip accessing in sparse arrays - if ( i in array && array[ i ] === elem ) { - return i; - } - } - } - - return -1; - }, - - merge: function( first, second ) { - var i = first.length, - j = 0; - - if ( typeof second.length === "number" ) { - for ( var l = second.length; j < l; j++ ) { - first[ i++ ] = second[ j ]; - } - - } else { - while ( second[j] !== undefined ) { - first[ i++ ] = second[ j++ ]; - } - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, inv ) { - var ret = [], retVal; - inv = !!inv; - - // Go through the array, only saving the items - // that pass the validator function - for ( var i = 0, length = elems.length; i < length; i++ ) { - retVal = !!callback( elems[ i ], i ); - if ( inv !== retVal ) { - ret.push( elems[ i ] ); - } - } - - return ret; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var value, key, ret = [], - i = 0, - length = elems.length, - // jquery objects are treated as arrays - isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; - - // Go through the array, translating each of the items to their - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - - // Go through every key on the object, - } else { - for ( key in elems ) { - value = callback( elems[ key ], key, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - } - - // Flatten any nested arrays - return ret.concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - if ( typeof context === "string" ) { - var tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - var args = slice.call( arguments, 2 ), - proxy = function() { - return fn.apply( context, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - - return proxy; - }, - - // Mutifunctional method to get and set values to a collection - // The value/s can optionally be executed if it's a function - access: function( elems, fn, key, value, chainable, emptyGet, pass ) { - var exec, - bulk = key == null, - i = 0, - length = elems.length; - - // Sets many values - if ( key && typeof key === "object" ) { - for ( i in key ) { - jQuery.access( elems, fn, i, key[i], 1, emptyGet, value ); - } - chainable = 1; - - // Sets one value - } else if ( value !== undefined ) { - // Optionally, function values get executed if exec is true - exec = pass === undefined && jQuery.isFunction( value ); - - if ( bulk ) { - // Bulk operations only iterate when executing function values - if ( exec ) { - exec = fn; - fn = function( elem, key, value ) { - return exec.call( jQuery( elem ), value ); - }; - - // Otherwise they run against the entire set - } else { - fn.call( elems, value ); - fn = null; - } - } - - if ( fn ) { - for (; i < length; i++ ) { - fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); - } - } - - chainable = 1; - } - - return chainable ? - elems : - - // Gets - bulk ? - fn.call( elems ) : - length ? fn( elems[0], key ) : emptyGet; - }, - - now: function() { - return ( new Date() ).getTime(); - }, - - // Use of jQuery.browser is frowned upon. - // More details: http://docs.jquery.com/Utilities/jQuery.browser - uaMatch: function( ua ) { - ua = ua.toLowerCase(); - - var match = rwebkit.exec( ua ) || - ropera.exec( ua ) || - rmsie.exec( ua ) || - ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || - []; - - return { browser: match[1] || "", version: match[2] || "0" }; - }, - - sub: function() { - function jQuerySub( selector, context ) { - return new jQuerySub.fn.init( selector, context ); - } - jQuery.extend( true, jQuerySub, this ); - jQuerySub.superclass = this; - jQuerySub.fn = jQuerySub.prototype = this(); - jQuerySub.fn.constructor = jQuerySub; - jQuerySub.sub = this.sub; - jQuerySub.fn.init = function init( selector, context ) { - if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { - context = jQuerySub( context ); - } - - return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); - }; - jQuerySub.fn.init.prototype = jQuerySub.fn; - var rootjQuerySub = jQuerySub(document); - return jQuerySub; - }, - - browser: {} -}); - -// Populate the class2type map -jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -}); - -browserMatch = jQuery.uaMatch( userAgent ); -if ( browserMatch.browser ) { - jQuery.browser[ browserMatch.browser ] = true; - jQuery.browser.version = browserMatch.version; -} - -// Deprecated, use jQuery.browser.webkit instead -if ( jQuery.browser.webkit ) { - jQuery.browser.safari = true; -} - -// IE doesn't match non-breaking spaces with \s -if ( rnotwhite.test( "\xA0" ) ) { - trimLeft = /^[\s\xA0]+/; - trimRight = /[\s\xA0]+$/; -} - -// All jQuery objects should point back to these -rootjQuery = jQuery(document); - -// Cleanup functions for the document ready method -if ( document.addEventListener ) { - DOMContentLoaded = function() { - document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - jQuery.ready(); - }; - -} else if ( document.attachEvent ) { - DOMContentLoaded = function() { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( document.readyState === "complete" ) { - document.detachEvent( "onreadystatechange", DOMContentLoaded ); - jQuery.ready(); - } - }; -} - -// The DOM ready check for Internet Explorer -function doScrollCheck() { - if ( jQuery.isReady ) { - return; - } - - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - document.documentElement.doScroll("left"); - } catch(e) { - setTimeout( doScrollCheck, 1 ); - return; - } - - // and execute any waiting functions - jQuery.ready(); -} - -return jQuery; - -})(); - - -// String to Object flags format cache -var flagsCache = {}; - -// Convert String-formatted flags into Object-formatted ones and store in cache -function createFlags( flags ) { - var object = flagsCache[ flags ] = {}, - i, length; - flags = flags.split( /\s+/ ); - for ( i = 0, length = flags.length; i < length; i++ ) { - object[ flags[i] ] = true; - } - return object; -} - -/* - * Create a callback list using the following parameters: - * - * flags: an optional list of space-separated flags that will change how - * the callback list behaves - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible flags: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( flags ) { - - // Convert flags from String-formatted to Object-formatted - // (we check in cache first) - flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; - - var // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = [], - // Last fire value (for non-forgettable lists) - memory, - // Flag to know if list was already fired - fired, - // Flag to know if list is currently firing - firing, - // First callback to fire (used internally by add and fireWith) - firingStart, - // End of the loop when firing - firingLength, - // Index of currently firing callback (modified by remove if needed) - firingIndex, - // Add one or several callbacks to the list - add = function( args ) { - var i, - length, - elem, - type, - actual; - for ( i = 0, length = args.length; i < length; i++ ) { - elem = args[ i ]; - type = jQuery.type( elem ); - if ( type === "array" ) { - // Inspect recursively - add( elem ); - } else if ( type === "function" ) { - // Add if not in unique mode and callback is not in - if ( !flags.unique || !self.has( elem ) ) { - list.push( elem ); - } - } - } - }, - // Fire callbacks - fire = function( context, args ) { - args = args || []; - memory = !flags.memory || [ context, args ]; - fired = true; - firing = true; - firingIndex = firingStart || 0; - firingStart = 0; - firingLength = list.length; - for ( ; list && firingIndex < firingLength; firingIndex++ ) { - if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { - memory = true; // Mark as halted - break; - } - } - firing = false; - if ( list ) { - if ( !flags.once ) { - if ( stack && stack.length ) { - memory = stack.shift(); - self.fireWith( memory[ 0 ], memory[ 1 ] ); - } - } else if ( memory === true ) { - self.disable(); - } else { - list = []; - } - } - }, - // Actual Callbacks object - self = { - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - var length = list.length; - add( arguments ); - // Do we need to add the callbacks to the - // current firing batch? - if ( firing ) { - firingLength = list.length; - // With memory, if we're not firing then - // we should call right away, unless previous - // firing was halted (stopOnFalse) - } else if ( memory && memory !== true ) { - firingStart = length; - fire( memory[ 0 ], memory[ 1 ] ); - } - } - return this; - }, - // Remove a callback from the list - remove: function() { - if ( list ) { - var args = arguments, - argIndex = 0, - argLength = args.length; - for ( ; argIndex < argLength ; argIndex++ ) { - for ( var i = 0; i < list.length; i++ ) { - if ( args[ argIndex ] === list[ i ] ) { - // Handle firingIndex and firingLength - if ( firing ) { - if ( i <= firingLength ) { - firingLength--; - if ( i <= firingIndex ) { - firingIndex--; - } - } - } - // Remove the element - list.splice( i--, 1 ); - // If we have some unicity property then - // we only need to do this once - if ( flags.unique ) { - break; - } - } - } - } - } - return this; - }, - // Control if a given callback is in the list - has: function( fn ) { - if ( list ) { - var i = 0, - length = list.length; - for ( ; i < length; i++ ) { - if ( fn === list[ i ] ) { - return true; - } - } - } - return false; - }, - // Remove all callbacks from the list - empty: function() { - list = []; - return this; - }, - // Have the list do nothing anymore - disable: function() { - list = stack = memory = undefined; - return this; - }, - // Is it disabled? - disabled: function() { - return !list; - }, - // Lock the list in its current state - lock: function() { - stack = undefined; - if ( !memory || memory === true ) { - self.disable(); - } - return this; - }, - // Is it locked? - locked: function() { - return !stack; - }, - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( stack ) { - if ( firing ) { - if ( !flags.once ) { - stack.push( [ context, args ] ); - } - } else if ( !( flags.once && memory ) ) { - fire( context, args ); - } - } - return this; - }, - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - - - -var // Static reference to slice - sliceDeferred = [].slice; - -jQuery.extend({ - - Deferred: function( func ) { - var doneList = jQuery.Callbacks( "once memory" ), - failList = jQuery.Callbacks( "once memory" ), - progressList = jQuery.Callbacks( "memory" ), - state = "pending", - lists = { - resolve: doneList, - reject: failList, - notify: progressList - }, - promise = { - done: doneList.add, - fail: failList.add, - progress: progressList.add, - - state: function() { - return state; - }, - - // Deprecated - isResolved: doneList.fired, - isRejected: failList.fired, - - then: function( doneCallbacks, failCallbacks, progressCallbacks ) { - deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); - return this; - }, - always: function() { - deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); - return this; - }, - pipe: function( fnDone, fnFail, fnProgress ) { - return jQuery.Deferred(function( newDefer ) { - jQuery.each( { - done: [ fnDone, "resolve" ], - fail: [ fnFail, "reject" ], - progress: [ fnProgress, "notify" ] - }, function( handler, data ) { - var fn = data[ 0 ], - action = data[ 1 ], - returned; - if ( jQuery.isFunction( fn ) ) { - deferred[ handler ](function() { - returned = fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); - } else { - newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); - } - }); - } else { - deferred[ handler ]( newDefer[ action ] ); - } - }); - }).promise(); - }, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - if ( obj == null ) { - obj = promise; - } else { - for ( var key in promise ) { - obj[ key ] = promise[ key ]; - } - } - return obj; - } - }, - deferred = promise.promise({}), - key; - - for ( key in lists ) { - deferred[ key ] = lists[ key ].fire; - deferred[ key + "With" ] = lists[ key ].fireWith; - } - - // Handle state - deferred.done( function() { - state = "resolved"; - }, failList.disable, progressList.lock ).fail( function() { - state = "rejected"; - }, doneList.disable, progressList.lock ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( firstParam ) { - var args = sliceDeferred.call( arguments, 0 ), - i = 0, - length = args.length, - pValues = new Array( length ), - count = length, - pCount = length, - deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? - firstParam : - jQuery.Deferred(), - promise = deferred.promise(); - function resolveFunc( i ) { - return function( value ) { - args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; - if ( !( --count ) ) { - deferred.resolveWith( deferred, args ); - } - }; - } - function progressFunc( i ) { - return function( value ) { - pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; - deferred.notifyWith( promise, pValues ); - }; - } - if ( length > 1 ) { - for ( ; i < length; i++ ) { - if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { - args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); - } else { - --count; - } - } - if ( !count ) { - deferred.resolveWith( deferred, args ); - } - } else if ( deferred !== firstParam ) { - deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); - } - return promise; - } -}); - - - - -jQuery.support = (function() { - - var support, - all, - a, - select, - opt, - input, - fragment, - tds, - events, - eventName, - i, - isSupported, - div = document.createElement( "div" ), - documentElement = document.documentElement; - - // Preliminary tests - div.setAttribute("className", "t"); - div.innerHTML = "
a"; - - all = div.getElementsByTagName( "*" ); - a = div.getElementsByTagName( "a" )[ 0 ]; - - // Can't get basic test support - if ( !all || !all.length || !a ) { - return {}; - } - - // First batch of supports tests - select = document.createElement( "select" ); - opt = select.appendChild( document.createElement("option") ); - input = div.getElementsByTagName( "input" )[ 0 ]; - - support = { - // IE strips leading whitespace when .innerHTML is used - leadingWhitespace: ( div.firstChild.nodeType === 3 ), - - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - tbody: !div.getElementsByTagName("tbody").length, - - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - htmlSerialize: !!div.getElementsByTagName("link").length, - - // Get the style information from getAttribute - // (IE uses .cssText instead) - style: /top/.test( a.getAttribute("style") ), - - // Make sure that URLs aren't manipulated - // (IE normalizes it by default) - hrefNormalized: ( a.getAttribute("href") === "/a" ), - - // Make sure that element opacity exists - // (IE uses filter instead) - // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.55/.test( a.style.opacity ), - - // Verify style float existence - // (IE uses styleFloat instead of cssFloat) - cssFloat: !!a.style.cssFloat, - - // Make sure that if no value is specified for a checkbox - // that it defaults to "on". - // (WebKit defaults to "" instead) - checkOn: ( input.value === "on" ), - - // Make sure that a selected-by-default option has a working selected property. - // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) - optSelected: opt.selected, - - // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) - getSetAttribute: div.className !== "t", - - // Tests for enctype support on a form(#6743) - enctype: !!document.createElement("form").enctype, - - // Makes sure cloning an html5 element does not cause problems - // Where outerHTML is undefined, this still works - html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", - - // Will be defined later - submitBubbles: true, - changeBubbles: true, - focusinBubbles: false, - deleteExpando: true, - noCloneEvent: true, - inlineBlockNeedsLayout: false, - shrinkWrapBlocks: false, - reliableMarginRight: true, - pixelMargin: true - }; - - // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead - jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat"); - - // Make sure checked status is properly cloned - input.checked = true; - support.noCloneChecked = input.cloneNode( true ).checked; - - // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as disabled) - select.disabled = true; - support.optDisabled = !opt.disabled; - - // Test to see if it's possible to delete an expando from an element - // Fails in Internet Explorer - try { - delete div.test; - } catch( e ) { - support.deleteExpando = false; - } - - if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { - div.attachEvent( "onclick", function() { - // Cloning a node shouldn't copy over any - // bound event handlers (IE does this) - support.noCloneEvent = false; - }); - div.cloneNode( true ).fireEvent( "onclick" ); - } - - // Check if a radio maintains its value - // after being appended to the DOM - input = document.createElement("input"); - input.value = "t"; - input.setAttribute("type", "radio"); - support.radioValue = input.value === "t"; - - input.setAttribute("checked", "checked"); - - // #11217 - WebKit loses check when the name is after the checked attribute - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - fragment = document.createDocumentFragment(); - fragment.appendChild( div.lastChild ); - - // WebKit doesn't clone checked state correctly in fragments - support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Check if a disconnected checkbox will retain its checked - // value of true after appended to the DOM (IE6/7) - support.appendChecked = input.checked; - - fragment.removeChild( input ); - fragment.appendChild( div ); - - // Technique from Juriy Zaytsev - // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ - // We only care about the case where non-standard event systems - // are used, namely in IE. Short-circuiting here helps us to - // avoid an eval call (in setAttribute) which can cause CSP - // to go haywire. See: https://developer.mozilla.org/en/Security/CSP - if ( div.attachEvent ) { - for ( i in { - submit: 1, - change: 1, - focusin: 1 - }) { - eventName = "on" + i; - isSupported = ( eventName in div ); - if ( !isSupported ) { - div.setAttribute( eventName, "return;" ); - isSupported = ( typeof div[ eventName ] === "function" ); - } - support[ i + "Bubbles" ] = isSupported; - } - } - - fragment.removeChild( div ); - - // Null elements to avoid leaks in IE - fragment = select = opt = div = input = null; - - // Run tests that need a body at doc ready - jQuery(function() { - var container, outer, inner, table, td, offsetSupport, - marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight, - paddingMarginBorderVisibility, paddingMarginBorder, - body = document.getElementsByTagName("body")[0]; - - if ( !body ) { - // Return for frameset docs that don't have a body - return; - } - - conMarginTop = 1; - paddingMarginBorder = "padding:0;margin:0;border:"; - positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;"; - paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;"; - style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;"; - html = "
" + - "" + - "
"; - - container = document.createElement("div"); - container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; - body.insertBefore( container, body.firstChild ); - - // Construct the test element - div = document.createElement("div"); - container.appendChild( div ); - - // Check if table cells still have offsetWidth/Height when they are set - // to display:none and there are still other visible table cells in a - // table row; if so, offsetWidth/Height are not reliable for use when - // determining if an element has been hidden directly using - // display:none (it is still safe to use offsets if a parent element is - // hidden; don safety goggles and see bug #4512 for more information). - // (only IE 8 fails this test) - div.innerHTML = "
t
"; - tds = div.getElementsByTagName( "td" ); - isSupported = ( tds[ 0 ].offsetHeight === 0 ); - - tds[ 0 ].style.display = ""; - tds[ 1 ].style.display = "none"; - - // Check if empty table cells still have offsetWidth/Height - // (IE <= 8 fail this test) - support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); - - // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. For more - // info see bug #3333 - // Fails in WebKit before Feb 2011 nightlies - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - if ( window.getComputedStyle ) { - div.innerHTML = ""; - marginDiv = document.createElement( "div" ); - marginDiv.style.width = "0"; - marginDiv.style.marginRight = "0"; - div.style.width = "2px"; - div.appendChild( marginDiv ); - support.reliableMarginRight = - ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; - } - - if ( typeof div.style.zoom !== "undefined" ) { - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - // (IE < 8 does this) - div.innerHTML = ""; - div.style.width = div.style.padding = "1px"; - div.style.border = 0; - div.style.overflow = "hidden"; - div.style.display = "inline"; - div.style.zoom = 1; - support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); - - // Check if elements with layout shrink-wrap their children - // (IE 6 does this) - div.style.display = "block"; - div.style.overflow = "visible"; - div.innerHTML = "
"; - support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); - } - - div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility; - div.innerHTML = html; - - outer = div.firstChild; - inner = outer.firstChild; - td = outer.nextSibling.firstChild.firstChild; - - offsetSupport = { - doesNotAddBorder: ( inner.offsetTop !== 5 ), - doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) - }; - - inner.style.position = "fixed"; - inner.style.top = "20px"; - - // safari subtracts parent border width here which is 5px - offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); - inner.style.position = inner.style.top = ""; - - outer.style.overflow = "hidden"; - outer.style.position = "relative"; - - offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); - offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); - - if ( window.getComputedStyle ) { - div.style.marginTop = "1%"; - support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%"; - } - - if ( typeof container.style.zoom !== "undefined" ) { - container.style.zoom = 1; - } - - body.removeChild( container ); - marginDiv = div = container = null; - - jQuery.extend( support, offsetSupport ); - }); - - return support; -})(); - - - - -var rbrace = /^(?:\{.*\}|\[.*\])$/, - rmultiDash = /([A-Z])/g; - -jQuery.extend({ - cache: {}, - - // Please use with caution - uuid: 0, - - // Unique for each copy of jQuery on the page - // Non-digits removed to match rinlinejQuery - expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), - - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true - }, - - hasData: function( elem ) { - elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; - return !!elem && !isEmptyDataObject( elem ); - }, - - data: function( elem, name, data, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var privateCache, thisCache, ret, - internalKey = jQuery.expando, - getByName = typeof name === "string", - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, - - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, - isEvents = name === "events"; - - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { - return; - } - - if ( !id ) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - elem[ internalKey ] = id = ++jQuery.uuid; - } else { - id = internalKey; - } - } - - if ( !cache[ id ] ) { - cache[ id ] = {}; - - // Avoids exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } - } - - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if ( typeof name === "object" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ] = jQuery.extend( cache[ id ], name ); - } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); - } - } - - privateCache = thisCache = cache[ id ]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if ( !pvt ) { - if ( !thisCache.data ) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } - - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } - - // Users should not attempt to inspect the internal events object using jQuery.data, - // it is undocumented and subject to change. But does anyone listen? No. - if ( isEvents && !thisCache[ name ] ) { - return privateCache.events; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( getByName ) { - - // First Try to find as-is property data - ret = thisCache[ name ]; - - // Test for null|undefined property data - if ( ret == null ) { - - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; - } - } else { - ret = thisCache; - } - - return ret; - }, - - removeData: function( elem, name, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, i, l, - - // Reference to internal data cache key - internalKey = jQuery.expando, - - isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - - // See jQuery.data for more information - id = isNode ? elem[ internalKey ] : internalKey; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } - - if ( name ) { - - thisCache = pvt ? cache[ id ] : cache[ id ].data; - - if ( thisCache ) { - - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { - - // try the string as a key before any manipulation - if ( name in thisCache ) { - name = [ name ]; - } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split( " " ); - } - } - } - - for ( i = 0, l = name.length; i < l; i++ ) { - delete thisCache[ name[i] ]; - } - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { - return; - } - } - } - - // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject(cache[ id ]) ) { - return; - } - } - - // Browsers that fail expando deletion also refuse to delete expandos on - // the window, but it will allow it on all other JS objects; other browsers - // don't care - // Ensure that `cache` is not a window object #10080 - if ( jQuery.support.deleteExpando || !cache.setInterval ) { - delete cache[ id ]; - } else { - cache[ id ] = null; - } - - // We destroyed the cache and need to eliminate the expando on the node to avoid - // false lookups in the cache for entries that no longer exist - if ( isNode ) { - // IE does not allow us to delete expando properties from nodes, - // nor does it have a removeAttribute function on Document nodes; - // we must handle all of these cases - if ( jQuery.support.deleteExpando ) { - delete elem[ internalKey ]; - } else if ( elem.removeAttribute ) { - elem.removeAttribute( internalKey ); - } else { - elem[ internalKey ] = null; - } - } - }, - - // For internal use only. - _data: function( elem, name, data ) { - return jQuery.data( elem, name, data, true ); - }, - - // A method for determining if a DOM node can handle the data expando - acceptData: function( elem ) { - if ( elem.nodeName ) { - var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; - - if ( match ) { - return !(match === true || elem.getAttribute("classid") !== match); - } - } - - return true; - } -}); - -jQuery.fn.extend({ - data: function( key, value ) { - var parts, part, attr, name, l, - elem = this[0], - i = 0, - data = null; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = jQuery.data( elem ); - - if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { - attr = elem.attributes; - for ( l = attr.length; i < l; i++ ) { - name = attr[i].name; - - if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.substring(5) ); - - dataAttr( elem, name, data[ name ] ); - } - } - jQuery._data( elem, "parsedAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each(function() { - jQuery.data( this, key ); - }); - } - - parts = key.split( ".", 2 ); - parts[1] = parts[1] ? "." + parts[1] : ""; - part = parts[1] + "!"; - - return jQuery.access( this, function( value ) { - - if ( value === undefined ) { - data = this.triggerHandler( "getData" + part, [ parts[0] ] ); - - // Try to fetch any internally stored data first - if ( data === undefined && elem ) { - data = jQuery.data( elem, key ); - data = dataAttr( elem, key, data ); - } - - return data === undefined && parts[1] ? - this.data( parts[0] ) : - data; - } - - parts[1] = value; - this.each(function() { - var self = jQuery( this ); - - self.triggerHandler( "setData" + part, parts ); - jQuery.data( this, key, value ); - self.triggerHandler( "changeData" + part, parts ); - }); - }, null, value, arguments.length > 1, null, false ); - }, - - removeData: function( key ) { - return this.each(function() { - jQuery.removeData( this, key ); - }); - } -}); - -function dataAttr( elem, key, data ) { - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - - var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - jQuery.isNumeric( data ) ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - // Make sure we set the data so it isn't changed later - jQuery.data( elem, key, data ); - - } else { - data = undefined; - } - } - - return data; -} - -// checks a cache object for emptiness -function isEmptyDataObject( obj ) { - for ( var name in obj ) { - - // if the public data object is empty, the private is still empty - if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { - continue; - } - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} - - - - -function handleQueueMarkDefer( elem, type, src ) { - var deferDataKey = type + "defer", - queueDataKey = type + "queue", - markDataKey = type + "mark", - defer = jQuery._data( elem, deferDataKey ); - if ( defer && - ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && - ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { - // Give room for hard-coded callbacks to fire first - // and eventually mark/queue something else on the element - setTimeout( function() { - if ( !jQuery._data( elem, queueDataKey ) && - !jQuery._data( elem, markDataKey ) ) { - jQuery.removeData( elem, deferDataKey, true ); - defer.fire(); - } - }, 0 ); - } -} - -jQuery.extend({ - - _mark: function( elem, type ) { - if ( elem ) { - type = ( type || "fx" ) + "mark"; - jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); - } - }, - - _unmark: function( force, elem, type ) { - if ( force !== true ) { - type = elem; - elem = force; - force = false; - } - if ( elem ) { - type = type || "fx"; - var key = type + "mark", - count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); - if ( count ) { - jQuery._data( elem, key, count ); - } else { - jQuery.removeData( elem, key, true ); - handleQueueMarkDefer( elem, type, "mark" ); - } - } - }, - - queue: function( elem, type, data ) { - var q; - if ( elem ) { - type = ( type || "fx" ) + "queue"; - q = jQuery._data( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !q || jQuery.isArray(data) ) { - q = jQuery._data( elem, type, jQuery.makeArray(data) ); - } else { - q.push( data ); - } - } - return q || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - fn = queue.shift(), - hooks = {}; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - } - - if ( fn ) { - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - jQuery._data( elem, type + ".run", hooks ); - fn.call( elem, function() { - jQuery.dequeue( elem, type ); - }, hooks ); - } - - if ( !queue.length ) { - jQuery.removeData( elem, type + "queue " + type + ".run", true ); - handleQueueMarkDefer( elem, type, "queue" ); - } - } -}); - -jQuery.fn.extend({ - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[0], type ); - } - - return data === undefined ? - this : - this.each(function() { - var queue = jQuery.queue( this, type, data ); - - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); - }, - dequeue: function( type ) { - return this.each(function() { - jQuery.dequeue( this, type ); - }); - }, - // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ - delay: function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = setTimeout( next, time ); - hooks.stop = function() { - clearTimeout( timeout ); - }; - }); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, object ) { - if ( typeof type !== "string" ) { - object = type; - type = undefined; - } - type = type || "fx"; - var defer = jQuery.Deferred(), - elements = this, - i = elements.length, - count = 1, - deferDataKey = type + "defer", - queueDataKey = type + "queue", - markDataKey = type + "mark", - tmp; - function resolve() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - } - while( i-- ) { - if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || - ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || - jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && - jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { - count++; - tmp.add( resolve ); - } - } - resolve(); - return defer.promise( object ); - } -}); - - - - -var rclass = /[\n\t\r]/g, - rspace = /\s+/, - rreturn = /\r/g, - rtype = /^(?:button|input)$/i, - rfocusable = /^(?:button|input|object|select|textarea)$/i, - rclickable = /^a(?:rea)?$/i, - rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, - getSetAttribute = jQuery.support.getSetAttribute, - nodeHook, boolHook, fixSpecified; - -jQuery.fn.extend({ - attr: function( name, value ) { - return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each(function() { - jQuery.removeAttr( this, name ); - }); - }, - - prop: function( name, value ) { - return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - name = jQuery.propFix[ name ] || name; - return this.each(function() { - // try/catch handles cases where IE balks (such as removing a property on window) - try { - this[ name ] = undefined; - delete this[ name ]; - } catch( e ) {} - }); - }, - - addClass: function( value ) { - var classNames, i, l, elem, - setClass, c, cl; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).addClass( value.call(this, j, this.className) ); - }); - } - - if ( value && typeof value === "string" ) { - classNames = value.split( rspace ); - - for ( i = 0, l = this.length; i < l; i++ ) { - elem = this[ i ]; - - if ( elem.nodeType === 1 ) { - if ( !elem.className && classNames.length === 1 ) { - elem.className = value; - - } else { - setClass = " " + elem.className + " "; - - for ( c = 0, cl = classNames.length; c < cl; c++ ) { - if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { - setClass += classNames[ c ] + " "; - } - } - elem.className = jQuery.trim( setClass ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classNames, i, l, elem, className, c, cl; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).removeClass( value.call(this, j, this.className) ); - }); - } - - if ( (value && typeof value === "string") || value === undefined ) { - classNames = ( value || "" ).split( rspace ); - - for ( i = 0, l = this.length; i < l; i++ ) { - elem = this[ i ]; - - if ( elem.nodeType === 1 && elem.className ) { - if ( value ) { - className = (" " + elem.className + " ").replace( rclass, " " ); - for ( c = 0, cl = classNames.length; c < cl; c++ ) { - className = className.replace(" " + classNames[ c ] + " ", " "); - } - elem.className = jQuery.trim( className ); - - } else { - elem.className = ""; - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isBool = typeof stateVal === "boolean"; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( i ) { - jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); - }); - } - - return this.each(function() { - if ( type === "string" ) { - // toggle individual class names - var className, - i = 0, - self = jQuery( this ), - state = stateVal, - classNames = value.split( rspace ); - - while ( (className = classNames[ i++ ]) ) { - // check each className given, space seperated list - state = isBool ? state : !self.hasClass( className ); - self[ state ? "addClass" : "removeClass" ]( className ); - } - - } else if ( type === "undefined" || type === "boolean" ) { - if ( this.className ) { - // store className if set - jQuery._data( this, "__className__", this.className ); - } - - // toggle whole className - this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; - } - }); - }, - - hasClass: function( selector ) { - var className = " " + selector + " ", - i = 0, - l = this.length; - for ( ; i < l; i++ ) { - if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { - return true; - } - } - - return false; - }, - - val: function( value ) { - var hooks, ret, isFunction, - elem = this[0]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { - return ret; - } - - ret = elem.value; - - return typeof ret === "string" ? - // handle most common string cases - ret.replace(rreturn, "") : - // handle cases where value is null/undef or number - ret == null ? "" : ret; - } - - return; - } - - isFunction = jQuery.isFunction( value ); - - return this.each(function( i ) { - var self = jQuery(this), val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call( this, i, self.val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - } else if ( typeof val === "number" ) { - val += ""; - } else if ( jQuery.isArray( val ) ) { - val = jQuery.map(val, function ( value ) { - return value == null ? "" : value + ""; - }); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - }); - } -}); - -jQuery.extend({ - valHooks: { - option: { - get: function( elem ) { - // attributes.value is undefined in Blackberry 4.7 but - // uses .value. See #6932 - var val = elem.attributes.value; - return !val || val.specified ? elem.value : elem.text; - } - }, - select: { - get: function( elem ) { - var value, i, max, option, - index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type === "select-one"; - - // Nothing was selected - if ( index < 0 ) { - return null; - } - - // Loop through all the selected options - i = one ? index : 0; - max = one ? index + 1 : options.length; - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Don't return options that are disabled or in a disabled optgroup - if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && - (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - // Fixes Bug #2551 -- select.val() broken in IE after form.reset() - if ( one && !values.length && options.length ) { - return jQuery( options[ index ] ).val(); - } - - return values; - }, - - set: function( elem, value ) { - var values = jQuery.makeArray( value ); - - jQuery(elem).find("option").each(function() { - this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; - }); - - if ( !values.length ) { - elem.selectedIndex = -1; - } - return values; - } - } - }, - - attrFn: { - val: true, - css: true, - html: true, - text: true, - data: true, - width: true, - height: true, - offset: true - }, - - attr: function( elem, name, value, pass ) { - var ret, hooks, notxml, - nType = elem.nodeType; - - // don't get/set attributes on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( pass && name in jQuery.attrFn ) { - return jQuery( elem )[ name ]( value ); - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - - // All attributes are lowercase - // Grab necessary hook if one is defined - if ( notxml ) { - name = name.toLowerCase(); - hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); - } - - if ( value !== undefined ) { - - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - - } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { - return ret; - - } else { - elem.setAttribute( name, "" + value ); - return value; - } - - } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - - ret = elem.getAttribute( name ); - - // Non-existent attributes return null, we normalize to undefined - return ret === null ? - undefined : - ret; - } - }, - - removeAttr: function( elem, value ) { - var propName, attrNames, name, l, isBool, - i = 0; - - if ( value && elem.nodeType === 1 ) { - attrNames = value.toLowerCase().split( rspace ); - l = attrNames.length; - - for ( ; i < l; i++ ) { - name = attrNames[ i ]; - - if ( name ) { - propName = jQuery.propFix[ name ] || name; - isBool = rboolean.test( name ); - - // See #9699 for explanation of this approach (setting first, then removal) - // Do not do this for boolean attributes (see #10870) - if ( !isBool ) { - jQuery.attr( elem, name, "" ); - } - elem.removeAttribute( getSetAttribute ? name : propName ); - - // Set corresponding property to false for boolean attributes - if ( isBool && propName in elem ) { - elem[ propName ] = false; - } - } - } - } - }, - - attrHooks: { - type: { - set: function( elem, value ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { - // Setting the type on a radio button after the value resets the value in IE6-9 - // Reset value to it's default in case type is set after value - // This is for element creation - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - }, - // Use the value property for back compat - // Use the nodeHook for button elements in IE6/7 (#1954) - value: { - get: function( elem, name ) { - if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { - return nodeHook.get( elem, name ); - } - return name in elem ? - elem.value : - null; - }, - set: function( elem, value, name ) { - if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { - return nodeHook.set( elem, value, name ); - } - // Does not return so that setAttribute is also used - elem.value = value; - } - } - }, - - propFix: { - tabindex: "tabIndex", - readonly: "readOnly", - "for": "htmlFor", - "class": "className", - maxlength: "maxLength", - cellspacing: "cellSpacing", - cellpadding: "cellPadding", - rowspan: "rowSpan", - colspan: "colSpan", - usemap: "useMap", - frameborder: "frameBorder", - contenteditable: "contentEditable" - }, - - prop: function( elem, name, value ) { - var ret, hooks, notxml, - nType = elem.nodeType; - - // don't get/set properties on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - - if ( notxml ) { - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { - return ret; - - } else { - return ( elem[ name ] = value ); - } - - } else { - if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - return elem[ name ]; - } - } - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - var attributeNode = elem.getAttributeNode("tabindex"); - - return attributeNode && attributeNode.specified ? - parseInt( attributeNode.value, 10 ) : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; - } - } - } -}); - -// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) -jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; - -// Hook for boolean attributes -boolHook = { - get: function( elem, name ) { - // Align boolean attributes with corresponding properties - // Fall back to attribute presence where some booleans are not supported - var attrNode, - property = jQuery.prop( elem, name ); - return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? - name.toLowerCase() : - undefined; - }, - set: function( elem, value, name ) { - var propName; - if ( value === false ) { - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - // value is true since we know at this point it's type boolean and not false - // Set boolean attributes to the same name and set the DOM property - propName = jQuery.propFix[ name ] || name; - if ( propName in elem ) { - // Only set the IDL specifically if it already exists on the element - elem[ propName ] = true; - } - - elem.setAttribute( name, name.toLowerCase() ); - } - return name; - } -}; - -// IE6/7 do not support getting/setting some attributes with get/setAttribute -if ( !getSetAttribute ) { - - fixSpecified = { - name: true, - id: true, - coords: true - }; - - // Use this for any attribute in IE6/7 - // This fixes almost every IE6/7 issue - nodeHook = jQuery.valHooks.button = { - get: function( elem, name ) { - var ret; - ret = elem.getAttributeNode( name ); - return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? - ret.nodeValue : - undefined; - }, - set: function( elem, value, name ) { - // Set the existing or create a new attribute node - var ret = elem.getAttributeNode( name ); - if ( !ret ) { - ret = document.createAttribute( name ); - elem.setAttributeNode( ret ); - } - return ( ret.nodeValue = value + "" ); - } - }; - - // Apply the nodeHook to tabindex - jQuery.attrHooks.tabindex.set = nodeHook.set; - - // Set width and height to auto instead of 0 on empty string( Bug #8150 ) - // This is for removals - jQuery.each([ "width", "height" ], function( i, name ) { - jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { - set: function( elem, value ) { - if ( value === "" ) { - elem.setAttribute( name, "auto" ); - return value; - } - } - }); - }); - - // Set contenteditable to false on removals(#10429) - // Setting to empty string throws an error as an invalid value - jQuery.attrHooks.contenteditable = { - get: nodeHook.get, - set: function( elem, value, name ) { - if ( value === "" ) { - value = "false"; - } - nodeHook.set( elem, value, name ); - } - }; -} - - -// Some attributes require a special call on IE -if ( !jQuery.support.hrefNormalized ) { - jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { - jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { - get: function( elem ) { - var ret = elem.getAttribute( name, 2 ); - return ret === null ? undefined : ret; - } - }); - }); -} - -if ( !jQuery.support.style ) { - jQuery.attrHooks.style = { - get: function( elem ) { - // Return undefined in the case of empty string - // Normalize to lowercase since IE uppercases css property names - return elem.style.cssText.toLowerCase() || undefined; - }, - set: function( elem, value ) { - return ( elem.style.cssText = "" + value ); - } - }; -} - -// Safari mis-reports the default selected property of an option -// Accessing the parent's selectedIndex property fixes it -if ( !jQuery.support.optSelected ) { - jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { - get: function( elem ) { - var parent = elem.parentNode; - - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - return null; - } - }); -} - -// IE6/7 call enctype encoding -if ( !jQuery.support.enctype ) { - jQuery.propFix.enctype = "encoding"; -} - -// Radios and checkboxes getter/setter -if ( !jQuery.support.checkOn ) { - jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - get: function( elem ) { - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - return elem.getAttribute("value") === null ? "on" : elem.value; - } - }; - }); -} -jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { - set: function( elem, value ) { - if ( jQuery.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); - } - } - }); -}); - - - - -var rformElems = /^(?:textarea|input|select)$/i, - rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, - rhoverHack = /(?:^|\s)hover(\.\S+)?\b/, - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|contextmenu)|click/, - rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, - quickParse = function( selector ) { - var quick = rquickIs.exec( selector ); - if ( quick ) { - // 0 1 2 3 - // [ _, tag, id, class ] - quick[1] = ( quick[1] || "" ).toLowerCase(); - quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); - } - return quick; - }, - quickIs = function( elem, m ) { - var attrs = elem.attributes || {}; - return ( - (!m[1] || elem.nodeName.toLowerCase() === m[1]) && - (!m[2] || (attrs.id || {}).value === m[2]) && - (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) - ); - }, - hoverHack = function( events ) { - return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); - }; - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - add: function( elem, types, handler, data, selector ) { - - var elemData, eventHandle, events, - t, tns, type, namespaces, handleObj, - handleObjIn, quick, handlers, special; - - // Don't attach events to noData or text/comment nodes (allow plain objects tho) - if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - events = elemData.events; - if ( !events ) { - elemData.events = events = {}; - } - eventHandle = elemData.handle; - if ( !eventHandle ) { - elemData.handle = eventHandle = function( e ) { - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? - jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : - undefined; - }; - // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events - eventHandle.elem = elem; - } - - // Handle multiple events separated by a space - // jQuery(...).bind("mouseover mouseout", fn); - types = jQuery.trim( hoverHack(types) ).split( " " ); - for ( t = 0; t < types.length; t++ ) { - - tns = rtypenamespace.exec( types[t] ) || []; - type = tns[1]; - namespaces = ( tns[2] || "" ).split( "." ).sort(); - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend({ - type: type, - origType: tns[1], - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - quick: selector && quickParse( selector ), - namespace: namespaces.join(".") - }, handleObjIn ); - - // Init the event handler queue if we're the first - handlers = events[ type ]; - if ( !handlers ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener/attachEvent if the special events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - // Bind the global event handler to the element - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - global: {}, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), - t, tns, type, origType, namespaces, origCount, - j, events, special, handle, eventType, handleObj; - - if ( !elemData || !(events = elemData.events) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = jQuery.trim( hoverHack( types || "" ) ).split(" "); - for ( t = 0; t < types.length; t++ ) { - tns = rtypenamespace.exec( types[t] ) || []; - type = origType = tns[1]; - namespaces = tns[2]; - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector? special.delegateType : special.bindType ) || type; - eventType = events[ type ] || []; - origCount = eventType.length; - namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; - - // Remove matching events - for ( j = 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !namespaces || namespaces.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { - eventType.splice( j--, 1 ); - - if ( handleObj.selector ) { - eventType.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( eventType.length === 0 && origCount !== eventType.length ) { - if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - handle = elemData.handle; - if ( handle ) { - handle.elem = null; - } - - // removeData also checks for emptiness and clears the expando if empty - // so use it instead of delete - jQuery.removeData( elem, [ "events", "handle" ], true ); - } - }, - - // Events that are safe to short-circuit if no handlers are attached. - // Native DOM events should not be added, they may have inline handlers. - customEvent: { - "getData": true, - "setData": true, - "changeData": true - }, - - trigger: function( event, data, elem, onlyHandlers ) { - // Don't do events on text and comment nodes - if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { - return; - } - - // Event object or event type - var type = event.type || event, - namespaces = [], - cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "!" ) >= 0 ) { - // Exclusive events trigger only for the exact event (no namespaces) - type = type.slice(0, -1); - exclusive = true; - } - - if ( type.indexOf( "." ) >= 0 ) { - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split("."); - type = namespaces.shift(); - namespaces.sort(); - } - - if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { - // No jQuery handlers for this event type, and it can't have inline handlers - return; - } - - // Caller can pass in an Event, Object, or just an event type string - event = typeof event === "object" ? - // jQuery.Event object - event[ jQuery.expando ] ? event : - // Object literal - new jQuery.Event( type, event ) : - // Just the event type (string) - new jQuery.Event( type ); - - event.type = type; - event.isTrigger = true; - event.exclusive = exclusive; - event.namespace = namespaces.join( "." ); - event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; - ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; - - // Handle a global trigger - if ( !elem ) { - - // TODO: Stop taunting the data cache; remove global events and always attach to document - cache = jQuery.cache; - for ( i in cache ) { - if ( cache[ i ].events && cache[ i ].events[ type ] ) { - jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); - } - } - return; - } - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data != null ? jQuery.makeArray( data ) : []; - data.unshift( event ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - eventPath = [[ elem, special.bindType || type ]]; - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; - old = null; - for ( ; cur; cur = cur.parentNode ) { - eventPath.push([ cur, bubbleType ]); - old = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( old && old === elem.ownerDocument ) { - eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); - } - } - - // Fire handlers on the event path - for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { - - cur = eventPath[i][0]; - event.type = eventPath[i][1]; - - handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - // Note that this is a bare JS function and not a jQuery handler - handle = ontype && cur[ ontype ]; - if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { - event.preventDefault(); - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && - !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name name as the event. - // Can't use an .isFunction() check here because IE6/7 fails that test. - // Don't do default actions on window, that's where global variables be (#6170) - // IE<9 dies on focus/blur to hidden element (#1486) - if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - old = elem[ ontype ]; - - if ( old ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - elem[ type ](); - jQuery.event.triggered = undefined; - - if ( old ) { - elem[ ontype ] = old; - } - } - } - } - - return event.result; - }, - - dispatch: function( event ) { - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( event || window.event ); - - var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), - delegateCount = handlers.delegateCount, - args = [].slice.call( arguments, 0 ), - run_all = !event.exclusive && !event.namespace, - special = jQuery.event.special[ event.type ] || {}, - handlerQueue = [], - i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[0] = event; - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers that should run if there are delegated events - // Avoid non-left-click bubbling in Firefox (#3861) - if ( delegateCount && !(event.button && event.type === "click") ) { - - // Pregenerate a single jQuery object for reuse with .is() - jqcur = jQuery(this); - jqcur.context = this.ownerDocument || this; - - for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { - - // Don't process events on disabled elements (#6911, #8165) - if ( cur.disabled !== true ) { - selMatch = {}; - matches = []; - jqcur[0] = cur; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - sel = handleObj.selector; - - if ( selMatch[ sel ] === undefined ) { - selMatch[ sel ] = ( - handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) - ); - } - if ( selMatch[ sel ] ) { - matches.push( handleObj ); - } - } - if ( matches.length ) { - handlerQueue.push({ elem: cur, matches: matches }); - } - } - } - } - - // Add the remaining (directly-bound) handlers - if ( handlers.length > delegateCount ) { - handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); - } - - // Run delegates first; they may want to stop propagation beneath us - for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { - matched = handlerQueue[ i ]; - event.currentTarget = matched.elem; - - for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { - handleObj = matched.matches[ j ]; - - // Triggered event must either 1) be non-exclusive and have no namespace, or - // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). - if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { - - event.data = handleObj.data; - event.handleObj = handleObj; - - ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) - .apply( matched.elem, args ); - - if ( ret !== undefined ) { - event.result = ret; - if ( ret === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - // Includes some event props shared by KeyEvent and MouseEvent - // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** - props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split(" "), - filter: function( event, original ) { - - // Add which for key events - if ( event.which == null ) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } - - return event; - } - }, - - mouseHooks: { - props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), - filter: function( event, original ) { - var eventDoc, doc, body, - button = original.button, - fromElement = original.fromElement; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && original.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && fromElement ) { - event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); - } - - return event; - } - }, - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, - originalEvent = event, - fixHook = jQuery.event.fixHooks[ event.type ] || {}, - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - - event = jQuery.Event( originalEvent ); - - for ( i = copy.length; i; ) { - prop = copy[ --i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) - if ( !event.target ) { - event.target = originalEvent.srcElement || document; - } - - // Target should not be a text node (#504, Safari) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) - if ( event.metaKey === undefined ) { - event.metaKey = event.ctrlKey; - } - - return fixHook.filter? fixHook.filter( event, originalEvent ) : event; - }, - - special: { - ready: { - // Make sure the ready event is setup - setup: jQuery.bindReady - }, - - load: { - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - - focus: { - delegateType: "focusin" - }, - blur: { - delegateType: "focusout" - }, - - beforeunload: { - setup: function( data, namespaces, eventHandle ) { - // We only want to do this special case on windows - if ( jQuery.isWindow( this ) ) { - this.onbeforeunload = eventHandle; - } - }, - - teardown: function( namespaces, eventHandle ) { - if ( this.onbeforeunload === eventHandle ) { - this.onbeforeunload = null; - } - } - } - }, - - simulate: function( type, elem, event, bubble ) { - // Piggyback on a donor event to simulate a different one. - // Fake originalEvent to avoid donor's stopPropagation, but if the - // simulated event prevents default then we do the same on the donor. - var e = jQuery.extend( - new jQuery.Event(), - event, - { type: type, - isSimulated: true, - originalEvent: {} - } - ); - if ( bubble ) { - jQuery.event.trigger( e, null, elem ); - } else { - jQuery.event.dispatch.call( elem, e ); - } - if ( e.isDefaultPrevented() ) { - event.preventDefault(); - } - } -}; - -// Some plugins are using, but it's undocumented/deprecated and will be removed. -// The 1.7 special event interface should provide all the hooks needed now. -jQuery.event.handle = jQuery.event.dispatch; - -jQuery.removeEvent = document.removeEventListener ? - function( elem, type, handle ) { - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle, false ); - } - } : - function( elem, type, handle ) { - if ( elem.detachEvent ) { - elem.detachEvent( "on" + type, handle ); - } - }; - -jQuery.Event = function( src, props ) { - // Allow instantiation without the 'new' keyword - if ( !(this instanceof jQuery.Event) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || - src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -function returnFalse() { - return false; -} -function returnTrue() { - return true; -} - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - preventDefault: function() { - this.isDefaultPrevented = returnTrue; - - var e = this.originalEvent; - if ( !e ) { - return; - } - - // if preventDefault exists run it on the original event - if ( e.preventDefault ) { - e.preventDefault(); - - // otherwise set the returnValue property of the original event to false (IE) - } else { - e.returnValue = false; - } - }, - stopPropagation: function() { - this.isPropagationStopped = returnTrue; - - var e = this.originalEvent; - if ( !e ) { - return; - } - // if stopPropagation exists run it on the original event - if ( e.stopPropagation ) { - e.stopPropagation(); - } - // otherwise set the cancelBubble property of the original event to true (IE) - e.cancelBubble = true; - }, - stopImmediatePropagation: function() { - this.isImmediatePropagationStopped = returnTrue; - this.stopPropagation(); - }, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse -}; - -// Create mouseenter/leave events using mouseover/out and event-time checks -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var target = this, - related = event.relatedTarget, - handleObj = event.handleObj, - selector = handleObj.selector, - ret; - - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !jQuery.contains( target, related )) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -}); - -// IE submit delegation -if ( !jQuery.support.submitBubbles ) { - - jQuery.event.special.submit = { - setup: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Lazy-add a submit handler when a descendant form may potentially be submitted - jQuery.event.add( this, "click._submit keypress._submit", function( e ) { - // Node name check avoids a VML-related crash in IE (#9807) - var elem = e.target, - form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; - if ( form && !form._submit_attached ) { - jQuery.event.add( form, "submit._submit", function( event ) { - event._submit_bubble = true; - }); - form._submit_attached = true; - } - }); - // return undefined since we don't need an event listener - }, - - postDispatch: function( event ) { - // If form was submitted by the user, bubble the event up the tree - if ( event._submit_bubble ) { - delete event._submit_bubble; - if ( this.parentNode && !event.isTrigger ) { - jQuery.event.simulate( "submit", this.parentNode, event, true ); - } - } - }, - - teardown: function() { - // Only need this for delegated form submit events - if ( jQuery.nodeName( this, "form" ) ) { - return false; - } - - // Remove delegated handlers; cleanData eventually reaps submit handlers attached above - jQuery.event.remove( this, "._submit" ); - } - }; -} - -// IE change delegation and checkbox/radio fix -if ( !jQuery.support.changeBubbles ) { - - jQuery.event.special.change = { - - setup: function() { - - if ( rformElems.test( this.nodeName ) ) { - // IE doesn't fire change on a check/radio until blur; trigger it on click - // after a propertychange. Eat the blur-change in special.change.handle. - // This still fires onchange a second time for check/radio after blur. - if ( this.type === "checkbox" || this.type === "radio" ) { - jQuery.event.add( this, "propertychange._change", function( event ) { - if ( event.originalEvent.propertyName === "checked" ) { - this._just_changed = true; - } - }); - jQuery.event.add( this, "click._change", function( event ) { - if ( this._just_changed && !event.isTrigger ) { - this._just_changed = false; - jQuery.event.simulate( "change", this, event, true ); - } - }); - } - return false; - } - // Delegated event; lazy-add a change handler on descendant inputs - jQuery.event.add( this, "beforeactivate._change", function( e ) { - var elem = e.target; - - if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { - jQuery.event.add( elem, "change._change", function( event ) { - if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { - jQuery.event.simulate( "change", this.parentNode, event, true ); - } - }); - elem._change_attached = true; - } - }); - }, - - handle: function( event ) { - var elem = event.target; - - // Swallow native change events from checkbox/radio, we already triggered them above - if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { - return event.handleObj.handler.apply( this, arguments ); - } - }, - - teardown: function() { - jQuery.event.remove( this, "._change" ); - - return rformElems.test( this.nodeName ); - } - }; -} - -// Create "bubbling" focus and blur events -if ( !jQuery.support.focusinBubbles ) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler while someone wants focusin/focusout - var attaches = 0, - handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - if ( attaches++ === 0 ) { - document.addEventListener( orig, handler, true ); - } - }, - teardown: function() { - if ( --attaches === 0 ) { - document.removeEventListener( orig, handler, true ); - } - } - }; - }); -} - -jQuery.fn.extend({ - - on: function( types, selector, data, fn, /*INTERNAL*/ one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { // && selector != null - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - this.on( type, selector, data, types[ type ], one ); - } - return this; - } - - if ( data == null && fn == null ) { - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return this; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return this.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - }); - }, - one: function( types, selector, data, fn ) { - return this.on( types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - if ( types && types.preventDefault && types.handleObj ) { - // ( event ) dispatched jQuery.Event - var handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - // ( types-object [, selector] ) - for ( var type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each(function() { - jQuery.event.remove( this, types, fn, selector ); - }); - }, - - bind: function( types, data, fn ) { - return this.on( types, null, data, fn ); - }, - unbind: function( types, fn ) { - return this.off( types, null, fn ); - }, - - live: function( types, data, fn ) { - jQuery( this.context ).on( types, this.selector, data, fn ); - return this; - }, - die: function( types, fn ) { - jQuery( this.context ).off( types, this.selector || "**", fn ); - return this; - }, - - delegate: function( selector, types, data, fn ) { - return this.on( types, selector, data, fn ); - }, - undelegate: function( selector, types, fn ) { - // ( namespace ) or ( selector, types [, fn] ) - return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - triggerHandler: function( type, data ) { - if ( this[0] ) { - return jQuery.event.trigger( type, data, this[0], true ); - } - }, - - toggle: function( fn ) { - // Save reference to arguments for access in closure - var args = arguments, - guid = fn.guid || jQuery.guid++, - i = 0, - toggler = function( event ) { - // Figure out which function to execute - var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; - jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[ lastToggle ].apply( this, arguments ) || false; - }; - - // link all the functions, so any of them can unbind this click handler - toggler.guid = guid; - while ( i < args.length ) { - args[ i++ ].guid = guid; - } - - return this.click( toggler ); - }, - - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -}); - -jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - if ( fn == null ) { - fn = data; - data = null; - } - - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }; - - if ( jQuery.attrFn ) { - jQuery.attrFn[ name ] = true; - } - - if ( rkeyEvent.test( name ) ) { - jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; - } - - if ( rmouseEvent.test( name ) ) { - jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; - } -}); - - - -/*! - * Sizzle CSS Selector Engine - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ - -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - expando = "sizcache" + (Math.random() + '').replace('.', ''), - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true, - rBackslash = /\\/g, - rReturn = /\r\n/g, - rNonWord = /\W/; - -// Here we check if the JavaScript engine is using some sort of -// optimization where it does not always call our comparision -// function. If that is the case, discard the hasDuplicate value. -// Thus far that includes Google Chrome. -[0, 0].sort(function() { - baseHasDuplicate = false; - return 0; -}); - -var Sizzle = function( selector, context, results, seed ) { - results = results || []; - context = context || document; - - var origContext = context; - - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - var m, set, checkSet, extra, ret, cur, pop, i, - prune = true, - contextXML = Sizzle.isXML( context ), - parts = [], - soFar = selector; - - // Reset the position of the chunker regexp (start from head) - do { - chunker.exec( "" ); - m = chunker.exec( soFar ); - - if ( m ) { - soFar = m[3]; - - parts.push( m[1] ); - - if ( m[2] ) { - extra = m[3]; - break; - } - } - } while ( m ); - - if ( parts.length > 1 && origPOS.exec( selector ) ) { - - if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context, seed ); - - } else { - set = Expr.relative[ parts[0] ] ? - [ context ] : - Sizzle( parts.shift(), context ); - - while ( parts.length ) { - selector = parts.shift(); - - if ( Expr.relative[ selector ] ) { - selector += parts.shift(); - } - - set = posProcess( selector, set, seed ); - } - } - - } else { - // Take a shortcut and set the context if the root selector is an ID - // (but not if it'll be faster if the inner selector is an ID) - if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - - ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? - Sizzle.filter( ret.expr, ret.set )[0] : - ret.set[0]; - } - - if ( context ) { - ret = seed ? - { expr: parts.pop(), set: makeArray(seed) } : - Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - - set = ret.expr ? - Sizzle.filter( ret.expr, ret.set ) : - ret.set; - - if ( parts.length > 0 ) { - checkSet = makeArray( set ); - - } else { - prune = false; - } - - while ( parts.length ) { - cur = parts.pop(); - pop = cur; - - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } - - if ( pop == null ) { - pop = context; - } - - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - - } else { - checkSet = parts = []; - } - } - - if ( !checkSet ) { - checkSet = set; - } - - if ( !checkSet ) { - Sizzle.error( cur || selector ); - } - - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - - } else if ( context && context.nodeType === 1 ) { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - - } else { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } - } - } - - } else { - makeArray( checkSet, results ); - } - - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } - - return results; -}; - -Sizzle.uniqueSort = function( results ) { - if ( sortOrder ) { - hasDuplicate = baseHasDuplicate; - results.sort( sortOrder ); - - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[ i - 1 ] ) { - results.splice( i--, 1 ); - } - } - } - } - - return results; -}; - -Sizzle.matches = function( expr, set ) { - return Sizzle( expr, null, null, set ); -}; - -Sizzle.matchesSelector = function( node, expr ) { - return Sizzle( expr, null, null, [node] ).length > 0; -}; - -Sizzle.find = function( expr, context, isXML ) { - var set, i, len, match, type, left; - - if ( !expr ) { - return []; - } - - for ( i = 0, len = Expr.order.length; i < len; i++ ) { - type = Expr.order[i]; - - if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - left = match[1]; - match.splice( 1, 1 ); - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace( rBackslash, "" ); - set = Expr.find[ type ]( match, context, isXML ); - - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } - - if ( !set ) { - set = typeof context.getElementsByTagName !== "undefined" ? - context.getElementsByTagName( "*" ) : - []; - } - - return { set: set, expr: expr }; -}; - -Sizzle.filter = function( expr, set, inplace, not ) { - var match, anyFound, - type, found, item, filter, left, - i, pass, - old = expr, - result = [], - curLoop = set, - isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); - - while ( expr && set.length ) { - for ( type in Expr.filter ) { - if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - filter = Expr.filter[ type ]; - left = match[1]; - - anyFound = false; - - match.splice(1,1); - - if ( left.substr( left.length - 1 ) === "\\" ) { - continue; - } - - if ( curLoop === result ) { - result = []; - } - - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); - - if ( !match ) { - anyFound = found = true; - - } else if ( match === true ) { - continue; - } - } - - if ( match ) { - for ( i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - pass = not ^ found; - - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - - } else { - curLoop[i] = false; - } - - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } - - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; - } - - expr = expr.replace( Expr.match[ type ], "" ); - - if ( !anyFound ) { - return []; - } - - break; - } - } - } - - // Improper expression - if ( expr === old ) { - if ( anyFound == null ) { - Sizzle.error( expr ); - - } else { - break; - } - } - - old = expr; - } - - return curLoop; -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Utility function for retreiving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -var getText = Sizzle.getText = function( elem ) { - var i, node, - nodeType = elem.nodeType, - ret = ""; - - if ( nodeType ) { - if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent || innerText for elements - if ( typeof elem.textContent === 'string' ) { - return elem.textContent; - } else if ( typeof elem.innerText === 'string' ) { - // Replace IE's carriage returns - return elem.innerText.replace( rReturn, '' ); - } else { - // Traverse it's children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - } else { - - // If no nodeType, this is expected to be an array - for ( i = 0; (node = elem[i]); i++ ) { - // Do not traverse comment nodes - if ( node.nodeType !== 8 ) { - ret += getText( node ); - } - } - } - return ret; -}; - -var Expr = Sizzle.selectors = { - order: [ "ID", "NAME", "TAG" ], - - match: { - ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ - }, - - leftMatch: {}, - - attrMap: { - "class": "className", - "for": "htmlFor" - }, - - attrHandle: { - href: function( elem ) { - return elem.getAttribute( "href" ); - }, - type: function( elem ) { - return elem.getAttribute( "type" ); - } - }, - - relative: { - "+": function(checkSet, part){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !rNonWord.test( part ), - isPartStrNotTag = isPartStr && !isTag; - - if ( isTag ) { - part = part.toLowerCase(); - } - - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - - checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? - elem || false : - elem === part; - } - } - - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); - } - }, - - ">": function( checkSet, part ) { - var elem, - isPartStr = typeof part === "string", - i = 0, - l = checkSet.length; - - if ( isPartStr && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - - for ( ; i < l; i++ ) { - elem = checkSet[i]; - - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; - } - } - - } else { - for ( ; i < l; i++ ) { - elem = checkSet[i]; - - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } - } - }, - - "": function(checkSet, part, isXML){ - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if ( typeof part === "string" && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); - }, - - "~": function( checkSet, part, isXML ) { - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if ( typeof part === "string" && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); - } - }, - - find: { - ID: function( match, context, isXML ) { - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }, - - NAME: function( match, context ) { - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], - results = context.getElementsByName( match[1] ); - - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } - - return ret.length === 0 ? null : ret; - } - }, - - TAG: function( match, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( match[1] ); - } - } - }, - preFilter: { - CLASS: function( match, curLoop, inplace, result, not, isXML ) { - match = " " + match[1].replace( rBackslash, "" ) + " "; - - if ( isXML ) { - return match; - } - - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { - if ( !inplace ) { - result.push( elem ); - } - - } else if ( inplace ) { - curLoop[i] = false; - } - } - } - - return false; - }, - - ID: function( match ) { - return match[1].replace( rBackslash, "" ); - }, - - TAG: function( match, curLoop ) { - return match[1].replace( rBackslash, "" ).toLowerCase(); - }, - - CHILD: function( match ) { - if ( match[1] === "nth" ) { - if ( !match[2] ) { - Sizzle.error( match[0] ); - } - - match[2] = match[2].replace(/^\+|\s*/g, ''); - - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( - match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - - // calculate the numbers (first)n+(last) including if they are negative - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - else if ( match[2] ) { - Sizzle.error( match[0] ); - } - - // TODO: Move to normal caching system - match[0] = done++; - - return match; - }, - - ATTR: function( match, curLoop, inplace, result, not, isXML ) { - var name = match[1] = match[1].replace( rBackslash, "" ); - - if ( !isXML && Expr.attrMap[name] ) { - match[1] = Expr.attrMap[name]; - } - - // Handle if an un-quoted value was used - match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); - - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; - } - - return match; - }, - - PSEUDO: function( match, curLoop, inplace, result, not ) { - if ( match[1] === "not" ) { - // If we're dealing with a complex expression, or a simple one - if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { - match[3] = Sizzle(match[3], null, null, curLoop); - - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - - if ( !inplace ) { - result.push.apply( result, ret ); - } - - return false; - } - - } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { - return true; - } - - return match; - }, - - POS: function( match ) { - match.unshift( true ); - - return match; - } - }, - - filters: { - enabled: function( elem ) { - return elem.disabled === false && elem.type !== "hidden"; - }, - - disabled: function( elem ) { - return elem.disabled === true; - }, - - checked: function( elem ) { - return elem.checked === true; - }, - - selected: function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - parent: function( elem ) { - return !!elem.firstChild; - }, - - empty: function( elem ) { - return !elem.firstChild; - }, - - has: function( elem, i, match ) { - return !!Sizzle( match[3], elem ).length; - }, - - header: function( elem ) { - return (/h\d/i).test( elem.nodeName ); - }, - - text: function( elem ) { - var attr = elem.getAttribute( "type" ), type = elem.type; - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); - }, - - radio: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; - }, - - checkbox: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; - }, - - file: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; - }, - - password: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; - }, - - submit: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && "submit" === elem.type; - }, - - image: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; - }, - - reset: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && "reset" === elem.type; - }, - - button: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && "button" === elem.type || name === "button"; - }, - - input: function( elem ) { - return (/input|select|textarea|button/i).test( elem.nodeName ); - }, - - focus: function( elem ) { - return elem === elem.ownerDocument.activeElement; - } - }, - setFilters: { - first: function( elem, i ) { - return i === 0; - }, - - last: function( elem, i, match, array ) { - return i === array.length - 1; - }, - - even: function( elem, i ) { - return i % 2 === 0; - }, - - odd: function( elem, i ) { - return i % 2 === 1; - }, - - lt: function( elem, i, match ) { - return i < match[3] - 0; - }, - - gt: function( elem, i, match ) { - return i > match[3] - 0; - }, - - nth: function( elem, i, match ) { - return match[3] - 0 === i; - }, - - eq: function( elem, i, match ) { - return match[3] - 0 === i; - } - }, - filter: { - PSEUDO: function( elem, match, i, array ) { - var name = match[1], - filter = Expr.filters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; - - } else if ( name === "not" ) { - var not = match[3]; - - for ( var j = 0, l = not.length; j < l; j++ ) { - if ( not[j] === elem ) { - return false; - } - } - - return true; - - } else { - Sizzle.error( name ); - } - }, - - CHILD: function( elem, match ) { - var first, last, - doneName, parent, cache, - count, diff, - type = match[1], - node = elem; - - switch ( type ) { - case "only": - case "first": - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - if ( type === "first" ) { - return true; - } - - node = elem; - - /* falls through */ - case "last": - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - return true; - - case "nth": - first = match[2]; - last = match[3]; - - if ( first === 1 && last === 0 ) { - return true; - } - - doneName = match[0]; - parent = elem.parentNode; - - if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { - count = 0; - - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - - parent[ expando ] = doneName; - } - - diff = elem.nodeIndex - last; - - if ( first === 0 ) { - return diff === 0; - - } else { - return ( diff % first === 0 && diff / first >= 0 ); - } - } - }, - - ID: function( elem, match ) { - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - - TAG: function( elem, match ) { - return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; - }, - - CLASS: function( elem, match ) { - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - - ATTR: function( elem, match ) { - var name = match[1], - result = Sizzle.attr ? - Sizzle.attr( elem, name ) : - Expr.attrHandle[ name ] ? - Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - - return result == null ? - type === "!=" : - !type && Sizzle.attr ? - result != null : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value !== check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - - POS: function( elem, match, i, array ) { - var name = match[2], - filter = Expr.setFilters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } - } - } -}; - -var origPOS = Expr.match.POS, - fescape = function(all, num){ - return "\\" + (num - 0 + 1); - }; - -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); - Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); -} -// Expose origPOS -// "global" as in regardless of relation to brackets/parens -Expr.match.globalPOS = origPOS; - -var makeArray = function( array, results ) { - array = Array.prototype.slice.call( array, 0 ); - - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; - -// Perform a simple check to determine if the browser is capable of -// converting a NodeList to an array using builtin methods. -// Also verifies that the returned array holds DOM nodes -// (which is not the case in the Blackberry browser) -try { - Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; - -// Provide a fallback method if it does not work -} catch( e ) { - makeArray = function( array, results ) { - var i = 0, - ret = results || []; - - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - - } else { - if ( typeof array.length === "number" ) { - for ( var l = array.length; i < l; i++ ) { - ret.push( array[i] ); - } - - } else { - for ( ; array[i]; i++ ) { - ret.push( array[i] ); - } - } - } - - return ret; - }; -} - -var sortOrder, siblingCheck; - -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { - return a.compareDocumentPosition ? -1 : 1; - } - - return a.compareDocumentPosition(b) & 4 ? -1 : 1; - }; - -} else { - sortOrder = function( a, b ) { - // The nodes are identical, we can exit early - if ( a === b ) { - hasDuplicate = true; - return 0; - - // Fallback to using sourceIndex (in IE) if it's available on both nodes - } else if ( a.sourceIndex && b.sourceIndex ) { - return a.sourceIndex - b.sourceIndex; - } - - var al, bl, - ap = [], - bp = [], - aup = a.parentNode, - bup = b.parentNode, - cur = aup; - - // If the nodes are siblings (or identical) we can do a quick check - if ( aup === bup ) { - return siblingCheck( a, b ); - - // If no parents were found then the nodes are disconnected - } else if ( !aup ) { - return -1; - - } else if ( !bup ) { - return 1; - } - - // Otherwise they're somewhere else in the tree so we need - // to build up a full list of the parentNodes for comparison - while ( cur ) { - ap.unshift( cur ); - cur = cur.parentNode; - } - - cur = bup; - - while ( cur ) { - bp.unshift( cur ); - cur = cur.parentNode; - } - - al = ap.length; - bl = bp.length; - - // Start walking down the tree looking for a discrepancy - for ( var i = 0; i < al && i < bl; i++ ) { - if ( ap[i] !== bp[i] ) { - return siblingCheck( ap[i], bp[i] ); - } - } - - // We ended someplace up the tree so do a sibling check - return i === al ? - siblingCheck( a, bp[i], -1 ) : - siblingCheck( ap[i], b, 1 ); - }; - - siblingCheck = function( a, b, ret ) { - if ( a === b ) { - return ret; - } - - var cur = a.nextSibling; - - while ( cur ) { - if ( cur === b ) { - return -1; - } - - cur = cur.nextSibling; - } - - return 1; - }; -} - -// Check to see if the browser returns elements by name when -// querying by getElementById (and provide a workaround) -(function(){ - // We're going to inject a fake input element with a specified name - var form = document.createElement("div"), - id = "script" + (new Date()).getTime(), - root = document.documentElement; - - form.innerHTML = ""; - - // Inject it into the root element, check its status, and remove it quickly - root.insertBefore( form, root.firstChild ); - - // The workaround has to do additional checks after a getElementById - // Which slows things down for other browsers (hence the branching) - if ( document.getElementById( id ) ) { - Expr.find.ID = function( match, context, isXML ) { - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - - return m ? - m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? - [m] : - undefined : - []; - } - }; - - Expr.filter.ID = function( elem, match ) { - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - - root.removeChild( form ); - - // release memory in IE - root = form = null; -})(); - -(function(){ - // Check to see if the browser returns only elements - // when doing getElementsByTagName("*") - - // Create a fake element - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); - - // Make sure no comments are found - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function( match, context ) { - var results = context.getElementsByTagName( match[1] ); - - // Filter out possible comments - if ( match[1] === "*" ) { - var tmp = []; - - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } - - results = tmp; - } - - return results; - }; - } - - // Check to see if an attribute returns normalized href attributes - div.innerHTML = ""; - - if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#" ) { - - Expr.attrHandle.href = function( elem ) { - return elem.getAttribute( "href", 2 ); - }; - } - - // release memory in IE - div = null; -})(); - -if ( document.querySelectorAll ) { - (function(){ - var oldSizzle = Sizzle, - div = document.createElement("div"), - id = "__sizzle__"; - - div.innerHTML = "

"; - - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function( query, context, extra, seed ) { - context = context || document; - - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && !Sizzle.isXML(context) ) { - // See if we find a selector to speed up - var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); - - if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { - // Speed-up: Sizzle("TAG") - if ( match[1] ) { - return makeArray( context.getElementsByTagName( query ), extra ); - - // Speed-up: Sizzle(".CLASS") - } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { - return makeArray( context.getElementsByClassName( match[2] ), extra ); - } - } - - if ( context.nodeType === 9 ) { - // Speed-up: Sizzle("body") - // The body element only exists once, optimize finding it - if ( query === "body" && context.body ) { - return makeArray( [ context.body ], extra ); - - // Speed-up: Sizzle("#ID") - } else if ( match && match[3] ) { - var elem = context.getElementById( match[3] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id === match[3] ) { - return makeArray( [ elem ], extra ); - } - - } else { - return makeArray( [], extra ); - } - } - - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(qsaError) {} - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - var oldContext = context, - old = context.getAttribute( "id" ), - nid = old || id, - hasParent = context.parentNode, - relativeHierarchySelector = /^\s*[+~]/.test( query ); - - if ( !old ) { - context.setAttribute( "id", nid ); - } else { - nid = nid.replace( /'/g, "\\$&" ); - } - if ( relativeHierarchySelector && hasParent ) { - context = context.parentNode; - } - - try { - if ( !relativeHierarchySelector || hasParent ) { - return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); - } - - } catch(pseudoError) { - } finally { - if ( !old ) { - oldContext.removeAttribute( "id" ); - } - } - } - } - - return oldSizzle(query, context, extra, seed); - }; - - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } - - // release memory in IE - div = null; - })(); -} - -(function(){ - var html = document.documentElement, - matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; - - if ( matches ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9 fails this) - var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), - pseudoWorks = false; - - try { - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( document.documentElement, "[test!='']:sizzle" ); - - } catch( pseudoError ) { - pseudoWorks = true; - } - - Sizzle.matchesSelector = function( node, expr ) { - // Make sure that attribute selectors are quoted - expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); - - if ( !Sizzle.isXML( node ) ) { - try { - if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { - var ret = matches.call( node, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || !disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9, so check for that - node.document && node.document.nodeType !== 11 ) { - return ret; - } - } - } catch(e) {} - } - - return Sizzle(expr, null, null, [node]).length > 0; - }; - } -})(); - -(function(){ - var div = document.createElement("div"); - - div.innerHTML = "
"; - - // Opera can't find a second classname (in 9.6) - // Also, make sure that getElementsByClassName actually exists - if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { - return; - } - - // Safari caches class attributes, doesn't catch changes (in 3.2) - div.lastChild.className = "e"; - - if ( div.getElementsByClassName("e").length === 1 ) { - return; - } - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function( match, context, isXML ) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; - - // release memory in IE - div = null; -})(); - -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - - if ( elem ) { - var match = false; - - elem = elem[dir]; - - while ( elem ) { - if ( elem[ expando ] === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 && !isXML ){ - elem[ expando ] = doneName; - elem.sizset = i; - } - - if ( elem.nodeName.toLowerCase() === cur ) { - match = elem; - break; - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - - if ( elem ) { - var match = false; - - elem = elem[dir]; - - while ( elem ) { - if ( elem[ expando ] === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem[ expando ] = doneName; - elem.sizset = i; - } - - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } - - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -if ( document.documentElement.contains ) { - Sizzle.contains = function( a, b ) { - return a !== b && (a.contains ? a.contains(b) : true); - }; - -} else if ( document.documentElement.compareDocumentPosition ) { - Sizzle.contains = function( a, b ) { - return !!(a.compareDocumentPosition(b) & 16); - }; - -} else { - Sizzle.contains = function() { - return false; - }; -} - -Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; - - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -var posProcess = function( selector, context, seed ) { - var match, - tmpSet = [], - later = "", - root = context.nodeType ? [context] : context; - - // Position selectors must be done after the filter - // And so must :not(positional) so we move all PSEUDOs to the end - while ( (match = Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace( Expr.match.PSEUDO, "" ); - } - - selector = Expr.relative[selector] ? selector + "*" : selector; - - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet, seed ); - } - - return Sizzle.filter( later, tmpSet ); -}; - -// EXPOSE -// Override sizzle attribute retrieval -Sizzle.attr = jQuery.attr; -Sizzle.selectors.attrMap = {}; -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.filters; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - -})(); - - -var runtil = /Until$/, - rparentsprev = /^(?:parents|prevUntil|prevAll)/, - // Note: This RegExp should be improved, or likely pulled from Sizzle - rmultiselector = /,/, - isSimple = /^.[^:#\[\.,]*$/, - slice = Array.prototype.slice, - POS = jQuery.expr.match.globalPOS, - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend({ - find: function( selector ) { - var self = this, - i, l; - - if ( typeof selector !== "string" ) { - return jQuery( selector ).filter(function() { - for ( i = 0, l = self.length; i < l; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - }); - } - - var ret = this.pushStack( "", "find", selector ), - length, n, r; - - for ( i = 0, l = this.length; i < l; i++ ) { - length = ret.length; - jQuery.find( selector, this[i], ret ); - - if ( i > 0 ) { - // Make sure that the results are unique - for ( n = length; n < ret.length; n++ ) { - for ( r = 0; r < length; r++ ) { - if ( ret[r] === ret[n] ) { - ret.splice(n--, 1); - break; - } - } - } - } - } - - return ret; - }, - - has: function( target ) { - var targets = jQuery( target ); - return this.filter(function() { - for ( var i = 0, l = targets.length; i < l; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - not: function( selector ) { - return this.pushStack( winnow(this, selector, false), "not", selector); - }, - - filter: function( selector ) { - return this.pushStack( winnow(this, selector, true), "filter", selector ); - }, - - is: function( selector ) { - return !!selector && ( - typeof selector === "string" ? - // If this is a positional selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - POS.test( selector ) ? - jQuery( selector, this.context ).index( this[0] ) >= 0 : - jQuery.filter( selector, this ).length > 0 : - this.filter( selector ).length > 0 ); - }, - - closest: function( selectors, context ) { - var ret = [], i, l, cur = this[0]; - - // Array (deprecated as of jQuery 1.7) - if ( jQuery.isArray( selectors ) ) { - var level = 1; - - while ( cur && cur.ownerDocument && cur !== context ) { - for ( i = 0; i < selectors.length; i++ ) { - - if ( jQuery( cur ).is( selectors[ i ] ) ) { - ret.push({ selector: selectors[ i ], elem: cur, level: level }); - } - } - - cur = cur.parentNode; - level++; - } - - return ret; - } - - // String - var pos = POS.test( selectors ) || typeof selectors !== "string" ? - jQuery( selectors, context || this.context ) : - 0; - - for ( i = 0, l = this.length; i < l; i++ ) { - cur = this[i]; - - while ( cur ) { - if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { - ret.push( cur ); - break; - - } else { - cur = cur.parentNode; - if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { - break; - } - } - } - } - - ret = ret.length > 1 ? jQuery.unique( ret ) : ret; - - return this.pushStack( ret, "closest", selectors ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; - } - - // index in selector - if ( typeof elem === "string" ) { - return jQuery.inArray( this[0], jQuery( elem ) ); - } - - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[0] : elem, this ); - }, - - add: function( selector, context ) { - var set = typeof selector === "string" ? - jQuery( selector, context ) : - jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), - all = jQuery.merge( this.get(), set ); - - return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? - all : - jQuery.unique( all ) ); - }, - - andSelf: function() { - return this.add( this.prevObject ); - } -}); - -// A painfully simple check to see if an element is disconnected -// from a document (should be improved, where feasible). -function isDisconnected( node ) { - return !node || !node.parentNode || node.parentNode.nodeType === 11; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return jQuery.nth( elem, 2, "nextSibling" ); - }, - prev: function( elem ) { - return jQuery.nth( elem, 2, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return jQuery.nodeName( elem, "iframe" ) ? - elem.contentDocument || elem.contentWindow.document : - jQuery.makeArray( elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); - - if ( !runtil.test( name ) ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - ret = jQuery.filter( selector, ret ); - } - - ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; - - if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { - ret = ret.reverse(); - } - - return this.pushStack( ret, name, slice.call( arguments ).join(",") ); - }; -}); - -jQuery.extend({ - filter: function( expr, elems, not ) { - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 ? - jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : - jQuery.find.matches(expr, elems); - }, - - dir: function( elem, dir, until ) { - var matched = [], - cur = elem[ dir ]; - - while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { - if ( cur.nodeType === 1 ) { - matched.push( cur ); - } - cur = cur[dir]; - } - return matched; - }, - - nth: function( cur, result, dir, elem ) { - result = result || 1; - var num = 0; - - for ( ; cur; cur = cur[dir] ) { - if ( cur.nodeType === 1 && ++num === result ) { - break; - } - } - - return cur; - }, - - sibling: function( n, elem ) { - var r = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - r.push( n ); - } - } - - return r; - } -}); - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, keep ) { - - // Can't pass null or undefined to indexOf in Firefox 4 - // Set to 0 to skip string check - qualifier = qualifier || 0; - - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep(elements, function( elem, i ) { - var retVal = !!qualifier.call( elem, i, elem ); - return retVal === keep; - }); - - } else if ( qualifier.nodeType ) { - return jQuery.grep(elements, function( elem, i ) { - return ( elem === qualifier ) === keep; - }); - - } else if ( typeof qualifier === "string" ) { - var filtered = jQuery.grep(elements, function( elem ) { - return elem.nodeType === 1; - }); - - if ( isSimple.test( qualifier ) ) { - return jQuery.filter(qualifier, filtered, !keep); - } else { - qualifier = jQuery.filter( qualifier, filtered ); - } - } - - return jQuery.grep(elements, function( elem, i ) { - return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; - }); -} - - - - -function createSafeFragment( document ) { - var list = nodeNames.split( "|" ), - safeFrag = document.createDocumentFragment(); - - if ( safeFrag.createElement ) { - while ( list.length ) { - safeFrag.createElement( - list.pop() - ); - } - } - return safeFrag; -} - -var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + - "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", - rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, - rleadingWhitespace = /^\s+/, - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, - rtagName = /<([\w:]+)/, - rtbody = /]", "i"), - // checked="checked" or checked - rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - rscriptType = /\/(java|ecma)script/i, - rcleanScript = /^\s*", "" ], - legend: [ 1, "
", "
" ], - thead: [ 1, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - col: [ 2, "", "
" ], - area: [ 1, "", "" ], - _default: [ 0, "", "" ] - }, - safeFragment = createSafeFragment( document ); - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// IE can't serialize and - - - - - - - - -
-
-
-
- - -

Index

- -
- -
- - -
-
-
-
-
- - - - - -
-
-
-
- - - - \ No newline at end of file diff --git a/build/html/index.html b/build/html/index.html deleted file mode 100644 index f04970de4..000000000 --- a/build/html/index.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - Welcome to Chill documentation! — chill-doc 1.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -
-

Welcome to Chill documentation!¶

-

Chill is a free software for social workers.

-

Chill rely on the php framework Symfony. If you know symfony, you should not be lost. Symfony has a very good documentation.

-

Contents of this documentation:

-
- -
-
-
-

Indices and tables¶

- -
- - -
-
-
-
-
-

Table Of Contents

- - -

Next topic

-

Installation

-

This Page

- - - -
-
-
-
- - - - \ No newline at end of file diff --git a/build/html/installation/installation.html b/build/html/installation/installation.html deleted file mode 100644 index fc7dc7453..000000000 --- a/build/html/installation/installation.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - Installation — chill-doc 1.0.0 documentation - - - - - - - - - - - - - - -
-
-
-
- -
-

Installation¶

-

Chill is installed with composer.

-

Install composer on your system :

-
curl -sS https://getcomposer.org/installer | php
-
-
-

move composer.phar to your system.

-
- - -
-
-
-
-
-

Previous topic

-

Welcome to Chill documentation!

-

This Page

- - - -
-
-
-
- - - - \ No newline at end of file diff --git a/build/html/objects.inv b/build/html/objects.inv deleted file mode 100644 index 03801778c6e665866f4a387483e3861df1e59e48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 205 zcmY#Z2rkIT%&Sny%qvUHE6FdaR47X=D$dN$Q!wIERtPA{&q_@$u~JCR$jr&nP03FN z3Wh-xSSc9l834H<8L0|Iskw=nc`2zy3i)XYB^jB;3Tc@+sR}?kIX}0cD7CmaHASJc zI5RI@p(-acNsp`ImbRa-j@P*W4L#3|r#yYU&IVpE4bfohewjMu$ukXY7H1I_m1WC| zo<3Wi@@(mnumxKBE3UM9I(+@{Z26RDOSCGQT6SCt{=6Zhsne;{VG0w&x*LX)0I8Z$ ALjV8( diff --git a/build/html/search.html b/build/html/search.html deleted file mode 100644 index fe0ece0c6..000000000 --- a/build/html/search.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - Search — chill-doc 1.0.0 documentation - - - - - - - - - - - - - - - - - - - -
-
-
-
- -

Search

-
- -

- Please activate JavaScript to enable the search - functionality. -

-
-

- From here you can search these documents. Enter your search - words into the box below and click "search". Note that the search - function will automatically search for all of the words. Pages - containing fewer words won't appear in the result list. -

-
- - - -
- -
- -
- -
-
-
-
-
-
-
-
-
- - - - \ No newline at end of file diff --git a/build/html/searchindex.js b/build/html/searchindex.js deleted file mode 100644 index bf0f5f612..000000000 --- a/build/html/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({envversion:43,objects:{},titles:["Welcome to Chill documentation!","Installation"],objnames:{},filenames:["index","installation/installation"],titleterms:{indic:0,document:0,tabl:0,welcom:0,instal:1,doc:[],chill:0},terms:{framework:0,worker:0,thi:0,veri:0,http:1,softwar:0,search:0,page:0,system:1,know:0,lost:0,index:0,compos:1,symfoni:0,free:0,phar:1,curl:1,you:0,instal:0,php:[0,1],org:1,good:0,should:0,your:1,content:0,modul:0,social:0,move:1,reli:0,getcompos:1},objtypes:{}}) \ No newline at end of file From 463dfb0bd606fce8a55115326b8bbe10d2f8ccaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 29 Sep 2014 18:38:51 +0200 Subject: [PATCH 003/157] index page --- source/index.rst | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/source/index.rst b/source/index.rst index dbe52a35b..ef060f4e2 100644 --- a/source/index.rst +++ b/source/index.rst @@ -18,11 +18,19 @@ Contents of this documentation: installation/installation.rst +Contribute +========== -Indices and tables -================== +* `Issue tracker `_ (the certificate is available `here `_) +* source code is available here : -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + * Chill-standard : https://github.com/Champs-Libres/chill-standard + * Chill-main : https://github.com/Champs-Libres/ChillMain +Licence +======== + +The project is available under the `GPLv3 licence`_. + + +.. _GPLv3 licence: http://www.gnu.org/licenses/gpl-3.0.txt From ddea18ebd147180720f1bd9f1c1de4d5a3e1c679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 29 Sep 2014 20:14:28 +0200 Subject: [PATCH 004/157] add readme and license --- LICENSE | 451 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 26 ++++ 2 files changed, 477 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..2f7e03ca5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,451 @@ + + GNU Free Documentation License + Version 1.3, 3 November 2008 + + + Copyright (C) 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +0. PREAMBLE + +The purpose of this License is to make a manual, textbook, or other +functional and useful document "free" in the sense of freedom: to +assure everyone the effective freedom to copy and redistribute it, +with or without modifying it, either commercially or noncommercially. +Secondarily, this License preserves for the author and publisher a way +to get credit for their work, while not being considered responsible +for modifications made by others. + +This License is a kind of "copyleft", which means that derivative +works of the document must themselves be free in the same sense. It +complements the GNU General Public License, which is a copyleft +license designed for free software. + +We have designed this License in order to use it for manuals for free +software, because free software needs free documentation: a free +program should come with manuals providing the same freedoms that the +software does. But this License is not limited to software manuals; +it can be used for any textual work, regardless of subject matter or +whether it is published as a printed book. We recommend this License +principally for works whose purpose is instruction or reference. + + +1. APPLICABILITY AND DEFINITIONS + +This License applies to any manual or other work, in any medium, that +contains a notice placed by the copyright holder saying it can be +distributed under the terms of this License. Such a notice grants a +world-wide, royalty-free license, unlimited in duration, to use that +work under the conditions stated herein. The "Document", below, +refers to any such manual or work. Any member of the public is a +licensee, and is addressed as "you". You accept the license if you +copy, modify or distribute the work in a way requiring permission +under copyright law. + +A "Modified Version" of the Document means any work containing the +Document or a portion of it, either copied verbatim, or with +modifications and/or translated into another language. + +A "Secondary Section" is a named appendix or a front-matter section of +the Document that deals exclusively with the relationship of the +publishers or authors of the Document to the Document's overall +subject (or to related matters) and contains nothing that could fall +directly within that overall subject. (Thus, if the Document is in +part a textbook of mathematics, a Secondary Section may not explain +any mathematics.) The relationship could be a matter of historical +connection with the subject or with related matters, or of legal, +commercial, philosophical, ethical or political position regarding +them. + +The "Invariant Sections" are certain Secondary Sections whose titles +are designated, as being those of Invariant Sections, in the notice +that says that the Document is released under this License. If a +section does not fit the above definition of Secondary then it is not +allowed to be designated as Invariant. The Document may contain zero +Invariant Sections. If the Document does not identify any Invariant +Sections then there are none. + +The "Cover Texts" are certain short passages of text that are listed, +as Front-Cover Texts or Back-Cover Texts, in the notice that says that +the Document is released under this License. A Front-Cover Text may +be at most 5 words, and a Back-Cover Text may be at most 25 words. + +A "Transparent" copy of the Document means a machine-readable copy, +represented in a format whose specification is available to the +general public, that is suitable for revising the document +straightforwardly with generic text editors or (for images composed of +pixels) generic paint programs or (for drawings) some widely available +drawing editor, and that is suitable for input to text formatters or +for automatic translation to a variety of formats suitable for input +to text formatters. A copy made in an otherwise Transparent file +format whose markup, or absence of markup, has been arranged to thwart +or discourage subsequent modification by readers is not Transparent. +An image format is not Transparent if used for any substantial amount +of text. A copy that is not "Transparent" is called "Opaque". + +Examples of suitable formats for Transparent copies include plain +ASCII without markup, Texinfo input format, LaTeX input format, SGML +or XML using a publicly available DTD, and standard-conforming simple +HTML, PostScript or PDF designed for human modification. Examples of +transparent image formats include PNG, XCF and JPG. Opaque formats +include proprietary formats that can be read and edited only by +proprietary word processors, SGML or XML for which the DTD and/or +processing tools are not generally available, and the +machine-generated HTML, PostScript or PDF produced by some word +processors for output purposes only. + +The "Title Page" means, for a printed book, the title page itself, +plus such following pages as are needed to hold, legibly, the material +this License requires to appear in the title page. For works in +formats which do not have any title page as such, "Title Page" means +the text near the most prominent appearance of the work's title, +preceding the beginning of the body of the text. + +The "publisher" means any person or entity that distributes copies of +the Document to the public. + +A section "Entitled XYZ" means a named subunit of the Document whose +title either is precisely XYZ or contains XYZ in parentheses following +text that translates XYZ in another language. (Here XYZ stands for a +specific section name mentioned below, such as "Acknowledgements", +"Dedications", "Endorsements", or "History".) To "Preserve the Title" +of such a section when you modify the Document means that it remains a +section "Entitled XYZ" according to this definition. + +The Document may include Warranty Disclaimers next to the notice which +states that this License applies to the Document. These Warranty +Disclaimers are considered to be included by reference in this +License, but only as regards disclaiming warranties: any other +implication that these Warranty Disclaimers may have is void and has +no effect on the meaning of this License. + +2. VERBATIM COPYING + +You may copy and distribute the Document in any medium, either +commercially or noncommercially, provided that this License, the +copyright notices, and the license notice saying this License applies +to the Document are reproduced in all copies, and that you add no +other conditions whatsoever to those of this License. You may not use +technical measures to obstruct or control the reading or further +copying of the copies you make or distribute. However, you may accept +compensation in exchange for copies. If you distribute a large enough +number of copies you must also follow the conditions in section 3. + +You may also lend copies, under the same conditions stated above, and +you may publicly display copies. + + +3. COPYING IN QUANTITY + +If you publish printed copies (or copies in media that commonly have +printed covers) of the Document, numbering more than 100, and the +Document's license notice requires Cover Texts, you must enclose the +copies in covers that carry, clearly and legibly, all these Cover +Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on +the back cover. Both covers must also clearly and legibly identify +you as the publisher of these copies. The front cover must present +the full title with all words of the title equally prominent and +visible. You may add other material on the covers in addition. +Copying with changes limited to the covers, as long as they preserve +the title of the Document and satisfy these conditions, can be treated +as verbatim copying in other respects. + +If the required texts for either cover are too voluminous to fit +legibly, you should put the first ones listed (as many as fit +reasonably) on the actual cover, and continue the rest onto adjacent +pages. + +If you publish or distribute Opaque copies of the Document numbering +more than 100, you must either include a machine-readable Transparent +copy along with each Opaque copy, or state in or with each Opaque copy +a computer-network location from which the general network-using +public has access to download using public-standard network protocols +a complete Transparent copy of the Document, free of added material. +If you use the latter option, you must take reasonably prudent steps, +when you begin distribution of Opaque copies in quantity, to ensure +that this Transparent copy will remain thus accessible at the stated +location until at least one year after the last time you distribute an +Opaque copy (directly or through your agents or retailers) of that +edition to the public. + +It is requested, but not required, that you contact the authors of the +Document well before redistributing any large number of copies, to +give them a chance to provide you with an updated version of the +Document. + + +4. MODIFICATIONS + +You may copy and distribute a Modified Version of the Document under +the conditions of sections 2 and 3 above, provided that you release +the Modified Version under precisely this License, with the Modified +Version filling the role of the Document, thus licensing distribution +and modification of the Modified Version to whoever possesses a copy +of it. In addition, you must do these things in the Modified Version: + +A. Use in the Title Page (and on the covers, if any) a title distinct + from that of the Document, and from those of previous versions + (which should, if there were any, be listed in the History section + of the Document). You may use the same title as a previous version + if the original publisher of that version gives permission. +B. List on the Title Page, as authors, one or more persons or entities + responsible for authorship of the modifications in the Modified + Version, together with at least five of the principal authors of the + Document (all of its principal authors, if it has fewer than five), + unless they release you from this requirement. +C. State on the Title page the name of the publisher of the + Modified Version, as the publisher. +D. Preserve all the copyright notices of the Document. +E. Add an appropriate copyright notice for your modifications + adjacent to the other copyright notices. +F. Include, immediately after the copyright notices, a license notice + giving the public permission to use the Modified Version under the + terms of this License, in the form shown in the Addendum below. +G. Preserve in that license notice the full lists of Invariant Sections + and required Cover Texts given in the Document's license notice. +H. Include an unaltered copy of this License. +I. Preserve the section Entitled "History", Preserve its Title, and add + to it an item stating at least the title, year, new authors, and + publisher of the Modified Version as given on the Title Page. If + there is no section Entitled "History" in the Document, create one + stating the title, year, authors, and publisher of the Document as + given on its Title Page, then add an item describing the Modified + Version as stated in the previous sentence. +J. Preserve the network location, if any, given in the Document for + public access to a Transparent copy of the Document, and likewise + the network locations given in the Document for previous versions + it was based on. These may be placed in the "History" section. + You may omit a network location for a work that was published at + least four years before the Document itself, or if the original + publisher of the version it refers to gives permission. +K. For any section Entitled "Acknowledgements" or "Dedications", + Preserve the Title of the section, and preserve in the section all + the substance and tone of each of the contributor acknowledgements + and/or dedications given therein. +L. Preserve all the Invariant Sections of the Document, + unaltered in their text and in their titles. Section numbers + or the equivalent are not considered part of the section titles. +M. Delete any section Entitled "Endorsements". Such a section + may not be included in the Modified Version. +N. Do not retitle any existing section to be Entitled "Endorsements" + or to conflict in title with any Invariant Section. +O. Preserve any Warranty Disclaimers. + +If the Modified Version includes new front-matter sections or +appendices that qualify as Secondary Sections and contain no material +copied from the Document, you may at your option designate some or all +of these sections as invariant. To do this, add their titles to the +list of Invariant Sections in the Modified Version's license notice. +These titles must be distinct from any other section titles. + +You may add a section Entitled "Endorsements", provided it contains +nothing but endorsements of your Modified Version by various +parties--for example, statements of peer review or that the text has +been approved by an organization as the authoritative definition of a +standard. + +You may add a passage of up to five words as a Front-Cover Text, and a +passage of up to 25 words as a Back-Cover Text, to the end of the list +of Cover Texts in the Modified Version. Only one passage of +Front-Cover Text and one of Back-Cover Text may be added by (or +through arrangements made by) any one entity. If the Document already +includes a cover text for the same cover, previously added by you or +by arrangement made by the same entity you are acting on behalf of, +you may not add another; but you may replace the old one, on explicit +permission from the previous publisher that added the old one. + +The author(s) and publisher(s) of the Document do not by this License +give permission to use their names for publicity for or to assert or +imply endorsement of any Modified Version. + + +5. COMBINING DOCUMENTS + +You may combine the Document with other documents released under this +License, under the terms defined in section 4 above for modified +versions, provided that you include in the combination all of the +Invariant Sections of all of the original documents, unmodified, and +list them all as Invariant Sections of your combined work in its +license notice, and that you preserve all their Warranty Disclaimers. + +The combined work need only contain one copy of this License, and +multiple identical Invariant Sections may be replaced with a single +copy. If there are multiple Invariant Sections with the same name but +different contents, make the title of each such section unique by +adding at the end of it, in parentheses, the name of the original +author or publisher of that section if known, or else a unique number. +Make the same adjustment to the section titles in the list of +Invariant Sections in the license notice of the combined work. + +In the combination, you must combine any sections Entitled "History" +in the various original documents, forming one section Entitled +"History"; likewise combine any sections Entitled "Acknowledgements", +and any sections Entitled "Dedications". You must delete all sections +Entitled "Endorsements". + + +6. COLLECTIONS OF DOCUMENTS + +You may make a collection consisting of the Document and other +documents released under this License, and replace the individual +copies of this License in the various documents with a single copy +that is included in the collection, provided that you follow the rules +of this License for verbatim copying of each of the documents in all +other respects. + +You may extract a single document from such a collection, and +distribute it individually under this License, provided you insert a +copy of this License into the extracted document, and follow this +License in all other respects regarding verbatim copying of that +document. + + +7. AGGREGATION WITH INDEPENDENT WORKS + +A compilation of the Document or its derivatives with other separate +and independent documents or works, in or on a volume of a storage or +distribution medium, is called an "aggregate" if the copyright +resulting from the compilation is not used to limit the legal rights +of the compilation's users beyond what the individual works permit. +When the Document is included in an aggregate, this License does not +apply to the other works in the aggregate which are not themselves +derivative works of the Document. + +If the Cover Text requirement of section 3 is applicable to these +copies of the Document, then if the Document is less than one half of +the entire aggregate, the Document's Cover Texts may be placed on +covers that bracket the Document within the aggregate, or the +electronic equivalent of covers if the Document is in electronic form. +Otherwise they must appear on printed covers that bracket the whole +aggregate. + + +8. TRANSLATION + +Translation is considered a kind of modification, so you may +distribute translations of the Document under the terms of section 4. +Replacing Invariant Sections with translations requires special +permission from their copyright holders, but you may include +translations of some or all Invariant Sections in addition to the +original versions of these Invariant Sections. You may include a +translation of this License, and all the license notices in the +Document, and any Warranty Disclaimers, provided that you also include +the original English version of this License and the original versions +of those notices and disclaimers. In case of a disagreement between +the translation and the original version of this License or a notice +or disclaimer, the original version will prevail. + +If a section in the Document is Entitled "Acknowledgements", +"Dedications", or "History", the requirement (section 4) to Preserve +its Title (section 1) will typically require changing the actual +title. + + +9. TERMINATION + +You may not copy, modify, sublicense, or distribute the Document +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense, or distribute it is void, and +will automatically terminate your rights under this License. + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, receipt of a copy of some or all of the same material does +not give you any rights to use it. + + +10. FUTURE REVISIONS OF THIS LICENSE + +The Free Software Foundation may publish new, revised versions of the +GNU Free Documentation License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in +detail to address new problems or concerns. See +http://www.gnu.org/copyleft/. + +Each version of the License is given a distinguishing version number. +If the Document specifies that a particular numbered version of this +License "or any later version" applies to it, you have the option of +following the terms and conditions either of that specified version or +of any later version that has been published (not as a draft) by the +Free Software Foundation. If the Document does not specify a version +number of this License, you may choose any version ever published (not +as a draft) by the Free Software Foundation. If the Document +specifies that a proxy can decide which future versions of this +License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the +Document. + +11. RELICENSING + +"Massive Multiauthor Collaboration Site" (or "MMC Site") means any +World Wide Web server that publishes copyrightable works and also +provides prominent facilities for anybody to edit those works. A +public wiki that anybody can edit is an example of such a server. A +"Massive Multiauthor Collaboration" (or "MMC") contained in the site +means any set of copyrightable works thus published on the MMC site. + +"CC-BY-SA" means the Creative Commons Attribution-Share Alike 3.0 +license published by Creative Commons Corporation, a not-for-profit +corporation with a principal place of business in San Francisco, +California, as well as future copyleft versions of that license +published by that same organization. + +"Incorporate" means to publish or republish a Document, in whole or in +part, as part of another Document. + +An MMC is "eligible for relicensing" if it is licensed under this +License, and if all works that were first published under this License +somewhere other than this MMC, and subsequently incorporated in whole or +in part into the MMC, (1) had no cover texts or invariant sections, and +(2) were thus incorporated prior to November 1, 2008. + +The operator of an MMC Site may republish an MMC contained in the site +under CC-BY-SA on the same site at any time before August 1, 2009, +provided the MMC is eligible for relicensing. + + +ADDENDUM: How to use this License for your documents + +To use this License in a document you have written, include a copy of +the License in the document and put the following copyright and +license notices just after the title page: + + Copyright (c) YEAR YOUR NAME. + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, +replace the "with...Texts." line with this: + + with the Invariant Sections being LIST THEIR TITLES, with the + Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. + +If you have Invariant Sections without Cover Texts, or some other +combination of the three, merge those two alternatives to suit the +situation. + +If your document contains nontrivial examples of program code, we +recommend releasing these examples in parallel under your choice of +free software license, such as the GNU General Public License, +to permit their use in free software. diff --git a/README.md b/README.md new file mode 100644 index 000000000..60b134289 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +Chill documentation +==================== + +Documentation of the Chill software. + +This may be see here : https://chill.readthedocs.org + + +Compilation into HTML +===================== + +To compile this documentation : + +1. Install [sphinx-doc](http://sphinx-doc.org) +2. run `make html` from the root directory +3. The base file is located on build/html/index.html + +Contribute +=========== + +Issue tracker : https://redmine.champs-libres.coop/projects/chill/issues (certificate is located here : http://www.champs-libres.coop/ca.pem) + +Licence +======= + +This documentation is available under the [GNU Free Documentation Licence](http://www.gnu.org/licenses/fdl-1.3.en.html). From 721b93e2095a54bb432abd164d139dcc96dd5cd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 29 Sep 2014 22:07:32 +0200 Subject: [PATCH 005/157] update docs --- source/installation/installation.rst | 72 +++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index bcea376ed..7ed1f06ca 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -1,14 +1,82 @@ +Requirements +============ + +* a postgresql database. The minimum version is postgresql 9.1, but we are working on developing on the 9.4 branch, which will provide features which will ease developper work +* php 5.5 + +You won't need any web server for demonstration or development. + +Preparation +=========== + +Prepare : + +* your credentials to use your postgresql database +* a random string, which will be use to improve entropy in security. Choose anything you want (random character, your father's birthplace, ...) Installation ============= Chill is installed with composer. +Install composer +---------------- + +.. note:: + If you do not know composer, it is a good idea to have a glance at `the composer documentation`_ + Install composer on your system : -:: +.. code-block:: bash curl -sS https://getcomposer.org/installer | php -move composer.phar to your system. +move composer.phar to your system (optional) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Install composer globally on you system will made the installation process more convenient. To do this, simply run + +.. code-block:: bash + sudo mv composer.phar /usr/local/bin/composer + +Then, just run `composer` instead of `php composer.phar` + +.. note:: + See `the composer introduction`_ to learn how to install composer on Mac OS X and Windows + +Create your project +------------------- + +Create your Chill project using composer: + +.. code-block:: bash + php composer.phar create-project champs-libres/chill-standard path/to/your/directory --stability=dev + +.. note:: + Until now, the stability of the project is set to "dev". Do not forget this argument, or composer will fail to download and create the project. + +Composer will download `the standard architecture`_ and ask you a few question about how to configure your project. + +* **database_host** : your postgresql server's address +* **database_port** : the port to reach your postgresql server +* **database_name** : the name of your database +* **database_user** : the username to reach your database +* **database_password** : your username's password +* **locale**: the language, as iso code. Until now, only fr is supported +* **secret**: the secret string you prepared (see "preparation") + +You may accept the default parameters of **debug_toolbar**, **debug_redirects** and **use_assetic_controller** for a demonstration installation. For production, set them all to `false`. + +Launch your server +------------------- + +If everything was fine, you are able to launch your built-in server : + +.. code-block::bash + php app/console server:run + +Your server should now be available at `http://localhost:8000` + +.. _the composer documentation: https://getcomposer.org/doc/ +.. _the composer introduction: https://getcomposer.org/doc/00-intro.md From 4ae4906754eff5a627d81e072409e94fc2aea573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 29 Sep 2014 22:12:17 +0200 Subject: [PATCH 006/157] fix rst syntax --- source/installation/installation.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 7ed1f06ca..9e745eb2a 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -38,6 +38,7 @@ move composer.phar to your system (optional) Install composer globally on you system will made the installation process more convenient. To do this, simply run .. code-block:: bash + sudo mv composer.phar /usr/local/bin/composer Then, just run `composer` instead of `php composer.phar` @@ -51,6 +52,7 @@ Create your project Create your Chill project using composer: .. code-block:: bash + php composer.phar create-project champs-libres/chill-standard path/to/your/directory --stability=dev .. note:: @@ -73,10 +75,12 @@ Launch your server If everything was fine, you are able to launch your built-in server : -.. code-block::bash +.. code-block:: bash + php app/console server:run Your server should now be available at `http://localhost:8000` .. _the composer documentation: https://getcomposer.org/doc/ .. _the composer introduction: https://getcomposer.org/doc/00-intro.md +.. _the standard architecture: https://github.com/Champs-Libres/chill-standard From 6501eba7dac66694b6ad7a84f4537ab6e056cade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 29 Sep 2014 22:19:30 +0200 Subject: [PATCH 007/157] improve bash statement using return line --- source/installation/installation.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 9e745eb2a..ce6f1e4ab 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -53,7 +53,8 @@ Create your Chill project using composer: .. code-block:: bash - php composer.phar create-project champs-libres/chill-standard path/to/your/directory --stability=dev + php composer.phar create-project champs-libres/chill-standard \ + path/to/your/directory --stability=dev .. note:: Until now, the stability of the project is set to "dev". Do not forget this argument, or composer will fail to download and create the project. From 46f62fe3ecc97f6e43acbcb0bc8e1364ee44f5ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 29 Sep 2014 22:26:48 +0200 Subject: [PATCH 008/157] add missing bash command --- source/installation/installation.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index ce6f1e4ab..fee2029d1 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -78,6 +78,7 @@ If everything was fine, you are able to launch your built-in server : .. code-block:: bash + cd path/to/your/directory php app/console server:run Your server should now be available at `http://localhost:8000` From 43f7ba0e16592d1d526964bf3a1d419ede501351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 9 Oct 2014 10:14:19 +0200 Subject: [PATCH 009/157] restructuration of titles order + various informations --- source/installation/installation.rst | 68 +++++++++++++++++++--------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index fee2029d1..f67ef0fc4 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -1,27 +1,44 @@ +Installation +############ + +Basic installation +`````````````````` + Requirements -============ +------------ -* a postgresql database. The minimum version is postgresql 9.1, but we are working on developing on the 9.4 branch, which will provide features which will ease developper work +Server requirements +^^^^^^^^^^^^^^^^^^^^ + +* a postgresql database. The minimum version is postgresql 9.3, but we are working on developing on the 9.4 branch, which will provide features which will ease developper work * php 5.5 +* If you run Chill in production mode, you should also install a web server (apache, ngnix, ...). We may use php built-in server for testing and development. + +Within this documentation, we are going to describe installation on Unix systems (Unix, Mac OS). Windows installation ha not been tested yet. You won't need any web server for demonstration or development. +Client requirements +^^^^^^^^^^^^^^^^^^^ + +Chill is accessible through a web browser. Currently, we focus our support on `Firefox`_, because firefox is open source, cross-platform, and very well active. The software should work with other browser (Chromium, Opera, ...) but some functionalities should break. + Preparation -=========== +----------- -Prepare : +You will need those informations : -* your credentials to use your postgresql database -* a random string, which will be use to improve entropy in security. Choose anything you want (random character, your father's birthplace, ...) +* The informations to access to your database: host, port, database name, and your credentials (username and password) ; +* a random string, which will be use to improve entropy in security. Choose anything you want (random character, your father's birthplace, ...). Installation -============= +------------ -Chill is installed with composer. +Chill is installed with `composer`_. Install composer ----------------- +^^^^^^^^^^^^^^^^ .. note:: If you do not know composer, it is a good idea to have a glance at `the composer documentation`_ @@ -32,8 +49,11 @@ Install composer on your system : curl -sS https://getcomposer.org/installer | php -move composer.phar to your system (optional) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +move composer.phar to your system +""""""""""""""""""""""""""""""""" + +.. note:: + This part is not necessary, but this will be more convenient for the process. If you do not want to install composer on your system, you will have to replace the commands `composer` by `php composer.phar`. Install composer globally on you system will made the installation process more convenient. To do this, simply run @@ -43,11 +63,13 @@ Install composer globally on you system will made the installation process more Then, just run `composer` instead of `php composer.phar` +You can test the installation by running `which composer` or `composer`: those command should not raise any error. + .. note:: See `the composer introduction`_ to learn how to install composer on Mac OS X and Windows Create your project -------------------- +^^^^^^^^^^^^^^^^^^^ Create your Chill project using composer: @@ -61,18 +83,18 @@ Create your Chill project using composer: Composer will download `the standard architecture`_ and ask you a few question about how to configure your project. -* **database_host** : your postgresql server's address -* **database_port** : the port to reach your postgresql server -* **database_name** : the name of your database -* **database_user** : the username to reach your database -* **database_password** : your username's password -* **locale**: the language, as iso code. Until now, only fr is supported -* **secret**: the secret string you prepared (see "preparation") +* `database_host` : your postgresql server's address +* `database_port` : the port to reach your postgresql server +* `database_name` : the name of your database +* `database_user` : the username to reach your database +* `database_password` : your username's password +* `locale`: the language, as iso code. Until now, only fr is supported +* `secret`: the secret string you prepared (see "preparation") -You may accept the default parameters of **debug_toolbar**, **debug_redirects** and **use_assetic_controller** for a demonstration installation. For production, set them all to `false`. +You may accept the default parameters of `debug_toolbar`, `debug_redirects` and `use_assetic_controller` for a demonstration installation. For production, set them all to `false`. Launch your server -------------------- +^^^^^^^^^^^^^^^^^^ If everything was fine, you are able to launch your built-in server : @@ -81,8 +103,10 @@ If everything was fine, you are able to launch your built-in server : cd path/to/your/directory php app/console server:run -Your server should now be available at `http://localhost:8000` +Your server should now be available at `http://localhost:8000`. Type this address on your browser and you should see the homepage. .. _the composer documentation: https://getcomposer.org/doc/ .. _the composer introduction: https://getcomposer.org/doc/00-intro.md .. _the standard architecture: https://github.com/Champs-Libres/chill-standard +.. _composer: https://getcomposer.org +.. _Firefox: https://www.mozilla.org From f1485147503d3b22b5f7a9db1b55310448bb7528 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 10 Oct 2014 18:59:41 +0200 Subject: [PATCH 010/157] add doc for developers --- source/development/create-a-new-bundle.rst | 30 +++ source/development/index.rst | 10 + source/development/installation.rst | 110 ++++++++++ source/development/installation.rst~ | 110 ++++++++++ source/development/make-test-working.rst | 231 +++++++++++++++++++++ source/index.rst | 21 +- source/installation/installation.rst | 21 ++ 7 files changed, 530 insertions(+), 3 deletions(-) create mode 100644 source/development/create-a-new-bundle.rst create mode 100644 source/development/index.rst create mode 100644 source/development/installation.rst create mode 100644 source/development/installation.rst~ create mode 100644 source/development/make-test-working.rst diff --git a/source/development/create-a-new-bundle.rst b/source/development/create-a-new-bundle.rst new file mode 100644 index 000000000..b1a581bd4 --- /dev/null +++ b/source/development/create-a-new-bundle.rst @@ -0,0 +1,30 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Create a new bundle +******************** + +Create your own bundle is not a trivial task. + +The easiest way to achieve this is seems to be : + +1. Prepare a fresh installation of the chill project, in a new directory +2. Create a new bundle in this project, in the src directory +3. Initialize a git repository **at the root bundle**, and create your initial commit. +4. Register the bundle with composer/packagist. If you do not plan to distribute your bundle with packagist, you may use a custom repository for achieve this [#f1]_ +5. Move to a development installation, made as described in the :ref:`installation-for-development` section, and add your new repository to the composer.json file +6. Work as :ref:`usual ` + +.. warning:: + + This part of the doc is not yet tested + +TODO + + +.. [#f1] Be aware that we use the Affero GPL Licence, which ensure that all users must have access to derivative works done with this software. diff --git a/source/development/index.rst b/source/development/index.rst new file mode 100644 index 000000000..c1ee46d17 --- /dev/null +++ b/source/development/index.rst @@ -0,0 +1,10 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Development +########### diff --git a/source/development/installation.rst b/source/development/installation.rst new file mode 100644 index 000000000..6c5783ebb --- /dev/null +++ b/source/development/installation.rst @@ -0,0 +1,110 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _installation-for-development : + +Installation for development +**************************** + +Installation for development should let you able to have an access to the source code, upload the code to our CVS (i.e. `git`_), and working with `composer`_. + +As Chill is divided into modules, each module has his own repository. + +Installation and big picture +----------------------------- + +At first, you should install Chill as described in the :ref:`basic-installation` section. + +Two things must be modified : + +At first, You should add the `--prefer-source` argument when you create project. + +.. code-block:: bash + + php composer.phar create-project champs-libres/chill-standard \ + path/to/your/directory --stability=dev --prefer-source + +Secondly, if composer ask you the following question : :: + + Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? + +**You should answer `n` (no).** + +This will install a project. All downloaded modules will be stored in the `/vendor` directories. In those directories, you will have access to the git commands. + +.. code-block:: bash + + $ cd vendor/champs-libres/chill-main/ + $ git remote -v + composer git://github.com/Champs-Libres/ChillMain.git (fetch) + composer git://github.com/Champs-Libres/ChillMain.git (push) + origin git://github.com/Champs-Libres/ChillMain.git (fetch) + origin git@github.com:Champs-Libres/ChillMain.git (push) + +Working with your own fork +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. warning:: + + This section has not been tested yet. Feedback wanted. + +Ideally, you will work on a fork of the main github repository. To ensure that composer will download the code from **your** repository, you will have to adapt the `composer.json` file accordingly, using your own repository. + +Add the following lines to your composer.json file if you want to force composer to download from your own repository. This will force to use your own repository for the ChillMain bundle, insert in `composer.json` the following lines : + +.. code-block:: json + + "repositories": [ + { + "type": "git", + "url": "git://github.com/your-github-username/ChillMain.git" + } + ] + +You may also `use aliases `_ to define versions. + +.. _editing-code-and-commiting : + +Editing the code and commiting +------------------------------ + +You may edit code in the `vendor/path/to/the/bundle` directory. + +After your edits, you should commit as usually : + +.. code-block:: bash + + $ cd vendor/path/to/bundle + $ git status + Sur la branche master + Votre branche est à jour avec 'origin/master'. + + rien à valider, la copie de travail est propre + +.. warning + + The git command must be run from you vendor bundle's path (`vendor/path/to/bundle`). + +Tips +^^^^ + +The command `composer status` (`see composer documentation `_) will give you and idea of which bundle has been edited : + +.. code-block:: bash + + $ cd ./../../ #back to the root project directory + $ composer status + You have changes in the following dependencies: + /path/to/your/project/install/vendor/champs-libres/chill-main + Use --verbose (-v) to see modified files + + + + +.. _git: http://git-scm.org +.. _composer: https://getcomposer.org diff --git a/source/development/installation.rst~ b/source/development/installation.rst~ new file mode 100644 index 000000000..b7633a751 --- /dev/null +++ b/source/development/installation.rst~ @@ -0,0 +1,110 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _installation-for-development : + +Installation for development +**************************** + +Installation for development should let you able to have an access to the source code, upload the code to our CVS (i.e. `git`_), and working with `composer`_. + +As Chill is divided into modules, each module has his own repository. + +Installation and big picture +----------------------------- + +At first, you should install Chill as described in the :ref:`basic-installation` section. + +Two things must be modified : + +At first, You should add the `--prefer-source` argument when you create project. + +.. code-block:: bash + + php composer.phar create-project champs-libres/chill-standard \ + path/to/your/directory --stability=dev --prefer-source + +Secondly, if composer ask you the following question : :: + + Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? + +**You should answer `n` (no).** + +This will install a project. All downloaded modules will be stored in the `/vendor` directories. In those directories, you will have access to the git commands. + +.. code-block:: bash + + $ cd vendor/champs-libres/chill-main/ + $ git remote -v + composer git://github.com/Champs-Libres/ChillMain.git (fetch) + composer git://github.com/Champs-Libres/ChillMain.git (push) + origin git://github.com/Champs-Libres/ChillMain.git (fetch) + origin git@github.com:Champs-Libres/ChillMain.git (push) + +Working with your own fork +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. warning + + This section has not been tested yet. Feedback wanted. + +Ideally, you will work on a fork of the main github repository. To ensure that composer will download the code from **your** repository, you will have to adapt the `composer.json` file accordingly, using your own repository. + +Add the following lines to your composer.json file if you want to force composer to download from your own repository. This will force to use your own repository for the ChillMain bundle, insert in `composer.json` the following lines : + +.. code-block:: json + + "repositories": [ + { + "type": "git", + "url": "git://github.com/your-github-username/ChillMain.git" + } + ] + +You may also `use aliases `_ to define versions. + +.. _editing-code-and-commiting : + +Editing the code and commiting +------------------------------ + +You may edit code in the `vendor/path/to/the/bundle` directory. + +After your edits, you should commit as usually : + +.. code-block:: bash + + $ cd vendor/path/to/bundle + $ git status + Sur la branche master + Votre branche est à jour avec 'origin/master'. + + rien à valider, la copie de travail est propre + +.. warning + + The git command must be run from you vendor bundle's path (`vendor/path/to/bundle`). + +Tips +^^^^ + +The command `composer status` (`see composer documentation `_) will give you and idea of which bundle has been edited : + +.. code-block:: bash + + $ cd ./../../ #back to the root project directory + $ composer status + You have changes in the following dependencies: + /path/to/your/project/install/vendor/champs-libres/chill-main + Use --verbose (-v) to see modified files + + + + +.. _git: http://git-scm.org +.. _composer: https://getcomposer.org diff --git a/source/development/make-test-working.rst b/source/development/make-test-working.rst new file mode 100644 index 000000000..dccdc736f --- /dev/null +++ b/source/development/make-test-working.rst @@ -0,0 +1,231 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Make tests working +****************** + +Unit and functional tests are important to ensure that bundle may be deployed securely. + +In reason of the Chill architecture, test should be runnable from the bundle's directory and works correctly: this will allow continuous integration tools to run tests automatically. + +.. note:: + + Integration tools (i.e. `travis-ci `_) works like this : + + * they clone the bundle repository in a virtual machine, using git + * they optionnaly run `composer` to download and install depedencies + * they optionnaly run other command to prepare a database, insert fixtures, ... + * they run test + +On the developer's machine test should be runnable with two or three commands **runned from the bundle directory** : + +.. code-block:: bash + + $ composer install --dev + $ // command to insert fixtures, ... + $ phpunit + +This chapter has been inspired by `this useful blog post <>`_. + +Bootstrap phpunit for a standalone bundle +========================================== + +Unit tests should run after achieving this step. + + +phpunit.xml +----------- + +A `phpunit.xml.dist` file should be present at the bundle root. + +.. code-block:: xml + + + + + + + + ./Tests + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + + + +bootstrap.php +-------------- + +A file `boostrap.php`, located in the `Tests` directory, will allow phpunit to resolve class autoloading : + +.. code-block:: php + + ` should be added like this, if your `AppKernel.php` file is located in `Tests/Fixtures/App` directory: + +.. code-block:: xml + + + + + + + ./Tests + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + + + + + + +AppKernel.php +------------- + +This file boostrap the app. It contains three functions. This is the file used in the ChillMain bundle : + +.. code-block:: php + + load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml'); + } + + /** + * @return string + */ + public function getCacheDir() + { + return sys_get_temp_dir().'/AcmeHelloBundle/cache'; + } + + /** + * @return string + */ + public function getLogDir() + { + return sys_get_temp_dir().'/AcmeHelloBundle/logs'; + } + } + +config_test.yml +--------------- + +There are only few parameters required for the config file. This is the config file for ChillMain : + +.. code-block:: yaml + + # config/config_test.yml + imports: + - { resource: config.yml } #here we import a config.yml file, this is not required + + framework: + test: ~ + session: + storage_id: session.storage.filesystem + +.. code-block:: yaml + + # config/config.yml + framework: + secret: Not very secret + router: { resource: "%kernel.root_dir%/config/routing.yml" } + form: true + csrf_protection: true + session: ~ + default_locale: fr + translator: { fallback: fr } + profiler: { only_exceptions: false } + templating: #required for assetic. Remove if not needed + engines: ['twig'] + +.. note:: + + You must adapt config.yml file according to your required bundle. Some options will be missing, other may be removed... + +.. note:: + + If you would like to tests different environments, with differents configuration, you could create differents config_XXX.yml files. + +routing.yml +------------ + +You should add there all routing information needed for your bundle. + +.. code-block: yaml + + chill_main_bundle: + resource: "@CLChillMainBundle/Resources/config/routing.yml" + +That's it. Tests should not pass. diff --git a/source/index.rst b/source/index.rst index ef060f4e2..680329685 100644 --- a/source/index.rst +++ b/source/index.rst @@ -3,6 +3,14 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + Welcome to Chill documentation! ===================================== @@ -16,7 +24,10 @@ Contents of this documentation: :maxdepth: 2 installation/installation.rst - + development/index.rst + development/installation.rst + development/create-a-new-bundle.rst + development/make-test-working.rst Contribute ========== @@ -30,7 +41,11 @@ Contribute Licence ======== -The project is available under the `GPLv3 licence`_. +The project is available under the `GNU AFFERO GENERAL PUBLIC LICENSE v3`_. + +This documentation is published under the `GNU Free Documentation License (FDL) v1.3`_ -.. _GPLv3 licence: http://www.gnu.org/licenses/gpl-3.0.txt +.. _GNU AFFERO GENERAL PUBLIC LICENSE v3: http://www.gnu.org/licenses/agpl-3.0.html +.. _GNU Free Documentation License (FDL) v1.3: http://www.gnu.org/licenses/fdl-1.3.html + diff --git a/source/installation/installation.rst b/source/installation/installation.rst index f67ef0fc4..518acf42f 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -1,10 +1,24 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + Installation ############ + +.. _basic-installation: + Basic installation `````````````````` + +Chill is written in PHP and use the `symfony framework`_. We take advantages of all the framework's feature, and installation should be as simple as installing symfony. + Requirements ------------ @@ -93,6 +107,12 @@ Composer will download `the standard architecture`_ and ask you a few question a You may accept the default parameters of `debug_toolbar`, `debug_redirects` and `use_assetic_controller` for a demonstration installation. For production, set them all to `false`. +If composer ask you the following question : :: + + Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? + +You may answer `Y` (Yes), or simply press the `return` button. + Launch your server ^^^^^^^^^^^^^^^^^^ @@ -110,3 +130,4 @@ Your server should now be available at `http://localhost:8000`. Type this addres .. _the standard architecture: https://github.com/Champs-Libres/chill-standard .. _composer: https://getcomposer.org .. _Firefox: https://www.mozilla.org +.. _symfony framework: http://symfony.com From a0a6657d18f31eca0be304c80c1c5fe4b8f608e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 10 Oct 2014 23:15:14 +0200 Subject: [PATCH 011/157] add gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d71828365..b5a3c2cc4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ build/* !build/git.txt - +*~ From ea56ff5a0888d21a45a334393de0f96663b19d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 10 Oct 2014 23:16:19 +0200 Subject: [PATCH 012/157] correct hierarchy --- source/development/index.rst | 8 ++++++++ source/index.rst | 4 +--- source/index.rst~ | 26 -------------------------- source/installation/installation.rst~ | 13 ------------- 4 files changed, 9 insertions(+), 42 deletions(-) delete mode 100644 source/index.rst~ delete mode 100644 source/installation/installation.rst~ diff --git a/source/development/index.rst b/source/development/index.rst index c1ee46d17..0fd23ffc3 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -8,3 +8,11 @@ Development ########### + +.. toctree:: + :maxdepth: 2 + + Install Chill for development + Instructions to create a new bundle + Bundles and tests + diff --git a/source/index.rst b/source/index.rst index 680329685..c4ca65b25 100644 --- a/source/index.rst +++ b/source/index.rst @@ -25,9 +25,7 @@ Contents of this documentation: installation/installation.rst development/index.rst - development/installation.rst - development/create-a-new-bundle.rst - development/make-test-working.rst + Contribute ========== diff --git a/source/index.rst~ b/source/index.rst~ deleted file mode 100644 index 24c416d03..000000000 --- a/source/index.rst~ +++ /dev/null @@ -1,26 +0,0 @@ -.. chill-doc documentation master file, created by - sphinx-quickstart on Sun Sep 28 22:04:08 2014. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to Chill documentation! -===================================== - -Chill is a free software for social workers. - -Contents of this documentation: - -.. toctree:: - :maxdepth: 2 - - installation/installation.rst - - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/source/installation/installation.rst~ b/source/installation/installation.rst~ deleted file mode 100644 index 924ec955f..000000000 --- a/source/installation/installation.rst~ +++ /dev/null @@ -1,13 +0,0 @@ - - -Installation -============= - -Chill is installed with composer. - -Install composer on your system : - -:: - - curl -sS https://getcomposer.org/installer | php - From a1466466aecef7ed3661615b4b8a5525a625f3d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 10 Oct 2014 23:26:15 +0200 Subject: [PATCH 013/157] switch to the rtd theme --- README.md | 1 + source/conf.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 60b134289..a1b361b41 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Compilation into HTML To compile this documentation : 1. Install [sphinx-doc](http://sphinx-doc.org) +2. Install the Sphinx theme provided by Read the Docs : `sudo pip install sphinx_rtd_theme` 2. run `make html` from the root directory 3. The base file is located on build/html/index.html diff --git a/source/conf.py b/source/conf.py index 517f70a82..e365b8215 100644 --- a/source/conf.py +++ b/source/conf.py @@ -15,6 +15,7 @@ import sys import os +import sphinx_rtd_theme # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -102,7 +103,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -110,7 +111,7 @@ html_theme = 'default' #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". From 6a5207597b257cb8ab43cd363511d3fbb1800e01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 10 Oct 2014 23:39:07 +0200 Subject: [PATCH 014/157] grammar and mention of symfony framework --- source/development/index.rst | 7 +++++++ source/index.rst | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/source/development/index.rst b/source/development/index.rst index 0fd23ffc3..e61eb086e 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -9,6 +9,8 @@ Development ########### +As Chill rely on the `symfony `_ framework, reading the framework's documentation should answer most of your questions. We are explaining here some tips to work with Chill, and things we provide to encounter our needs. + .. toctree:: :maxdepth: 2 @@ -16,3 +18,8 @@ Development Instructions to create a new bundle Bundles and tests + +Help, I am lost ! +***************** + +Write an email at info@champs-libres.coop, and we will help you ! diff --git a/source/index.rst b/source/index.rst index c4ca65b25..d6276ee2e 100644 --- a/source/index.rst +++ b/source/index.rst @@ -16,7 +16,7 @@ Welcome to Chill documentation! Chill is a free software for social workers. -Chill rely on the php framework Symfony. If you know symfony, you should not be lost. Symfony has a very good documentation. +Chill rely on the php framework `Symfony `_. Contents of this documentation: @@ -30,6 +30,7 @@ Contents of this documentation: Contribute ========== + * `Issue tracker `_ (the certificate is available `here `_) * source code is available here : From 74ab54ef469b2dc3ee6dee1049c6e07212bc7abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 10 Oct 2014 23:39:41 +0200 Subject: [PATCH 015/157] add licence to footer and correct project name --- source/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/conf.py b/source/conf.py index e365b8215..7a8f2a9a9 100644 --- a/source/conf.py +++ b/source/conf.py @@ -48,8 +48,8 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = 'chill-doc' -copyright = '2014, Champs-Libres' +project = 'Chill Documentation' +copyright = '2014, Champs-Libres, published under the terms of the GNU Free Documentation License, Version 1.3' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -58,7 +58,7 @@ copyright = '2014, Champs-Libres' # The short X.Y version. version = '1.0' # The full version, including alpha/beta/rc tags. -release = '1.0.0' +release = '1.0.0ALPHA' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 77e1540b83238352fb86f62a79688ffac6801c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sat, 11 Oct 2014 00:15:47 +0200 Subject: [PATCH 016/157] Revert "switch to the rtd theme" This reverts commit a1466466aecef7ed3661615b4b8a5525a625f3d0. --- README.md | 1 - source/conf.py | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a1b361b41..60b134289 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ Compilation into HTML To compile this documentation : 1. Install [sphinx-doc](http://sphinx-doc.org) -2. Install the Sphinx theme provided by Read the Docs : `sudo pip install sphinx_rtd_theme` 2. run `make html` from the root directory 3. The base file is located on build/html/index.html diff --git a/source/conf.py b/source/conf.py index 7a8f2a9a9..5c2331ea5 100644 --- a/source/conf.py +++ b/source/conf.py @@ -15,7 +15,6 @@ import sys import os -import sphinx_rtd_theme # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -103,7 +102,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_rtd_theme' +html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -111,7 +110,7 @@ html_theme = 'sphinx_rtd_theme' #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +#html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". From 6346a9670d5862d117654dc497afb8b8f635588a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 14 Oct 2014 14:49:50 +0200 Subject: [PATCH 017/157] add rtd_theme for local builds --- source/conf.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/source/conf.py b/source/conf.py index 5c2331ea5..fb37071c5 100644 --- a/source/conf.py +++ b/source/conf.py @@ -102,7 +102,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'default' #overriden if run locally, see end of file # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -265,7 +265,7 @@ texinfo_documents = [ # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. -epub_title = 'chill-doc' +epub_title = 'Chill Documentation' epub_author = 'Champs-Libres' epub_publisher = 'Champs-Libres' epub_copyright = '2014, Champs-Libres' @@ -330,3 +330,13 @@ epub_exclude_files = ['search.html'] # If false, no index is generated. #epub_use_index = True + +# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# otherwise, readthedocs.org uses their theme by default, so no need to specify it From d7cac3dd7336bf237e67f496b7e4447f33195e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 14 Oct 2014 14:50:17 +0200 Subject: [PATCH 018/157] create manual and routing page --- source/development/index.rst | 1 + source/development/manual/index.rst | 17 +++++++++++++++++ source/development/manual/routing-and-menus.rst | 10 ++++++++++ 3 files changed, 28 insertions(+) create mode 100644 source/development/manual/index.rst create mode 100644 source/development/manual/routing-and-menus.rst diff --git a/source/development/index.rst b/source/development/index.rst index e61eb086e..db2afa25f 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -17,6 +17,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Install Chill for development Instructions to create a new bundle Bundles and tests + manual/index.rst Help, I am lost ! diff --git a/source/development/manual/index.rst b/source/development/manual/index.rst new file mode 100644 index 000000000..d93fa3a7e --- /dev/null +++ b/source/development/manual/index.rst @@ -0,0 +1,17 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Developer manual +***************** + +.. toctree:: + :maxdepth: 2 + + routing-and-menus.rst + + diff --git a/source/development/manual/routing-and-menus.rst b/source/development/manual/routing-and-menus.rst new file mode 100644 index 000000000..6bbf3bc5c --- /dev/null +++ b/source/development/manual/routing-and-menus.rst @@ -0,0 +1,10 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Routing and menus +***************** From 0a4b8daf26c5fc279bc60c73b63d1ed9b41eca16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 14 Oct 2014 14:50:28 +0200 Subject: [PATCH 019/157] add link --- source/development/make-test-working.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/development/make-test-working.rst b/source/development/make-test-working.rst index dccdc736f..d63655440 100644 --- a/source/development/make-test-working.rst +++ b/source/development/make-test-working.rst @@ -30,7 +30,7 @@ On the developer's machine test should be runnable with two or three commands ** $ // command to insert fixtures, ... $ phpunit -This chapter has been inspired by `this useful blog post <>`_. +This chapter has been inspired by `this useful blog post `_. Bootstrap phpunit for a standalone bundle ========================================== From 709794eab73708afb836f4687e311df453f4ce7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 14 Oct 2014 22:27:08 +0200 Subject: [PATCH 020/157] doc for routing and menus --- .../development/manual/routing-and-menus.rst | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/source/development/manual/routing-and-menus.rst b/source/development/manual/routing-and-menus.rst index 6bbf3bc5c..5786f3fda 100644 --- a/source/development/manual/routing-and-menus.rst +++ b/source/development/manual/routing-and-menus.rst @@ -8,3 +8,136 @@ Routing and menus ***************** + + +The _Chill_'s architecture allows to choose bundle on each installation. This may lead to a huge diversity of installations, and a the developper challenge is to make his code working with all those possibles installations. + +_Chill_ uses menus to let users access easily to the most used functionalities. For instance, when you land on a "Person" page, you may access directly to his activities, notes, documents, ... in a single click on a side menu. + +For a developer, it is easy to extend this menu with his own entries. + +.. seealso:: + + `Symfony documentation about routing `_ + This documentation should be read before diving into those lines + + `Routes dans Chill `_ (FR) + The issue where we discussed routes. In French. + +Create routes +============== + +.. note:: + + We recommand using `yaml` to define routes. We have not tested the existing other ways to create routes (annotations, ...). Help wanted. + +The first step is as easy as create a route in symfony, and add some options in his description : + +.. code-block:: yaml + + chill_main_dummy_0: + pattern: /dummy/{personId} + defaults: { _controller: CLChillMainBundle:Default:index } + options: + #we begin menu information here : + menus: + foo: #must appears in menu named 'foo' + order: 500 #the order will be '500' + label: foolabel #the label shown on menu. Will be translated + otherkey: othervalue #you may add other informations, as needed by your layout + bar: #must also appears in menu named 'bar' + order: 500 + label: barlabel + +The mandatory parameters under the `menus` definition are : + +* `name`: the menu's name, defined as an key for the following entries +* `order`. Note: if we have duplicate order's values, the order will be incremented. We recommand using big intervals within orders and publishing the orders in your documentation +* `label`: the text which will be rendered inside the `` tag. The label should be processed trough the `trans` filter (`{{ route.label|trans }}`) + +You *may* also add other keys, which will be used optionally in the way the menu is rendered. See + +.. warning:: + + Although all keys will be kept from your `yaml` definition to your menu template, we recommend not using those keys, which are reserved for a future implementations of Chill : + + * `helper`, a text to help user or add more informations to him + * `access` : which will run a test with `Expression Langage `_ to determine if the user has the ACL to show the menu entry ; + * `condition`, which will test with the menu context if the entry must appears + +Show menu in twig templates +=========================== + +To show our previous menu in the twig template, we invoke the `chill_menu` function. This will render the `foo` menu : + +.. code-block:: jinja + + {{ chill_menu('foo') }} + +Passing variables +^^^^^^^^^^^^^^^^^ + +If your routes need arguments, i.e. an entity id, you should pass the as argument to the chill_menu function. If your route's pattern is `/person/{personId}`, your code become : + +.. code-block:: jinja + + {{ chill_menu('foo', { 'args' : { 'personId' : person.id } } ) }} + +Of course, `person` is a variable you must define in your code, which should have an `id` accessible property (i.e. : `$person->getId()`). + +.. note:: + + Be aware that your arguments will be passed to all routes in a menu. If a route does not require `personId` in his pattern, the route will become `/pattern?personId=XYZ`. This should not cause problem in your application. + +.. warning:: + + It is a good idea to reuse the same parameter's name in your pattern, to avoid collision. Prefer `/person/{personId}` to `/person/{id}`. + + If you don't do that and another developer create a bundle with `person/{personId}/{id}` where `{id}` is the key for something else, this will cause a lot of trouble... + +Rendering active entry +^^^^^^^^^^^^^^^^^^^^^^ + +Now, you want to render differently the *active* route of the menu [#f1]_. You should, in your controller or template, add the active route in your menu : + +.. code-block:: jinja + + {{ chill_menu('foo', { 'activeRouteKey' : 'chill_main_dummy_0' } ) }} + +On menu creation, the route wich has the key `chill_main_dummy_0` will be rendered on a different manner. + +Define your own template +------------------------- + +By default, the menu is rendered with the default template, which is a simple `ul` list. You may create your own templates : + +.. code-block:: html+jinja + + #MyBundle/Resources/views/Menu/MyMenu.html.twig + + +Arguments available in your template : + +* The `args` value are the value passed in the 'args' arguments requested by the `chill_menu` function. +* `activeRouteKey` is the key of the currently active route. +* `routes` is an array of routes. The array has this structure: `routes[order] = { 'key' : 'the_route_key', 'label' : 'the route label' }` The order is *resolved*: in case of collision (two routes from different bundles having the same order), the order will be incremented. You may find in the array your own keys (`{ 'otherkey' : 'othervalue'}` in the example above). + +Then, you will call your own template with the `layout` argument : + +.. code-block:: jinja + + {{ chill_menu('foo', { 'layout' : 'MyBundle:Menu:MyMenu.html.twig' } ) }} + +.. note:: + + Take care of specifying the absolute path to layout in the function. + + + +.. rubric:: Footnotes + +.. [#f1] In the default template, the currently active entry will be rendered with an "active" class : `
  • ...
  • ` From e9667ef8d621a61d7fca99c89f41343947bafc20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 14 Oct 2014 22:28:13 +0200 Subject: [PATCH 021/157] fix footnotes --- source/development/create-a-new-bundle.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/development/create-a-new-bundle.rst b/source/development/create-a-new-bundle.rst index b1a581bd4..7c987a70d 100644 --- a/source/development/create-a-new-bundle.rst +++ b/source/development/create-a-new-bundle.rst @@ -27,4 +27,6 @@ The easiest way to achieve this is seems to be : TODO +.. rubric:: Footnotes + .. [#f1] Be aware that we use the Affero GPL Licence, which ensure that all users must have access to derivative works done with this software. From 74f2b23b5412a8a618081248180d685770e84e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 14 Oct 2014 22:32:51 +0200 Subject: [PATCH 022/157] typo in structure to emphasis --- source/development/manual/routing-and-menus.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/development/manual/routing-and-menus.rst b/source/development/manual/routing-and-menus.rst index 5786f3fda..c3b8b61cb 100644 --- a/source/development/manual/routing-and-menus.rst +++ b/source/development/manual/routing-and-menus.rst @@ -10,9 +10,9 @@ Routing and menus ***************** -The _Chill_'s architecture allows to choose bundle on each installation. This may lead to a huge diversity of installations, and a the developper challenge is to make his code working with all those possibles installations. +The *Chill*'s architecture allows to choose bundle on each installation. This may lead to a huge diversity of installations, and a the developper challenge is to make his code working with all those possibles installations. -_Chill_ uses menus to let users access easily to the most used functionalities. For instance, when you land on a "Person" page, you may access directly to his activities, notes, documents, ... in a single click on a side menu. +*Chill* uses menus to let users access easily to the most used functionalities. For instance, when you land on a "Person" page, you may access directly to his activities, notes, documents, ... in a single click on a side menu. For a developer, it is easy to extend this menu with his own entries. From 0e5cf6fe86cee3237267d2dd7c2d775bc510e0e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 17 Oct 2014 07:56:38 +0200 Subject: [PATCH 023/157] update project name --- source/installation/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 518acf42f..03ddd8710 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -89,7 +89,7 @@ Create your Chill project using composer: .. code-block:: bash - php composer.phar create-project champs-libres/chill-standard \ + php composer.phar create-project chill-project/standard \ path/to/your/directory --stability=dev .. note:: From b424ec49bcf9a08a44449d3c62c04761cb5b3da7 Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Fri, 17 Oct 2014 17:29:06 +0200 Subject: [PATCH 024/157] Refactoring : champs-libres -> chill-project --- source/development/installation.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/development/installation.rst b/source/development/installation.rst index 6c5783ebb..87af17d5b 100644 --- a/source/development/installation.rst +++ b/source/development/installation.rst @@ -26,7 +26,7 @@ At first, You should add the `--prefer-source` argument when you create project. .. code-block:: bash - php composer.phar create-project champs-libres/chill-standard \ + php composer.phar create-project chill-project/standard \ path/to/your/directory --stability=dev --prefer-source Secondly, if composer ask you the following question : :: @@ -39,12 +39,12 @@ This will install a project. All downloaded modules will be stored in the `/vend .. code-block:: bash - $ cd vendor/champs-libres/chill-main/ + $ cd vendor/chill-project/main/ $ git remote -v - composer git://github.com/Champs-Libres/ChillMain.git (fetch) - composer git://github.com/Champs-Libres/ChillMain.git (push) - origin git://github.com/Champs-Libres/ChillMain.git (fetch) - origin git@github.com:Champs-Libres/ChillMain.git (push) + composer git://github.com/Chill-project/Standard.git (fetch) + composer git://github.com/Chill-project/Standard.git (push) + origin git://github.com/Chill-project/Standard.git (fetch) + origin git@github.com:Chill-project/Standard.git (push) Working with your own fork ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -100,7 +100,7 @@ The command `composer status` (`see composer documentation Date: Thu, 23 Oct 2014 14:59:05 +0200 Subject: [PATCH 025/157] refs #235 --- source/installation/installation.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 518acf42f..c954d9db7 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -113,6 +113,10 @@ If composer ask you the following question : :: You may answer `Y` (Yes), or simply press the `return` button. + +TODO insert 'check.php' + + Launch your server ^^^^^^^^^^^^^^^^^^ From a5ff98d855f02a9ca752fd7a3f2f44197708eefe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 5 Nov 2014 16:30:59 +0100 Subject: [PATCH 026/157] [work in progress] refs #265 documentation for custom fields --- source/development/bundles/custom-fields.rst | 119 +++++++++++++++++++ source/development/bundles/index.rst | 16 +++ source/development/bundles/main.rst | 22 ++++ source/development/index.rst | 3 +- 4 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 source/development/bundles/custom-fields.rst create mode 100644 source/development/bundles/index.rst create mode 100644 source/development/bundles/main.rst diff --git a/source/development/bundles/custom-fields.rst b/source/development/bundles/custom-fields.rst new file mode 100644 index 000000000..835921154 --- /dev/null +++ b/source/development/bundles/custom-fields.rst @@ -0,0 +1,119 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Custom fields bundle +==================== + +This bundle provide the ability to add custom fields to existing entities. + +Those custom fields contains extra data and will be stored in the DB, along with other data's entities. It may be string, text, date, ... or a link to an existing or to-be-created other entities. + +In the database, custom fields are stored in json format. + +.. seealso:: + + The full specification discussed `here. `_ + + `JSON Type on postgresql documentation `_ + +Custom Fields concepts +---------------------- + +.. warning:: + + This section is incomplete + +Allow custom fields on a entity +-------------------------------- + +As a developer, you may allow your users to add custom fields on your entities. + +Create a json field on your entity +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Declare a json field in your database : + +.. code-block:: yaml + + Chill\CustomFieldsBundle\Entity\BlopEntity: + type: entity + # ... + fields: + customField: + type: json_array + +Create the field accordingly : + +.. code-block:: php + + namespace Chill\CustomFieldsBundle\Entity; + + /** + * BlopEntity + */ + class BlopEntity + { + + /** + * @var array + */ + private $customField = array(); + + /** + * This method will be required for the Form component to record the + * custom fields into the class + * + * @param array $customField + * @return BlopEntity + */ + public function setCustomField(array $customField) + { + $this->customField = $customField; + return $this; + } + + /** + * Required by Forms to retrieve informations + * + * @return array + */ + public function getCustomField() + { + return $this->customField; + } + +Declare your customizable class in configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Two methods are available : + +* In your app/config.yml file. This is the easiest method, but discouraged because it will reduce the ease for installation. +* In your Configuration class + +In app/config.yml file +"""""""""""""""""""""" + +Add those file under `chill_custom_fields` section : + +.. code-block:: yaml + + chill_custom_fields: + customizables_class: + - { class: Chill\CustomFieldsBundle\Entity\BlopEntity, name: blop_entity } + +* The `name` allow you to define a string which is translatable. This string will appears when chill's admin will add/retrieve new customFieldsGroup. +* The class, which is a full FQDN class path + + + + +.. glossary:: + + customFieldsGroup + TODO + diff --git a/source/development/bundles/index.rst b/source/development/bundles/index.rst new file mode 100644 index 000000000..c077c1633 --- /dev/null +++ b/source/development/bundles/index.rst @@ -0,0 +1,16 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Official bundles documentation +############################### + +.. toctree:: + :maxdepth: 2 + + Main Bundle + Custom Fields diff --git a/source/development/bundles/main.rst b/source/development/bundles/main.rst new file mode 100644 index 000000000..254317691 --- /dev/null +++ b/source/development/bundles/main.rst @@ -0,0 +1,22 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Main bundle +########### + +This bundle is **required** for running Chill. + +This bundle provide : + +* Access control model (users, groups, and all concepts) +* ... + + +.. warning:: + + this section is incomplete. diff --git a/source/development/index.rst b/source/development/index.rst index db2afa25f..c06ca6db5 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -16,8 +16,9 @@ As Chill rely on the `symfony `_ framework, reading the fram Install Chill for development Instructions to create a new bundle - Bundles and tests + Testing manual/index.rst + Official bundle's documentation Help, I am lost ! From 9289a6304ba063089e9d1c2743f9245e0b988c7e Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Fri, 7 Nov 2014 12:31:09 +0100 Subject: [PATCH 027/157] CLChillMainBundle -> ChillMainBundle --- source/development/make-test-working.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/development/make-test-working.rst b/source/development/make-test-working.rst index d63655440..d05e3bace 100644 --- a/source/development/make-test-working.rst +++ b/source/development/make-test-working.rst @@ -149,7 +149,7 @@ This file boostrap the app. It contains three functions. This is the file used i { return array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), - new \CL\Chill\MainBundle\CLChillMainBundle(), + new Chill\MainBundle\ChillMainBundle(), new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), new \Symfony\Bundle\AsseticBundle\AsseticBundle(), From 91ca709cf9b7294536a24a4c0e97c0cbd628c767 Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Fri, 7 Nov 2014 12:32:25 +0100 Subject: [PATCH 028/157] AcmeHelloBundle -> ChillMainBundle --- source/development/make-test-working.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/development/make-test-working.rst b/source/development/make-test-working.rst index d05e3bace..7b1919c4b 100644 --- a/source/development/make-test-working.rst +++ b/source/development/make-test-working.rst @@ -167,7 +167,7 @@ This file boostrap the app. It contains three functions. This is the file used i */ public function getCacheDir() { - return sys_get_temp_dir().'/AcmeHelloBundle/cache'; + return sys_get_temp_dir().'/ChillMainBundle/cache'; } /** @@ -175,7 +175,7 @@ This file boostrap the app. It contains three functions. This is the file used i */ public function getLogDir() { - return sys_get_temp_dir().'/AcmeHelloBundle/logs'; + return sys_get_temp_dir().'/ChillMainBundle/logs'; } } From 515b8a07629d7f93ce4806cbf1e2600d3eec51e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 7 Nov 2014 15:57:07 +0100 Subject: [PATCH 029/157] add doc for menus and flashbag --- source/development/flashbags.rst | 35 +++++++++ source/development/index.rst | 2 + source/development/menus.rst | 123 +++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 source/development/flashbags.rst create mode 100644 source/development/menus.rst diff --git a/source/development/flashbags.rst b/source/development/flashbags.rst new file mode 100644 index 000000000..d6334a7a3 --- /dev/null +++ b/source/development/flashbags.rst @@ -0,0 +1,35 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _flashbags : + +Flashbags: message to users +**************************** + +The four following levels are defined : + ++-----------+----------------------------------------------------------------------------------------------+ +|Key |Intent | ++===========+==============================================================================================+ +|alert |A message not linked with the user action, but which should require an action or a | +| |correction. | ++-----------+----------------------------------------------------------------------------------------------+ +|success |The user action succeeds. | ++-----------+----------------------------------------------------------------------------------------------+ +|notice |A simple message to give information to the user. The message may be linked or not linked with| +| |the user action. | ++-----------+----------------------------------------------------------------------------------------------+ +|warning |A message linked with an action, the user should correct. | ++-----------+----------------------------------------------------------------------------------------------+ +|error |The user's action failed: he must correct something to process the action. | ++-----------+----------------------------------------------------------------------------------------------+ + +.. seealso:: + + `Flash Messages on Symfony documentation `_ + Learn how to use flash messages in controller. diff --git a/source/development/index.rst b/source/development/index.rst index c06ca6db5..6eef8cdd5 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -16,6 +16,8 @@ As Chill rely on the `symfony `_ framework, reading the fram Install Chill for development Instructions to create a new bundle + Menus + Message to users Testing manual/index.rst Official bundle's documentation diff --git a/source/development/menus.rst b/source/development/menus.rst new file mode 100644 index 000000000..5e6cda3ea --- /dev/null +++ b/source/development/menus.rst @@ -0,0 +1,123 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _menus : + +Menus +***** + +Chill has created his own menu system + +.. seealso:: + + `Routes dans Chill [specification] `_ + The issue wich discussed the implementation of routes. + +Concepts +======== + +.. warning:: + + to be written + + + +Add a menu in a template +======================== + +In your twig template, use the `chill_menu` function : + +.. code-block:: html+jinja + + {{ chill_menu('person', { + 'layout': 'ChillPersonBundle::menu.html.twig', + 'args' : {'id': person.id }, + 'activeRouteKey': 'chill_person_view' + }) }} + +The available arguments are: + +* `layout` : a custom layout. Default to `ChillMainBundle:Menu:defaultMenu.html.twig` +* `args` : those arguments will be passed through the url generator. +* `activeRouteKey` must be the route key name. + +.. note:: + + The argument `activeRouteKey` may be a twig variable, defined elsewhere in your template, even in child templates. + + + +Create an entry in an existing menu +=================================== + +If a route belongs to a menu, you simply add this to his definition in routing.yml : + +.. code-block:: yaml + + chill_person_history_list: + pattern: /person/{person_id}/history + defaults: { _controller: ChillPersonBundle:History:list } + options: + #declare menus + menus: + # the route should be in 'person' menu : + person: + #and have those arguments : + order: 100 + label: menu.person.history + +* `order` (mandatory) : the order in the menu. It is preferrable to increment by far more than 1. +* `label` (mandatory) : a translatable string. +* `helper` (optional) : a text to help people to understand what does the menu do. Not used in default implementation. +* `condition` (optional) : an `Expression Language `_ which will make the menu appears or not. Typically, it may be used to say "show this menu only if the person concerned is more than 18". **Not implemented yet**. +* `access` (optional) : an Expression Language to evalute the possibility, for the user, to show this menu according to Access Control Model. **Not implemented yet.** + +You may add additional keys, but should not use the keys described above. + +You may add the same route to multiple menus : + +.. code-block:: yaml + + chill_person_history_list: + pattern: /person/{person_id}/history + defaults: { _controller: ChillPersonBundle:History:list } + options: + menus: + menu1: + order: 100 + label: menu.person.history + menu2: + order: 100 + label: another.label + + + +Customize menu rendering +======================== + +You may customize menu rendering by using the `layout` option. + +.. warning :: + + TODO : this part should be written. + + + + + + +.. _caveats : + +Caveats +======= + +Currently, you may pass arguments globally to each menu, and they will be all passed to route url. This means that : + +* the argument name in the route entry must match the argument key in menu declaration in twig template +* if an argument is missing to generate an url, the url generator will throw an error +* if the argument name is not declared in route entry, it will be added to the url, (example: `/my/route?additional=foo`) From 75cc85746e7c5e38a0f7aa6a6b1927e40e869dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 7 Nov 2014 15:57:21 +0100 Subject: [PATCH 030/157] typo --- source/development/make-test-working.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/development/make-test-working.rst b/source/development/make-test-working.rst index 7b1919c4b..3f9778173 100644 --- a/source/development/make-test-working.rst +++ b/source/development/make-test-working.rst @@ -228,4 +228,4 @@ You should add there all routing information needed for your bundle. chill_main_bundle: resource: "@CLChillMainBundle/Resources/config/routing.yml" -That's it. Tests should not pass. +That's it. Tests should pass. From a271072f239ce813b46a5e53249d1ca77b8cf27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 7 Nov 2014 16:02:18 +0100 Subject: [PATCH 031/157] precise the exception in case of missing parameter in url --- source/development/menus.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/development/menus.rst b/source/development/menus.rst index 5e6cda3ea..977591f1d 100644 --- a/source/development/menus.rst +++ b/source/development/menus.rst @@ -119,5 +119,5 @@ Caveats Currently, you may pass arguments globally to each menu, and they will be all passed to route url. This means that : * the argument name in the route entry must match the argument key in menu declaration in twig template -* if an argument is missing to generate an url, the url generator will throw an error +* if an argument is missing to generate an url, the url generator will throw a `Symfony\Component\Routing\Exception\MissingMandatoryParametersException` * if the argument name is not declared in route entry, it will be added to the url, (example: `/my/route?additional=foo`) From f70d3ef0b2d658ac5e1942b350edad33bc9bc1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sun, 9 Nov 2014 18:53:50 +0100 Subject: [PATCH 032/157] add dev tips for watching a field --- source/development/bundles/custom-fields.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/source/development/bundles/custom-fields.rst b/source/development/bundles/custom-fields.rst index 835921154..c30861e71 100644 --- a/source/development/bundles/custom-fields.rst +++ b/source/development/bundles/custom-fields.rst @@ -110,6 +110,20 @@ Add those file under `chill_custom_fields` section : * The class, which is a full FQDN class path +Development tips +----------------- + +If you want to test the rendering of a custom fields group, you may use this method : + +1. Run the built-in server **from the custom-fields directory** : + +.. code-block:: bash + + ./run-server.sh + +2. assuming that your custom fields id is `1`, go to your navigator and enter url : `http://localhost:8000/customfieldsgroup/test/render/2` + + .. glossary:: From f9ef4ab5154cdd8f61188890c07570f25986f3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sun, 9 Nov 2014 22:02:19 +0100 Subject: [PATCH 033/157] fix url --- source/development/bundles/custom-fields.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/development/bundles/custom-fields.rst b/source/development/bundles/custom-fields.rst index c30861e71..ae422b395 100644 --- a/source/development/bundles/custom-fields.rst +++ b/source/development/bundles/custom-fields.rst @@ -121,7 +121,7 @@ If you want to test the rendering of a custom fields group, you may use this met ./run-server.sh -2. assuming that your custom fields id is `1`, go to your navigator and enter url : `http://localhost:8000/customfieldsgroup/test/render/2` +2. assuming that your custom fields id is `1`, go to your navigator and enter url : `http://localhost:8000/customfieldsgroup/test/render/1` From 6d7567de01bc5b9cdaa67db79652d4d902bd5762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 10 Nov 2014 00:47:20 +0100 Subject: [PATCH 034/157] enable todo extension --- source/conf.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/conf.py b/source/conf.py index fb37071c5..2bee69683 100644 --- a/source/conf.py +++ b/source/conf.py @@ -334,6 +334,12 @@ epub_exclude_files = ['search.html'] # on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +#-- Options for todo + +todo_include_todos = True + +#-- include template if not on RTD + if not on_rtd: # only import and set the theme if we're building docs locally import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' From 168e12045c70a6cc6ca58b471a23c0ef1629c738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 10 Nov 2014 00:47:46 +0100 Subject: [PATCH 035/157] doc on custom fields rendering and form --- source/development/bundles/custom-fields.rst | 70 +++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/source/development/bundles/custom-fields.rst b/source/development/bundles/custom-fields.rst index ae422b395..885652a97 100644 --- a/source/development/bundles/custom-fields.rst +++ b/source/development/bundles/custom-fields.rst @@ -93,7 +93,7 @@ Declare your customizable class in configuration Two methods are available : * In your app/config.yml file. This is the easiest method, but discouraged because it will reduce the ease for installation. -* In your Configuration class +* In your Extension class : harder for devs, easier for installers. In app/config.yml file """""""""""""""""""""" @@ -103,12 +103,78 @@ Add those file under `chill_custom_fields` section : .. code-block:: yaml chill_custom_fields: - customizables_class: + customizables_entities: - { class: Chill\CustomFieldsBundle\Entity\BlopEntity, name: blop_entity } * The `name` allow you to define a string which is translatable. This string will appears when chill's admin will add/retrieve new customFieldsGroup. * The class, which is a full FQDN class path +Automatically, in DepedencyInjection/Extension class +"""""""""""""""""""""""""""""""""""""""""""""""""""" + +.. todo:: + + Explain how to declare customizable entitites in DepedencyInjection/Extension. + +Rendering custom fields in a template +-------------------------------------- + +.. todo:: + + Develop + + +Custom Fields's form +--------------------- + +You should simply use the 'custom_field' type in a template, with the group you would like to render in the `group` option + +Example : + +.. warning:: + + The above code isn't tested. + +.. todo:: + + Test the above code. + +.. code-block:: php + + use Symfony\Component\Form\AbstractType; + use Symfony\Component\Form\FormBuilderInterface; + use Symfony\Component\OptionsResolver\OptionsResolverInterface; + use Chill\CustomFieldsBundle\Form\Type\CustomFieldType; + use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup; + + class BlopEntityType extends AbstractType + { + + public $group; + + public function __construct(CustomFieldsGroup $group) + { + $this->group = $group; + } + + + /** + * @param FormBuilderInterface $builder + * @param array $options + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('field1') + ->add('field2') + //->add('adress', new AdressType()) + ->add('customField', 'custom_field', array('group' => $group)) + ; + } + } + + + Development tips ----------------- From d3230043297c50bbc48e55fcfec69596ccbcbebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 10 Nov 2014 00:48:05 +0100 Subject: [PATCH 036/157] add a todo list on front page --- source/index.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/index.rst b/source/index.rst index d6276ee2e..3d3a8858f 100644 --- a/source/index.rst +++ b/source/index.rst @@ -37,6 +37,11 @@ Contribute * Chill-standard : https://github.com/Champs-Libres/chill-standard * Chill-main : https://github.com/Champs-Libres/ChillMain +TODO in documentation +===================== + +.. todolist:: + Licence ======== From 5c0a9090face81449db941e7e6e34f1993bf0a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 10 Nov 2014 13:07:32 +0100 Subject: [PATCH 037/157] add instructions to assume unchanged symfony files --- source/development/installation.rst | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/source/development/installation.rst b/source/development/installation.rst index 87af17d5b..85855d187 100644 --- a/source/development/installation.rst +++ b/source/development/installation.rst @@ -46,6 +46,37 @@ This will install a project. All downloaded modules will be stored in the `/vend origin git://github.com/Chill-project/Standard.git (fetch) origin git@github.com:Chill-project/Standard.git (push) +Deleted and added files after installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Composer will deleted unrequired files, and add some of them. This perfectly normal and will appears in your git index. But you should NOT delete those files. + + +This should appears : + +.. code-block:: bash + + $git status + #(...) + + Modifications qui ne seront pas validées : + (utilisez "git add/rm ..." pour mettre à jour ce qui sera validé) + (utilisez "git checkout -- ..." pour annuler les modifications dans la copie de travail) + + modifié: app/SymfonyRequirements.php + supprimé: app/SymfonyStandard/Composer.php + supprimé: app/SymfonyStandard/RootPackageInstallSubscriber.php + modifié: app/check.php + +You can ignore the locally using the `git update-index --assume-unchanged` command. + +.. code-block:: bash + + $ git update-index --assume-unchanged app/check.php + $ git update-index --assume-unchanged app/SymfonyRequirements.php + $ git update-index --assume-unchanged app/SymfonyStandard/Composer.php + $ git update-index --assume-unchanged app/SymfonyStandard/RootPackageInstallSuscriber.php + Working with your own fork ^^^^^^^^^^^^^^^^^^^^^^^^^^ From f6aaefd1e56ac3ef3ccd79388b1193f5950c6cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 10 Nov 2014 13:28:20 +0100 Subject: [PATCH 038/157] add docs for custom fields twig functions --- source/development/bundles/custom-fields.rst | 44 +++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/source/development/bundles/custom-fields.rst b/source/development/bundles/custom-fields.rst index 885652a97..2cff00294 100644 --- a/source/development/bundles/custom-fields.rst +++ b/source/development/bundles/custom-fields.rst @@ -119,9 +119,49 @@ Automatically, in DepedencyInjection/Extension class Rendering custom fields in a template -------------------------------------- -.. todo:: +Two function are available : - Develop +* `chill_custom_field_widget` to render the widget. This function is defined on a customFieldType basis. +* `chill_custom_field_label` to render the label. You can customize the label rendering by choosing the layout you would like to use. + +**chill_custom_field_label** + +The signature is : + +* `CustomField|object|string` **$customFieldOrClass** either a customField OR a customizable_entity OR the FQDN of the entity +* `string` **$slug** only necessary if the first argument is NOT a CustomField instance +* `array` **params** the parameters for rendering. Currently, 'label_layout' allow to choose a different label. Default is 'ChillCustomFieldsBundle:CustomField:render_label.html.twig' + +Examples + +.. code-block:: jinja + + {{ chill_custom_field_label(customField) }} + + {{ chill_custom_field_label(entity, 'slug') }} + + {{ chill_custom_field_label('Path\To\Entity', 'slug') }} + + +**chill_custom_field_widget** + +* array **$fields** the array raw, as stored in the db +* CustomField|object|string $customFieldOrClass either a customField OR a customizable_entity OR the FQDN of the entity +* string **$slug** only necessary if the first argument is NOT a CustomField instance + +Examples: + +.. code-block:: jinja + + {{ chill_custom_field_widget(entity.customFields, customField) }} + + {{ chill_custom_field_widget(entity.customFields, entity, 'slug') }} + + {{ chill_custom_field_widget(fields, 'Path\To\Entity', 'slug') }} + +.. warning:: + + This feature is not fully tested. See `the corresponding issue `_ Custom Fields's form From 63d994bc49fe087af469db98bed3257c69f12509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 4 Dec 2014 15:59:57 +0100 Subject: [PATCH 039/157] documentation about migrations close #216 --- source/development/index.rst | 1 + source/development/migrations.rst | 95 +++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 source/development/migrations.rst diff --git a/source/development/index.rst b/source/development/index.rst index 6eef8cdd5..2fb60d60d 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -18,6 +18,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Instructions to create a new bundle Menus Message to users + Database migrations Testing manual/index.rst Official bundle's documentation diff --git a/source/development/migrations.rst b/source/development/migrations.rst new file mode 100644 index 000000000..7c3bbf73c --- /dev/null +++ b/source/development/migrations.rst @@ -0,0 +1,95 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Database Migrations +******************** + +Every bundle potentially brings his own database operations : to persist entities, developers have to create database schema, creating indexes,... + +Those schema might be changed (the less is the better) from time to time. + +Consequence: each bundle should bring his own migration files, which will bring the database consistent with the operation you will run in your code. They will be gathered into the app installation, ready to be executed by the chill's installer. + +Currently, we use `doctrine migration`_ to manage those migration files. A `composer`_ script located in the **chill standard** component will copy the migrations from your bundle to the doctrne migration's excepted directory after each install and/or update operation. + +.. seealso:: + + The `doctrine migration`_ documentation + Learn concepts about migrations files and scripts and the doctrine ORM + + The `doctrine migration bundle`_ documentation + Learn about doctrine migration integration with Symfony framework + +Shipping migration files +======================== + +Migrations files should be shipped under the Resource/migrations directory. You could customize the migration directory by adding extra information in your composer.json: + +.. code-block:: json + + "extra": { + "migration-source": "path/to/my/dir" + } + +The class namespace should be `Application\Migrations`, as expected by doctrine migration. Only the files which will be executed by doctrine migration will be moved: they must have the pattern `VersionYYYYMMDDHHMMSS.php` where YYYY is the year, MM the month, DD the day, HH the hour, MM the month and SS the second of creation. + +They will be moved automatically by composer when you install or update a bundle. + +Executing migration files +========================== + +The installers will have to execute migrations files manually, running + +.. code-block:: bash + + php app/console doctrine:migrations:status #will give the current status of the database + php app/console doctrine:migrations:migrate #process the update + + +Updating migration files +========================= + +.. warning:: + + After an installation, migration files will be executed and registered as executed in the database (the version timestamp is recorded into the :title:`migrations_versions` table). If you update your migration file code, the file will still be considered as "executed" by doctrine migration, which will never recommand users to execute the migration again. + + Consequently, updating migration file should only be considered during development phase, and not published on public git branches. If you want to edit your database schema, you should create a new migration file, with a new timestamp, which will proceed to your schema adaptations. + +Every time a migration file is discovered, the composer'script will check if the migration exists in the local migration directory. If yes, the script will compare two file for changes (using a md5 hash). If migrations are discovered, the script will ask the installer to know if he must replace the file or ignore it. + +.. note:: + + You can manually run composer script by launching `composer run-script post-update-cmd` from your root chill installation's directory. + + +.. _doctrine migration: http://www.doctrine-project.org/projects/migrations.html +.. _doctrine migration bundle : http://symfony.com/doc/master/bundles/DoctrineMigrationsBundle/index.html +.. _composer : https://getcomposer.org + +Tips for development +==================== + +Generation +---------- + +You can generate migration file from the command line, using those commands: + +* `php app/console doctrine:migrations:diff` to generate a migration file by comparing your current database to your mapping information +* `php app/console doctrine:migrations:generate` to generate a blank migration file. + +Those files will be located into `app/DoctrineMigrations` directory. You will have to copy those file to your the directory `Resource/migrations` into your bundle directory. + +Comments and documentation +-------------------------- + +As files are copied from your bundle to the `app/DoctrineMigrations` directory, the link between your bundle and the copied file will be unclear. Please add all relevant documentation which will allow future developers to make a link between your file and your bundle. + +Inside the script +----------------- + +The script which move the migrations files to app directory `might be found here `_. From 208bc43e1fd1d1b17305ef2f3394fcf9d8c0ea1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 4 Dec 2014 16:13:11 +0100 Subject: [PATCH 040/157] add step "app/check.php" close #235 --- source/installation/installation.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 493afeaaa..5b7284e07 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -113,8 +113,16 @@ If composer ask you the following question : :: You may answer `Y` (Yes), or simply press the `return` button. +Check your requirements +^^^^^^^^^^^^^^^^^^^^^^^ -TODO insert 'check.php' +You should check your installation by running + +.. code-block:: bash + + php app/check.php + +Which will give you information about how your installation fullfill the requirements to running Chill, and give you advices to optimize your installation. Launch your server From ec446826a38e7c00dc7e265f11e63050c9a80c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 4 Dec 2014 16:16:36 +0100 Subject: [PATCH 041/157] typo --- source/installation/installation.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 5b7284e07..232dc5d3e 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -36,7 +36,7 @@ You won't need any web server for demonstration or development. Client requirements ^^^^^^^^^^^^^^^^^^^ -Chill is accessible through a web browser. Currently, we focus our support on `Firefox`_, because firefox is open source, cross-platform, and very well active. The software should work with other browser (Chromium, Opera, ...) but some functionalities should break. +Chill is accessible through a web browser. Currently, we focus our support on `Firefox`_, because firefox is open source, cross-platform, and very well active. The software should work with other browser (Chromium, Opera, ...) but some functionalities might break. Preparation ----------- @@ -63,7 +63,7 @@ Install composer on your system : curl -sS https://getcomposer.org/installer | php -move composer.phar to your system +Move composer.phar to your system """"""""""""""""""""""""""""""""" .. note:: From cc6326bc158f2f0578b5da530fe22ba15088e471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 4 Dec 2014 16:33:54 +0100 Subject: [PATCH 042/157] add reference on how to create the database after installation --- source/index.rst | 1 + source/installation/install_new_bundles.rst | 16 ++++++++ source/installation/installation.rst | 44 +++++++++++++++++++-- 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 source/installation/install_new_bundles.rst diff --git a/source/index.rst b/source/index.rst index 3d3a8858f..32b8f4d4a 100644 --- a/source/index.rst +++ b/source/index.rst @@ -24,6 +24,7 @@ Contents of this documentation: :maxdepth: 2 installation/installation.rst + installation/install_new_bundles.rst development/index.rst diff --git a/source/installation/install_new_bundles.rst b/source/installation/install_new_bundles.rst new file mode 100644 index 000000000..85b31d323 --- /dev/null +++ b/source/installation/install_new_bundles.rst @@ -0,0 +1,16 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _install-new-bundles: + +Install new bundles to your installation +######################################## + +.. todo:: + + the section "install new bundles" must be written. Help appreciated :-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 232dc5d3e..e1407f4ce 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -107,11 +107,21 @@ Composer will download `the standard architecture`_ and ask you a few question a You may accept the default parameters of `debug_toolbar`, `debug_redirects` and `use_assetic_controller` for a demonstration installation. For production, set them all to `false`. -If composer ask you the following question : :: +.. note:: - Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? + If composer ask you the following question : :: -You may answer `Y` (Yes), or simply press the `return` button. + Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? + + You may answer `Y` (Yes), or simply press the `return` button. + +.. note:: + + At the end of your installation, composer will warn you to execute database migration script, with this message : :: + + Some migration files have been imported. You should run `php app/console doctrine:migrations:status` and/or `php app/console doctrine:migrations:migrate` to apply them to your DB. + + We will proceed to this step some steps further. See :ref:`create-database-schema`. Check your requirements ^^^^^^^^^^^^^^^^^^^^^^^ @@ -125,6 +135,34 @@ You should check your installation by running Which will give you information about how your installation fullfill the requirements to running Chill, and give you advices to optimize your installation. +.. _create-database-schema: + +Create your database schema +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This step will create your table and minimum information into your database. Simply run + +.. code-block:: bash + + php app/console doctrine:migrations:migrate + +SQL queries will be printed into your console. + + +Populate your database with basic information +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Once your database schema is ready, you should populate your database with some basic information. Those are provided through scripts and might depends from the bundle you choose to install (see :ref:`install-new-bundles`) + +The main bundle require two scripts to be executed : + +.. code-block:: bash + + php app/console chill:main:countries:populate + php app/console chill:main:languages:populate + +Those will populate database, respectively, with countries (using ISO declaration) and languages (using, also, ISO informations). + Launch your server ^^^^^^^^^^^^^^^^^^ From 7e5945d6a1d84bf8050c3c095cc64b26881bdb98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 4 Dec 2014 16:41:46 +0100 Subject: [PATCH 043/157] improve documentation hierarchy We create a level into the "installation" directory, and create an index file into this directory. This will also be more consistent with the "developer" part. --- source/index.rst | 3 +-- source/installation/index.rst | 32 ++++++++++++++++++++++++++++ source/installation/installation.rst | 7 +++++- 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 source/installation/index.rst diff --git a/source/index.rst b/source/index.rst index 32b8f4d4a..a7db0cb28 100644 --- a/source/index.rst +++ b/source/index.rst @@ -23,8 +23,7 @@ Contents of this documentation: .. toctree:: :maxdepth: 2 - installation/installation.rst - installation/install_new_bundles.rst + installation/index.rst development/index.rst diff --git a/source/installation/index.rst b/source/installation/index.rst new file mode 100644 index 000000000..099628e02 --- /dev/null +++ b/source/installation/index.rst @@ -0,0 +1,32 @@ +.. chill-doc documentation master file, created by + sphinx-quickstart on Sun Sep 28 22:04:08 2014. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Instructions about installation +################################ + +You will learn here : + +.. toctree:: + :maxdepth: 2 + + How to install the software + Add new bundles to your installation + + +.. todo:: + + We should write a section on "how to update your installation". + +.. todo:: + + We should write a section on "what are the concepts of chill" and explain what is a bundle, why we should install it, how to find them, ... diff --git a/source/installation/installation.rst b/source/installation/installation.rst index e1407f4ce..4aa05a50c 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -92,6 +92,12 @@ Create your Chill project using composer: php composer.phar create-project chill-project/standard \ path/to/your/directory --stability=dev +You should, now, move your cursor to the new directory + +.. code-block:: bash + + cd path/to/your/directory + .. note:: Until now, the stability of the project is set to "dev". Do not forget this argument, or composer will fail to download and create the project. @@ -170,7 +176,6 @@ If everything was fine, you are able to launch your built-in server : .. code-block:: bash - cd path/to/your/directory php app/console server:run Your server should now be available at `http://localhost:8000`. Type this address on your browser and you should see the homepage. From 8129f156de0d6e230e57bf246aac2025d11e09a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 4 Dec 2014 16:50:53 +0100 Subject: [PATCH 044/157] remove a warning, as I tested the instructions locally --- source/development/installation.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/source/development/installation.rst b/source/development/installation.rst index 85855d187..069d98df9 100644 --- a/source/development/installation.rst +++ b/source/development/installation.rst @@ -80,10 +80,6 @@ You can ignore the locally using the `git update-index --assume-unchanged` comma Working with your own fork ^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. warning:: - - This section has not been tested yet. Feedback wanted. - Ideally, you will work on a fork of the main github repository. To ensure that composer will download the code from **your** repository, you will have to adapt the `composer.json` file accordingly, using your own repository. Add the following lines to your composer.json file if you want to force composer to download from your own repository. This will force to use your own repository for the ChillMain bundle, insert in `composer.json` the following lines : From f2e4146a439e58fb497d4f3de4ab882869357db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 4 Dec 2014 16:53:19 +0100 Subject: [PATCH 045/157] split text on multiple lines for readability --- source/installation/installation.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 4aa05a50c..1cfc4b43f 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -125,7 +125,9 @@ You may accept the default parameters of `debug_toolbar`, `debug_redirects` and At the end of your installation, composer will warn you to execute database migration script, with this message : :: - Some migration files have been imported. You should run `php app/console doctrine:migrations:status` and/or `php app/console doctrine:migrations:migrate` to apply them to your DB. + Some migration files have been imported. You should run + `php app/console doctrine:migrations:status` and/or + `php app/console doctrine:migrations:migrate` to apply them to your DB. We will proceed to this step some steps further. See :ref:`create-database-schema`. From ceef7e746efe5c728e0b3f838493949eebbdcd72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 4 Dec 2014 16:57:32 +0100 Subject: [PATCH 046/157] warning about data losing and migration files updating --- source/development/migrations.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/development/migrations.rst b/source/development/migrations.rst index 7c3bbf73c..5a3252160 100644 --- a/source/development/migrations.rst +++ b/source/development/migrations.rst @@ -56,7 +56,7 @@ Updating migration files .. warning:: - After an installation, migration files will be executed and registered as executed in the database (the version timestamp is recorded into the :title:`migrations_versions` table). If you update your migration file code, the file will still be considered as "executed" by doctrine migration, which will never recommand users to execute the migration again. + After an installation, migration files will be executed and registered as executed in the database (the version timestamp is recorded into the :title:`migrations_versions` table). If you update your migration file code, the file will still be considered as "executed" by doctrine migration, which will not offers the possibility to run the migration again. Consequently, updating migration file should only be considered during development phase, and not published on public git branches. If you want to edit your database schema, you should create a new migration file, with a new timestamp, which will proceed to your schema adaptations. @@ -74,6 +74,11 @@ Every time a migration file is discovered, the composer'script will check if the Tips for development ==================== +Migration and data +------------------ + +Each time you create a migration script, you should ensure that it will not lead to data losing. Eventually, feel free to use intermediate steps. + Generation ---------- From 30338451463aac8e1884b411b6628ca8f90c1285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sun, 4 Jan 2015 22:29:27 +0100 Subject: [PATCH 047/157] Add unaccent requirement --- source/installation/installation.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 1cfc4b43f..8aafc2131 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -25,7 +25,7 @@ Requirements Server requirements ^^^^^^^^^^^^^^^^^^^^ -* a postgresql database. The minimum version is postgresql 9.3, but we are working on developing on the 9.4 branch, which will provide features which will ease developper work +* a postgresql database, with the `*unaccent* extension`_ enabled. The minimum version is postgresql 9.3, but we are working on developing on the 9.4 branch, which will provide features which will ease developper work * php 5.5 * If you run Chill in production mode, you should also install a web server (apache, ngnix, ...). We may use php built-in server for testing and development. @@ -33,6 +33,10 @@ Within this documentation, we are going to describe installation on Unix systems You won't need any web server for demonstration or development. +.. note:: + + Installing unaccent extension on ther server is possible with the package `postgresql-contrib-9.x` (replace 9.x with your server version). The extension may be enabled with running `CREATE EXTENSION unaccent;` in the database, with a superuser account. + Client requirements ^^^^^^^^^^^^^^^^^^^ @@ -188,3 +192,4 @@ Your server should now be available at `http://localhost:8000`. Type this addres .. _composer: https://getcomposer.org .. _Firefox: https://www.mozilla.org .. _symfony framework: http://symfony.com +.. _*unaccent* extension: http://www.postgresql.org/docs/current/static/unaccent.html From 21485649dbb6453c2dc9cbe0dd321d825b89ca92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 5 Jan 2015 16:21:14 +0100 Subject: [PATCH 048/157] Documente search terms on person bundl --- source/development/bundles/index.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/development/bundles/index.rst b/source/development/bundles/index.rst index c077c1633..31959ecc6 100644 --- a/source/development/bundles/index.rst +++ b/source/development/bundles/index.rst @@ -12,5 +12,6 @@ Official bundles documentation .. toctree:: :maxdepth: 2 - Main Bundle - Custom Fields + Main bundle + Custom Fields bundle + Person bundle From cd9b95c7ca5229b21dcae7eb514f6823c6c75290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 5 Jan 2015 16:29:23 +0100 Subject: [PATCH 049/157] add missing report.rst file --- source/development/bundles/person.rst | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 source/development/bundles/person.rst diff --git a/source/development/bundles/person.rst b/source/development/bundles/person.rst new file mode 100644 index 000000000..e4208d01d --- /dev/null +++ b/source/development/bundles/person.rst @@ -0,0 +1,47 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Person bundle +########### + +This bundle provides the ability to record people in the software. This bundle is required by other bundle + +Entities provided +***************** + +.. todo:: + + describe entities provided by person bundle + + +Search terms +************* + +The class `Chill\PersonBundle\Search\PersonSearch` provide the search module. + +Domain +======= + +The search upon "person" is provided by default. The `@person` domain search may be omitted. + +* `@person` is the domain search for people. + + +Arguments +========= + +* `firstname` : provide the search on firstname. Example : `firstname:Depardieu`. May match part of the firstname (`firsname:dep` will match **Dep**ardieu) +* `lastname` : provide the search on lastname. May match part of the lastname. +* `birthdate` : provide the search on the birthdate. Example : `birthdate:1996-01-19` +* `gender`: performs search on man/woman. The accepted values are `man` or `woman`. +* `nationality` : performs search on nationality. Value must be a country code `as described in ISO 3166 `_. Example : `nationality:FR`. + +Default +======= + +The default search is performed on firstname and/or lastname. Both are concatened before search. If values are separated by spaces, the clause `AND` is used : the search `dep ge` will match '**Gé**rard **Dep**ardieu` or 'Jean **Dep**a**ge**lles', but not 'Charline **Dep**ardieu' (missing 'Ge' in word). From 6ea5a9227d302212df144b9fe25edac62c7d4326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 15 Jan 2015 09:27:38 +0100 Subject: [PATCH 050/157] move bundle directory at root This should ease the browsing fo documentation (less menu and submenus) --- source/{development => }/bundles/custom-fields.rst | 0 source/{development => }/bundles/index.rst | 9 ++++++++- source/{development => }/bundles/main.rst | 0 source/{development => }/bundles/person.rst | 0 source/development/index.rst | 1 - source/index.rst | 1 + 6 files changed, 9 insertions(+), 2 deletions(-) rename source/{development => }/bundles/custom-fields.rst (100%) rename source/{development => }/bundles/index.rst (63%) rename source/{development => }/bundles/main.rst (100%) rename source/{development => }/bundles/person.rst (100%) diff --git a/source/development/bundles/custom-fields.rst b/source/bundles/custom-fields.rst similarity index 100% rename from source/development/bundles/custom-fields.rst rename to source/bundles/custom-fields.rst diff --git a/source/development/bundles/index.rst b/source/bundles/index.rst similarity index 63% rename from source/development/bundles/index.rst rename to source/bundles/index.rst index 31959ecc6..9b7921acc 100644 --- a/source/development/bundles/index.rst +++ b/source/bundles/index.rst @@ -6,12 +6,19 @@ A copy of the license is included in the section entitled "GNU Free Documentation License". -Official bundles documentation +Bundles documentation ############################### +You will find here documentation about bundles working with Chill. + .. toctree:: :maxdepth: 2 Main bundle Custom Fields bundle Person bundle + +Your bundle here ? +------------------- + +The contributors still do not have a policy about those bundle integration, but we would like to be very open on this subject. Please write to us `or open an issue `_. diff --git a/source/development/bundles/main.rst b/source/bundles/main.rst similarity index 100% rename from source/development/bundles/main.rst rename to source/bundles/main.rst diff --git a/source/development/bundles/person.rst b/source/bundles/person.rst similarity index 100% rename from source/development/bundles/person.rst rename to source/bundles/person.rst diff --git a/source/development/index.rst b/source/development/index.rst index 2fb60d60d..356ff124d 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -21,7 +21,6 @@ As Chill rely on the `symfony `_ framework, reading the fram Database migrations Testing manual/index.rst - Official bundle's documentation Help, I am lost ! diff --git a/source/index.rst b/source/index.rst index a7db0cb28..20825f23d 100644 --- a/source/index.rst +++ b/source/index.rst @@ -25,6 +25,7 @@ Contents of this documentation: installation/index.rst development/index.rst + Bundles Contribute From 9762df463f5a685ed87fcb603459312e4f703f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 15 Jan 2015 15:34:46 +0100 Subject: [PATCH 051/157] Documentation about search refs #223 and refs #377 --- source/development/index.rst | 1 + source/development/searching.rst | 250 +++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 source/development/searching.rst diff --git a/source/development/index.rst b/source/development/index.rst index 356ff124d..eb5f2f7bf 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -19,6 +19,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Menus Message to users Database migrations + Searching Testing manual/index.rst diff --git a/source/development/searching.rst b/source/development/searching.rst new file mode 100644 index 000000000..c033c0e17 --- /dev/null +++ b/source/development/searching.rst @@ -0,0 +1,250 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + + +Searching +********* + +Chill should provide information needed by users when they need it. Searching within bundle, entities,... is an important feature to achieve this goal. + +The Main Bundle provide interfaces to ease the developer work. It will also attempt that search will work in the same way accross bundles. + +.. contents:: Table of content + :local: + +.. seealso:: + + `Our blog post about searching (in French) `_ + This blog post give some information for end-users about searching. + + `The issue about search behaviour `_ + Where the search behaviour is defined. + +Searching in a glance for developers +==================================== + +Chill suggests to use an easy-to-learn language search. + +.. note:: + + We are planning to provide a form to create automatically search pattern according to this language. Watch the `issue regarding this feature `_. + +The language is an association of search terms. Search terms may contains : + +- **a domain**: this is "the domain you want to search" : it may some entities like people, reports, ... Example : `@person` to search accross people, `@report` to browse reports, ... The search pattern may have **a maximum of one** domain by search, providing more should throw an error, and trigger a warning for users. +- **arguments and their values** : This is "what you search". Arguments narrow the search to specific fields : username, date of birth, nationality, ... The syntax is `argument:value`. I.e.: ` birthdate:2014-12-15`, `firstname:Depardieu`, ... **Arguments are optional**. If the value of an argument contains spaces or characters like punctuation, quotes ("), the value should be provided between parenthesis : `firstname:(Van de snoeck)`, `firstname:(M'bola)`, ... +- **default value** : this the "rest" of the search, not linked with any arguments or domain. Example : `@person dep` (`dep` is the "default value"), or simply `dep` if any domain is provided (which is perfectly acceptable). If a string is not idenfied as argument or domain, it will be present in the "default" term. + +If a search pattern (provided by the user) does not contains any domain, the search must be run across default domain/search modules. + +A domain may be supported by different search modules. For instance, if you provide the domain `@person`, the end-user may receive results of exact firstname/lastname, but also result with spelling suggestion, ... **But** if results do not fit into the first page (if you have 75 results and the screen show only 50 results), the next page should contains only the results from the required module. + +For instance : a user search across people by firstname/lastname, the exact spelling contains 10 results, the "spelling suggestion" results contains 75 names, but show only the first 50. If the user want to see the last 25, the next screen should not contains the results by firstname/lastname. + +Allowed characters as arguments +=============================== + +In order to execute regular expression, the allowed chararcters in arguments are a-z characters, numbers, and the sign '-'. Spaces and special characters like accents are note allowed (the accents are removed during parsing). + +Special characters and uppercase +================================ + +The search should not care about lowercase/uppercase and accentued characters. Currently, they are removed automatically by the `chill.main.search_provider`. + +Implementing search module for dev +=================================== + +To implement a search module, you should : + +- create a class which implements the `Chill\MainBundle\Search\SearchInterface` class. An abstract class `Chill\MainBundle\Search\AbstractSearch` will provide useful assertions for parsing date string to `DateTime` objects, ... +- register the class as a service, and tag the service with `chill.search` and an appropriate alias + +The search logic is provided under the `/search` route. + +.. seealso:: + + `The implementation of a search module in Person bundle `_ + An example of implementationhttps://github.com/Chill-project/Main/blob/master/DependencyInjection/SearchableServicesCompilerPass.php + +.. note:: + + **Internals explained** : the services tagged with `chill.search` are gathered into the `chill.main.search_provider` service during compilation (`see the compiler pass `_). + + The `chill.main.search_provider` service allow to : + + - retrieve all results (as html string) for all search module concerned by the search (according to the domain provided or modules marked as default) + - retrieve result for one search module + +The SearchInterface class +------------------------- + +.. code-block:: php + + namespace Chill\PersonBundle\Search; + + use Chill\MainBundle\Search\AbstractSearch; + use Doctrine\ORM\EntityManagerInterface; + use Chill\PersonBundle\Entity\Person; + use Symfony\Component\DependencyInjection\ContainerInterface; + use Symfony\Component\DependencyInjection\ContainerAware; + use Symfony\Component\DependencyInjection\ContainerAwareTrait; + use Chill\MainBundle\Search\ParsingException; + + class PersonSearch extends AbstractSearch + { + + // indicate which domain you support + // you may respond TRUE to multiple domain, according to your logic + public function supports($domain) + { + return 'person' === $domain; + } + + // if your domain must be called when no domain is provided, should return true + public function isActiveByDefault() + { + return true; + } + + // if multiple module respond to the same domain, indicate an order for your search. + public function getOrder() + { + return 100; + } + + + // This is where your search logic should be executed. + // This method must return an HTML string (a string with HTML tags) + // see below about the structure of the $term array + public function renderResult(array $terms, $start = 0, $limit = 50, array $options = array()) + { + return $this->container->get('templating')->render('ChillPersonBundle:Person:list.html.twig', + array( + // you should implements the `search` function somewhere :-) + 'persons' => $this->search($terms, $start, $limit, $options), + // recomposePattern is available in AbstractSearch class + 'pattern' => $this->recomposePattern($terms, array('nationality', + 'firstname', 'lastname', 'birthdate', 'gender'), $terms['_domain']), + // you should implement the `count` function somewhere :-) + 'total' => $this->count($terms) + )); + } + } + + +Structure of array `$term` +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The array term is parsed automatically by the `main.chill.search_provider` service. + +.. note:: + If you need to parse a search pattern, you may use the function `parse($pattern)` provided by the service. + +The array `$term` have the following structure after parsing : + +.. code-block:: php + + array( + '_domain' => 'person', //the domain, without the '@' + 'argument1' => 'value', //the argument1, with his value + 'argument2' => 'my value with spaces', //the argument2 + '_default' => 'abcde ef' // the default term + ); + +The original search would have been : `@person argument1:value argument2:(my value with spaces) abcde ef` + +.. warning:: + The search values are always unaccented. + +Register the service +-------------------- + +You should add your service in the configuration, and add a `chill.search` tag and an alias. + +Example : + +.. code-block:: yaml + + services: + chill.person.search_person: + class: Chill\PersonBundle\Search\PersonSearch + #your logic here + tags: + - { name: chill.search, alias: 'person_regular' } + +The alias will be used to get the results narrowed to this search module, in case of pagination (see above). + +Parsing date +============ + +The class `Chill\MainBundle\Search\AbstractSearch` provides a method to parse date : + +.. code-block:: php + + //from subclasses + $date = $this->parseDate($string); + +`$date` will be an instance of `DateTime `_. + +.. seealso:: + + `The possibility to add periods instead of date `_ + Which may be a future improvement for search with date. + +Exceptions +========== + +The logic of the search is handled by the controller for the `/search` path. + +You should throw those Exception from your instance of `SearchInterface` if needed : + +Chill\MainBundle\Search\ParsingException + If the terms does not fit your search logic (for instance, conflicting terms) + +Expected behaviour +================== + +Operators between multiple terms +-------------------------------- + +Multiple terms should be considered are "AND" instructions : + +@person nationality:RU firstname:dep + the people having the Russian nationality AND having DEP in their name + +@person birthdate:2015-12-12 charles + the people having 'charles' in their name or firstname AND born on December 12 2015 + +Spaces in default +----------------- + +Spaces in default terms should be considered as "AND" instruction + +@person charle dep + people having "dep" AND "charles" in their firstname or lastname. Match "Charles Depardieu" but not "Gérard Depardieu" ('charle' is not present) + +Rendering +--------- + +The rendering should contains : + +- the total number of results ; +- the search pattern in the search language. The aim of this is to let users learn the search language easily. +- a title + +Frequently Asked Questions (FAQ) +================================ + +Why renderResults returns an HTML string and not structured array ? + It seems that the form of results may vary (according to access-right logic, ...) and is not easily structurable + + + + + + From f2ea3edf293b8f504675773c6230effef0ce7979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 15 Jan 2015 15:45:30 +0100 Subject: [PATCH 052/157] add a table of content for person bundle page and fixe some errors in rst syntax --- source/bundles/person.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/source/bundles/person.rst b/source/bundles/person.rst index e4208d01d..091b00758 100644 --- a/source/bundles/person.rst +++ b/source/bundles/person.rst @@ -7,9 +7,12 @@ Free Documentation License". Person bundle -########### +############# -This bundle provides the ability to record people in the software. This bundle is required by other bundle +This bundle provides the ability to record people in the software. This bundle is required by other bundle. + +.. contents:: Table of content + :local: Entities provided ***************** @@ -35,7 +38,7 @@ The search upon "person" is provided by default. The `@person` domain search may Arguments ========= -* `firstname` : provide the search on firstname. Example : `firstname:Depardieu`. May match part of the firstname (`firsname:dep` will match **Dep**ardieu) +* `firstname` : provide the search on firstname. Example : `firstname:Depardieu`. May match part of the firstname (`firsname:dep` will match Depardieu) * `lastname` : provide the search on lastname. May match part of the lastname. * `birthdate` : provide the search on the birthdate. Example : `birthdate:1996-01-19` * `gender`: performs search on man/woman. The accepted values are `man` or `woman`. @@ -44,4 +47,4 @@ Arguments Default ======= -The default search is performed on firstname and/or lastname. Both are concatened before search. If values are separated by spaces, the clause `AND` is used : the search `dep ge` will match '**Gé**rard **Dep**ardieu` or 'Jean **Dep**a**ge**lles', but not 'Charline **Dep**ardieu' (missing 'Ge' in word). +The default search is performed on firstname and/or lastname. Both are concatened before search. If values are separated by spaces, the clause `AND` is used : the search `dep ge` will match 'Gérard Depardieu` or 'Jean Depagelles', but not 'Charline Depardieu' (missing 'Ge' in word). From d1140ba6d3ddcdf110e2110e83f035ac9a4a8f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 15 Jan 2015 15:53:08 +0100 Subject: [PATCH 053/157] documentation about search in reports --- source/bundles/index.rst | 1 + source/bundles/report.rst | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 source/bundles/report.rst diff --git a/source/bundles/index.rst b/source/bundles/index.rst index 9b7921acc..2d05286c7 100644 --- a/source/bundles/index.rst +++ b/source/bundles/index.rst @@ -17,6 +17,7 @@ You will find here documentation about bundles working with Chill. Main bundle Custom Fields bundle Person bundle + Report bundle Your bundle here ? ------------------- diff --git a/source/bundles/report.rst b/source/bundles/report.rst new file mode 100644 index 000000000..524606353 --- /dev/null +++ b/source/bundles/report.rst @@ -0,0 +1,44 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Report bundle +############# + +This bundle provides the ability to record report about people. We use custom fields to let user add fields to reports. + +.. contents:: Table of content + :local: + +.. todo:: + + The documentation about report is not writtend + +Concepts +======== + + +Search +====== + +Domain +------ + +* `@report` is the domain search for reports. + + +Arguments +--------- + +* `date` : The date of the report + +Default +------- + +The report's date is the default value. + +An error is thrown if an argument `date` and a default is used. From ab36443943ccba69420b99883c79edda5b826a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 22 Jan 2015 17:35:37 +0100 Subject: [PATCH 054/157] documentation for routing refs #273 --- source/development/index.rst | 1 + source/development/routing.rst | 68 ++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 source/development/routing.rst diff --git a/source/development/index.rst b/source/development/index.rst index eb5f2f7bf..68b5d62f2 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -16,6 +16,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Install Chill for development Instructions to create a new bundle + Routing Menus Message to users Database migrations diff --git a/source/development/routing.rst b/source/development/routing.rst new file mode 100644 index 000000000..519490918 --- /dev/null +++ b/source/development/routing.rst @@ -0,0 +1,68 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + + +Routing +####### + +Our goal is to ease the installation of the different bundle. Users should not have to dive into complicated config files to install bundles. + +A routing loader available for all bundles +=========================================== + +A Chill bundle may rely on the Routing Loader defined in ChillMain. + +The loader will load `yml` or `xml` files. You simply have to add them into `chill_main` config + +.. code-block:: yaml + + chill_main: + # ... other stuff here + routing: + resources: + - @ChillMyBundle/Resources/config/routing.yml + +Load routes automatically +------------------------- + +But this force users to modify config files. To avoid this, you may prepend config implementing the `PrependExtensionInterface` in the `YourBundleExtension` class. This is an example from **chill main** bundle : + + +.. code-block:: php + + namespace Chill\MainBundle\DependencyInjection; + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\HttpKernel\DependencyInjection\Extension; + use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; + + class ChillMainExtension extends Extension implements PrependExtensionInterface + { + + public function load(array $configs, ContainerBuilder $container) + { + // ... + } + + public function prepend(ContainerBuilder $container) + { + + //add current route to chill main + //this is where the resource is added automatically in the config + $container->prependExtensionConfig('chill_main', array( + 'routing' => array( + 'resources' => array( + '@ChillMainBundle/Resources/config/routing.yml' + ) + + ) + )); + } + } + + From 458a2e71a5aed948c72c13aa0d4d25567890ff3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 23 Jan 2015 15:01:47 +0100 Subject: [PATCH 055/157] add doc for date localisation Introduce the twig Intl extension and make ref to the doc. refs #272 --- source/development/index.rst | 1 + source/development/localisation.rst | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 source/development/localisation.rst diff --git a/source/development/index.rst b/source/development/index.rst index 68b5d62f2..a5b832b36 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -19,6 +19,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Routing Menus Message to users + Localisation Database migrations Searching Testing diff --git a/source/development/localisation.rst b/source/development/localisation.rst new file mode 100644 index 000000000..04cb54b89 --- /dev/null +++ b/source/development/localisation.rst @@ -0,0 +1,29 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Localisation +************* + +Date and time +============== + +The `Intl extension `_ is enabled on the Chill application. + +You may format date and time using the `localizeddate` function : + +.. code-block:: jinja + + date|localizeddate('long', 'none') + +By default, we prefer using the `long` format for date formatting. + +.. seealso:: + + `Documentation for Intl Extension `_ + Read the complete doc for the Intl extension. + From 74bf42e350935afa7e3d50cc49ec7941cca7ac6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 23 Jan 2015 15:19:14 +0100 Subject: [PATCH 056/157] documentation for locale in url close #288 --- source/development/localisation.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/source/development/localisation.rst b/source/development/localisation.rst index 04cb54b89..fca5ad8ba 100644 --- a/source/development/localisation.rst +++ b/source/development/localisation.rst @@ -9,6 +9,26 @@ Localisation ************* +Language in url +=============== + +Language should be present in URL, conventionnaly as first argument. + +.. code-block:: none + + /fr/your/url/here + +This allow users to change from one language to another one on each page, which may be useful in multilanguages teams. If the installation is single-language, the language switcher will not appears. + +This is an example of routing defined in yaml : + +.. code-block:: yaml + + chill_person_general_edit: + pattern: /{_locale}/person/{person_id}/general/edit + defaults: {_controller: ChillPersonBundle:Person:edit } + + Date and time ============== From 317acfa1a5f82ac7b02a279a88862ce613a23fba Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Tue, 27 Jan 2015 16:44:38 +0100 Subject: [PATCH 057/157] Revisioning --- source/development/make-test-working.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/development/make-test-working.rst b/source/development/make-test-working.rst index 7b1919c4b..135fe05df 100644 --- a/source/development/make-test-working.rst +++ b/source/development/make-test-working.rst @@ -182,7 +182,7 @@ This file boostrap the app. It contains three functions. This is the file used i config_test.yml --------------- -There are only few parameters required for the config file. This is the config file for ChillMain : +There are only few parameters required for the config file. This is a basic version for ChillMain : .. code-block:: yaml @@ -228,4 +228,4 @@ You should add there all routing information needed for your bundle. chill_main_bundle: resource: "@CLChillMainBundle/Resources/config/routing.yml" -That's it. Tests should not pass. +That's it. Tests should pass. From 39f186b4a673a311d63cbfd5fbd9669eed9fa48f Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Tue, 27 Jan 2015 17:01:08 +0100 Subject: [PATCH 058/157] More details about sphinx installation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 60b134289..f7879ba64 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Compilation into HTML To compile this documentation : -1. Install [sphinx-doc](http://sphinx-doc.org) +1. Install [sphinx-doc](http://sphinx-doc.org) (eg. pip install sphinx & pip install sphinx_rtd_theme) 2. run `make html` from the root directory 3. The base file is located on build/html/index.html From 319626bf7d7d825ca520504f25e0db4dfe9bfd42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 10 Mar 2015 22:39:14 +0100 Subject: [PATCH 059/157] documentation for custom fields bundle close #265 --- source/bundles/custom-fields.rst | 251 +++++++++++++++++++++++++------ 1 file changed, 202 insertions(+), 49 deletions(-) diff --git a/source/bundles/custom-fields.rst b/source/bundles/custom-fields.rst index 2cff00294..9d3ad86b3 100644 --- a/source/bundles/custom-fields.rst +++ b/source/bundles/custom-fields.rst @@ -24,14 +24,27 @@ In the database, custom fields are stored in json format. Custom Fields concepts ---------------------- -.. warning:: +Custom fields are extra data which may be added to entities by user. If a developer implements custom fields on a entity, users will be able to add more fields on this entity. - This section is incomplete +Example: the `person bundle` allow to record `firstname`, `lastname`, `date of birth` fields. But users need to store information about the kind of house he has (if he owns his house, if he rents it, ...). Custom fields allow to create those fields. + +Automatically, those fields are added at the person form. They are also printed in the person view and in exports. + +Custom fields and custom fields group +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Custom fields are associated to a custom fields group. When a user want to add custom fields on an entity, he must first create a custom fields group and associate this field with an entity. Then he may add add custom fields to this groups. + +Some entities needs a **default** custom fields group. For instance, the default custom fields group will be printed on the main form for person, and will be appended on the main person view. Some bundle does not use this feature (i.e. the `report` bundle). + +.. note:: + + In the future of the `person bundle`, other custom fields group will be added in forms accessible from the menu, allowing users to completely customize and separate their entities. Allow custom fields on a entity -------------------------------- -As a developer, you may allow your users to add custom fields on your entities. +As a developer, you must allow your users to add custom fields on your entities. Create a json field on your entity ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -47,7 +60,7 @@ Declare a json field in your database : customField: type: json_array -Create the field accordingly : +Create the field accordingly in the class logic : .. code-block:: php @@ -65,8 +78,8 @@ Create the field accordingly : private $customField = array(); /** - * This method will be required for the Form component to record the - * custom fields into the class + * You must set a setter in order to save automatically custom + * fields from forms, using Form Component * * @param array $customField * @return BlopEntity @@ -78,7 +91,8 @@ Create the field accordingly : } /** - * Required by Forms to retrieve informations + * You also must create a getter in order to let Form + * component populate form fields * * @return array */ @@ -87,13 +101,15 @@ Create the field accordingly : return $this->customField; } -Declare your customizable class in configuration -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Declare your customizable entity in configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This step is necessary to allow user to create custom fields group associated with this entity. Two methods are available : * In your app/config.yml file. This is the easiest method, but discouraged because it will reduce the ease for installation. -* In your Extension class : harder for devs, easier for installers. +* In your Extension class : a bit harder for devs, much easier for installers. In app/config.yml file """""""""""""""""""""" @@ -109,12 +125,141 @@ Add those file under `chill_custom_fields` section : * The `name` allow you to define a string which is translatable. This string will appears when chill's admin will add/retrieve new customFieldsGroup. * The class, which is a full FQDN class path -Automatically, in DepedencyInjection/Extension class -"""""""""""""""""""""""""""""""""""""""""""""""""""" +Automatically, in DependencyInjection/Extension class +"""""""""""""""""""""""""""""""""""""""""""""""""""""" -.. todo:: +This is the preferred way for declaring customizable classes. - Explain how to declare customizable entitites in DepedencyInjection/Extension. +You can prepend configuration of `custom fields bundle` from the class `YourBundle\DependencyInjection\YourBundleExtension`. **Note** that you also have to implements `Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface` on this class to make the `prepend` function being taken into account. + +Example here : + +.. code-block:: php + + class ChillReportExtension extends Extension implements PrependExtensionInterface + { + /** + * + * + * @param ContainerBuilder $container + */ + public function prepend(ContainerBuilder $container) + { + $bundles = $container->getParameter('kernel.bundles'); + if (!isset($bundles['ChillCustomFieldsBundle'])) { + throw new MissingBundleException('ChillCustomFieldsBundle'); + } + + $container->prependExtensionConfig('chill_custom_fields', + array('customizables_entities' => + array( + array( + 'class' => 'Chill\ReportBundle\Entity\Report', + 'name' => 'ReportEntity', + ) + ) + ) + ); + } + } + +.. seealso:: + + `How to simplify configuration of multiple bundles `_ + A cookbook page about prepending configuration. + +Adding options to your custom fields groups +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You may add options to the groups associated with an entity. + +In `config.yml` the declaration should be : + +.. code-block:: yaml + + chill_custom_fields: + customizables_entities: + - + class: Chill\ReportBundle\Entity\Report + name: ReportEntity + options: + # this will create a "myFieldKey" field as text, with a maxlength attribute to 150 (see http://symfony.com/doc/master/reference/forms/types/text.html) + myFieldKey: {form_type: text, form_options: {attr: [maxlength: 150]}} + +In the `PrependExtensionInterface::prepend` function, the options key will be added in the configuration definition : + +.. code-block:: php + + class ChillReportExtension extends Extension implements PrependExtensionInterface + { + /** + * + * + * @param ContainerBuilder $container + */ + public function prepend(ContainerBuilder $container) + { + $bundles = $container->getParameter('kernel.bundles'); + if (!isset($bundles['ChillCustomFieldsBundle'])) { + throw new MissingBundleException('ChillCustomFieldsBundle'); + } + + $container->prependExtensionConfig('chill_custom_fields', + array('customizables_entities' => + array( + array( + 'class' => 'Chill\ReportBundle\Entity\Report', + 'name' => 'ReportEntity', + 'options' => array( + 'myFieldKey' => [ 'form_type' => 'text', 'form_options' => [ 'attr' => [ 'maxlength' => 150 ] ] + )) + ) + ) + ); + } + } + +**Example :** the entity `Report` from **ReportBundle** has to pick some custom fields belonging to a group to print them in *summaries* the timeline page. The definition will use the special type `custom_fields_group_linked_custom_field` which will add a select input with all fields associated with the current custom fields group : + +.. code-block:: php + + class ChillReportExtension extends Extension implements PrependExtensionInterface + { + /** + * + * + * @param ContainerBuilder $container + */ + public function prepend(ContainerBuilder $container) + { + $bundles = $container->getParameter('kernel.bundles'); + if (!isset($bundles['ChillCustomFieldsBundle'])) { + throw new MissingBundleException('ChillCustomFieldsBundle'); + } + + $container->prependExtensionConfig('chill_custom_fields', + array('customizables_entities' => + array( + array( + 'class' => 'Chill\ReportBundle\Entity\Report', + 'name' => 'ReportEntity', + 'options' => array( + 'summary_fields' => array( + 'form_type' => 'custom_fields_group_linked_custom_fields', + 'form_options' => + [ + 'multiple' => true, + 'expanded' => false + ] + ) + )) + ) + ) + ); + } + } + +Note that `custom_fields_group_linked_custom_fields` does not create any input on `CustomFieldsGroup` creation : there aren't any fields associated with the custom fields just after the group creation... You have to add custom fields and associate them with the newly created group to see them appears. Rendering custom fields in a template -------------------------------------- @@ -167,50 +312,65 @@ Examples: Custom Fields's form --------------------- -You should simply use the 'custom_field' type in a template, with the group you would like to render in the `group` option +You should simply use the 'custom_field' type in a template, with the group you would like to render in the `group` option's type. Example : -.. warning:: - - The above code isn't tested. - -.. todo:: - - Test the above code. - .. code-block:: php + namespace Chill\ReportBundle\Form; + use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; - use Chill\CustomFieldsBundle\Form\Type\CustomFieldType; - use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup; - class BlopEntityType extends AbstractType + class ReportType extends AbstractType { - - public $group; - - public function __construct(CustomFieldsGroup $group) - { - $this->group = $group; - } - - /** * @param FormBuilderInterface $builder * @param array $options */ - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder - ->add('field1') - ->add('field2') - //->add('adress', new AdressType()) - ->add('customField', 'custom_field', array('group' => $group)) + public function buildForm(FormBuilderInterface $builder, array $options) + { + $entityManager = $options['em']; + + $builder + ->add('user') + ->add('date', 'date', + array('required' => true, 'widget' => 'single_text', 'format' => 'dd-MM-yyyy')) + #add the custom fields : + ->add('cFData', 'custom_field', + array('attr' => array('class' => 'cf-fields'), 'group' => $options['cFGroup'])) ; } + + /** + * @param OptionsResolverInterface $resolver + */ + public function setDefaultOptions(OptionsResolverInterface $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'Chill\ReportBundle\Entity\Report' + )); + + $resolver->setRequired(array( + 'em', + 'cFGroup', + )); + + $resolver->setAllowedTypes(array( + 'em' => 'Doctrine\Common\Persistence\ObjectManager', + 'cFGroup' => 'Chill\CustomFieldsBundle\Entity\CustomFieldsGroup' + )); + } + + /** + * @return string + */ + public function getName() + { + return 'chill_reportbundle_report'; + } } @@ -229,11 +389,4 @@ If you want to test the rendering of a custom fields group, you may use this met 2. assuming that your custom fields id is `1`, go to your navigator and enter url : `http://localhost:8000/customfieldsgroup/test/render/1` - - - -.. glossary:: - - customFieldsGroup - TODO From 489d1a5819e2b38c4f9d63aabe0b7fb02ee8b9d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 26 Mar 2015 20:32:56 +0100 Subject: [PATCH 060/157] documentation for timelines refs #224 Explain how to publish information into a timeline, and how to create a timeline with a new context --- source/development/index.rst | 1 + source/development/timelines.rst | 375 +++++++++++++++++++++++++++++++ 2 files changed, 376 insertions(+) create mode 100644 source/development/timelines.rst diff --git a/source/development/index.rst b/source/development/index.rst index a5b832b36..a071c6090 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -22,6 +22,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Localisation Database migrations Searching + Timelines Testing manual/index.rst diff --git a/source/development/timelines.rst b/source/development/timelines.rst new file mode 100644 index 000000000..ca1d2e317 --- /dev/null +++ b/source/development/timelines.rst @@ -0,0 +1,375 @@ +.. Copyright (C) 2015 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Timelines +********* + +.. contents:: Table of content + :local: + +Concept +======= + +From an user point of view +-------------------------- + +Chill has two objectives : + +* make the administrative tasks more lightweight ; +* help social workers to have all information they need to work + +To reach this second objective, Chill provides a special view: **timeline**. On a timeline view, information is gathered and shown on a single page, from the most recent event to the oldest one. + +The information gathered is linked to a *context*. This *context* may be, for instance : + +* a person : events linked to this person are shown on the page ; +* a center: events linked to a center are shown. They may concern different peoples ; +* ... + +In other word, the *context* is the kind of argument that will be used in the event's query. + +Let us recall that only the data the user has allowed to see should be shown. + +.. seealso:: + + `The issue where the subject was first discussed `_ + + +For developers +-------------- + +The `Main` bundle provides interfaces and services to help to build timelines. + +If a bundle wants to *push* information in a timeline, it should be create a service which implements `Chill\MainBundle\Timeline\TimelineProviderInterface`, and tag is with `chill.timeline` and arguments defining the supported context (you may use multiple `chill.timeline` tags in order to support multiple context with a single service/class). + +If a bundle wants to provide a new context for a timeline, the service `chill.main.timeline_builder` will helps to gather timeline's services supporting the defined context, and run queries across the models. + +.. _understanding-queries : + +Understanding queries +^^^^^^^^^^^^^^^^^^^^^ + +Due to the fact that timelines should show only the X last events from Y differents tables, queries for a timeline may consume a lot of resources: at first on the database, and then on the ORM part, which will have to deserialize DB data to PHP classes, which may not be used if they are not part of the "last X events". + +To avoid such load on database, the objects are queried in two steps : + +1. An UNION request which gather the last X events, ordered by date. The data retrieved are the ID, the date, and a string key: a type. This type discriminates the data type. +2. The PHP objects are queried by ID, the type helps the program to link id with the kind of objects. + +Those methods should ensure that only X PHP objects will be gathered and build by the ORM. + +What does the master timeline builder service ? +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + When the service `chill.main.timeline_builder` is instanciated, the service is informed of each service taggued with `chill.timeline` tags. Then, + +1. The service build an UNION query by assembling column and tables names provided by the `fetchQuery` result ; +2. The UNION query is run, the result contains an id and a type for each row (see :ref:`above `) +3. The master service gather all id with the same type. Then he searches for the `chill.timeline`'s service which will be able to get the entities. Then, the entities will be fetched using the `fetchEntities` function. All entities are gathered in one query ; +4. The information to render entities in HTML is gathered by passing entity, one by one, on `getEntityTemplate` function. + +Pushing events to a timeline +============================= + +To push events on a timeline : + +1. Create a class which implements `Chill\MainBundle\Timeline\TimelineProviderInterface` ; +2. Define the class as a service, and tag the service with `chill.timeline`, and define the context associated with this timeline (you may add multiple tags for different contexts). + +Implementing the TimelineProviderInterface +------------------------------------------ + +The has the following signature : + +.. code-block:: php + + namespace Chill\MainBundle\Timeline; + + interface TimelineProviderInterface + { + + /** + * + * @param string $context + * @param mixed[] $args the argument to the context. + * @return string[] + * @throw \LogicException if the context is not supported + */ + public function fetchQuery($context, array $args); + + /** + * Indicate if the result type may be handled by the service + * + * @param string $type the key present in the SELECT query + * @return boolean + */ + public function supportsType($type); + + /** + * fetch entities from db into an associative array. The keys **MUST BE** + * the id + * + * All ids returned by all SELECT queries + * (@see TimeLineProviderInterface::fetchQuery) and with the type + * supported by the provider (@see TimelineProviderInterface::supportsType) + * will be passed as argument. + * + * @param array $ids an array of id + * @return mixed[] an associative array of entities, with id as key + */ + public function getEntities(array $ids); + + /** + * return an associative array with argument to render the entity + * in an html template, which will be included in the timeline page + * + * The result must have the following key : + * + * - `template` : the template FQDN + * - `template_data`: the data required by the template + * + * + * Example: + * + * ``` + * array( + * 'template' => 'ChillMyBundle:timeline:template.html.twig', + * 'template_data' => array( + * 'accompanyingPeriod' => $entity, + * 'person' => $args['person'] + * ) + * ); + * ``` + * + * `$context` and `$args` are defined by the bundle which will call the timeline + * rendering. + * + * @param type $entity + * @param type $context + * @param array $args + * @return mixed[] + * @throws \LogicException if the context is not supported + */ + public function getEntityTemplate($entity, $context, array $args); + + } + + +The `fetchQuery` function +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The fetchQuery function help to build the UNION query to gather events. This function should return an associative array MUST have the following key : +* `id` : the name of the id column +* `type`: a string to indicate the type +* `date`: the name of the datetime column, used to order entities by date +* `FROM` (in capital) : the FROM clause. May contains JOIN instructions + +Those key are optional: +* `WHERE` (in capital) : the WHERE clause. + + Where relevant, the data must be quoted to avoid SQL injection. + +`$context` and `$args` are defined by the bundle which will call the timeline rendering. You may use them to build a different query depending on this context. + +For instance, if the context is `'person'`, the args will be this array : + +.. code-block:: php + + array( + 'person' => $person //a \Chill\PersonBundle\Entity\Person entity + ); + +You should find in the bundle documentation which contexts are arguments the bundle defines. + +.. note:: + + We encourage to use `ClassMetaData` to define column names arguments. If you change your column names, changes will be reflected automatically during the execution of your code. + +Example of an implementation : + +.. code-block:: php + + namespace Chill\ReportBundle\Timeline; + + use Chill\MainBundle\Timeline\TimelineProviderInterface; + use Doctrine\ORM\EntityManager; + + /** + * Provide report for inclusion in timeline + * + * @author Julien Fastré + * @author Champs Libres + */ + class TimelineReportProvider implements TimelineProviderInterface + { + + /** + * + * @var EntityManager + */ + protected $em; + + public function __construct(EntityManager $em) + { + $this->em = $em; + } + + public function fetchQuery($context, array $args) + { + $this->checkContext($context); + + $metadata = $this->em->getClassMetadata('ChillReportBundle:Report'); + + return array( + 'id' => $metadata->getColumnName('id'), + 'type' => 'report', + 'date' => $metadata->getColumnName('date'), + 'FROM' => $metadata->getTableName(), + 'WHERE' => sprintf('%s = %d', + $metadata + ->getAssociationMapping('person')['joinColumns'][0]['name'], + $args['person']->getId()) + ); + } + + //.... + + + } + +The `supportsType` function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This function indicate to the master `chill.main.timeline_builder` service (which orchestrate the build of UNION queries) that the service supports the type indicated in the result's array of the `fetchQuery` function. + +The implementation of our previous example will be : + +.. code-block:: php + + + namespace Chill\ReportBundle\Timeline; + + use Chill\MainBundle\Timeline\TimelineProviderInterface; + use Doctrine\ORM\EntityManager; + + class TimelineReportProvider implements TimelineProviderInterface + { + + //... + + /** + * + * {@inheritDoc} + */ + public function supportsType($type) + { + return $type === 'report'; + } + + //... + } + +The `getEntities` function +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This is where the service must fetch entities from database and return them to the master service. + +The results **must be** an array where the id given by the UNION query (remember `fetchQuery`). + +.. code-block:: php + + namespace Chill\ReportBundle\Timeline; + + use Chill\MainBundle\Timeline\TimelineProviderInterface; + use Doctrine\ORM\EntityManager; + + class TimelineReportProvider implements TimelineProviderInterface + { + + public function getEntities(array $ids) + { + $reports = $this->em->getRepository('ChillReportBundle:Report') + ->findBy(array('id' => $ids)); + + $result = array(); + foreach($reports as $report) { + $result[$report->getId()] = $report; + } + + return $result; + } + + } + +The `getEntityTemplate` function +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This is where the master service will collect information to render the entity. + +The result must be an associative array with : + +- **template** is the FQDN of the template ; +- **template_data** is an associative array where keys are the variables'names for this template, and values are the values. + +Example : + +.. code-block:: php + + array( + 'template' => 'ChillMyBundle:timeline:template.html.twig', + 'template_data' => array( + 'period' => $entity, + 'person' => $args['person'] + ) + ); + +The template must, obviously, exists. Example : + +.. code-block:: jinja + +

     {{ 'An accompanying period is opened for %person% on %date%'|trans({'%person%': person, '%date%': period.dateOpening|localizeddate('long', 'none') } ) }}

    + + +Create a timeline with his own context +====================================== + +You have to create a Controller which will execute the service `chill.main.timeline_builder`. Using the `Chill\MainBundle\Timeline\TimelineBuilder::getTimelineHTML` function, you will get an HTML representation of the timeline, which you may include with twig `raw` filter. + +Example : + +.. code-block:: php + + namespace Chill\PersonBundle\Controller; + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + + class TimelinePersonController extends Controller + { + + public function personAction(Request $request, $person_id) + { + $person = $this->getDoctrine() + ->getRepository('ChillPersonBundle:Person') + ->find($person_id); + + if ($person === NULL) { + throw $this->createNotFoundException(); + } + + return $this->render('ChillPersonBundle:Timeline:index.html.twig', array + ( + 'timeline' => $this->get('chill.main.timeline_builder') + ->getTimelineHTML('person', array('person' => $person)), + 'person' => $person + ) + ); + } + + } From 951b65b511075a5d0743781670725dadba8c8041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 27 Mar 2015 19:42:16 +0100 Subject: [PATCH 061/157] add extensions for php rendering --- .gitmodules | 3 +++ _exts/sphinx-php | 1 + source/conf.py | 31 ++++++++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 160000 _exts/sphinx-php diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..7bc519c88 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "_exts/sphinx-php"] + path = _exts/sphinx-php + url = https://github.com/fabpot/sphinx-php.git diff --git a/_exts/sphinx-php b/_exts/sphinx-php new file mode 160000 index 000000000..52f7bd221 --- /dev/null +++ b/_exts/sphinx-php @@ -0,0 +1 @@ +Subproject commit 52f7bd2216cc22ef52494f346c5643bb2a74513f diff --git a/source/conf.py b/source/conf.py index 2bee69683..97de731ea 100644 --- a/source/conf.py +++ b/source/conf.py @@ -21,6 +21,14 @@ import os # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) +sys.path.append(os.path.abspath('./../_exts/sphinx-php')) + + +# adding PhpLexer +from sphinx.highlighting import lexers +from pygments.lexers.compiled import CLexer +from pygments.lexers.web import PhpLexer + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -31,7 +39,11 @@ import os # ones. extensions = [ 'sphinx.ext.autodoc', - 'sphinx.ext.todo', + 'sphinx.ext.todo', + 'sensio.sphinx.refinclude', + 'sensio.sphinx.configurationblock', + 'sensio.sphinx.phpcode', + 'sensio.sphinx.bestpractice', ] # Add any paths that contain templates here, relative to this directory. @@ -97,6 +109,23 @@ pygments_style = 'sphinx' # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False +# -- Settings for symfony doc extension --------------------------------------------------- +# enable highlighting for PHP code not between ```` by default +lexers['php'] = PhpLexer(startinline=True) +lexers['php-annotations'] = PhpLexer(startinline=True) +lexers['php-standalone'] = PhpLexer(startinline=True) +lexers['php-symfony'] = PhpLexer(startinline=True) +lexers['varnish3'] = CLexer() +lexers['varnish4'] = CLexer() +config_block = { +'varnish3': 'Varnish 3', +'varnish4': 'Varnish 4' +} +# use PHP as the primary domain +primary_domain = 'php' +# set url for API links +api_url = 'http://api.symfony.com/master/%s' + # -- Options for HTML output ---------------------------------------------- From ba2335d73e95df69725b34095c44b04f2be140ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 27 Mar 2015 20:05:25 +0100 Subject: [PATCH 062/157] explain how to add extensions --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f7879ba64..cd5f9f39a 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,9 @@ Compilation into HTML To compile this documentation : 1. Install [sphinx-doc](http://sphinx-doc.org) (eg. pip install sphinx & pip install sphinx_rtd_theme) -2. run `make html` from the root directory -3. The base file is located on build/html/index.html +2. Install submodules : $ git submodule update --init; +3. run `make html` from the root directory +4. The base file is located on build/html/index.html Contribute =========== From dad59e3b04c05dbb31ea360283a638c5e9f154f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 1 May 2015 22:59:34 +0200 Subject: [PATCH 063/157] add docker postgresql image in installation part --- source/installation/installation.rst | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 8aafc2131..5ba03109e 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -25,18 +25,38 @@ Requirements Server requirements ^^^^^^^^^^^^^^^^^^^^ -* a postgresql database, with the `*unaccent* extension`_ enabled. The minimum version is postgresql 9.3, but we are working on developing on the 9.4 branch, which will provide features which will ease developper work +* a postgresql database, with the `*unaccent* extension`_ enabled. The minimum version is postgresql 9.4. Alternatively, you can use `the docker image provided `_ (see notes above) * php 5.5 * If you run Chill in production mode, you should also install a web server (apache, ngnix, ...). We may use php built-in server for testing and development. -Within this documentation, we are going to describe installation on Unix systems (Unix, Mac OS). Windows installation ha not been tested yet. +Within this documentation, we are going to describe installation on Unix systems (Unix, Mac OS). Windows installation ha not been tested. -You won't need any web server for demonstration or development. +You won't need any web server for setting up an instance for a demonstration or development. .. note:: Installing unaccent extension on ther server is possible with the package `postgresql-contrib-9.x` (replace 9.x with your server version). The extension may be enabled with running `CREATE EXTENSION unaccent;` in the database, with a superuser account. +.. note:: + + To avoid installation and configuration of a postgresql server, you may use `our docker image `_ to start and configure a database. + + After `docker installation `_, run : + + .. code-block:: bash + + sudo docker run -P --name=chill_db chill/database + + This will download the chill/database image and start a new docker instance with the name `chill_db` and export the postgresql port `5432` on another random local port. + + In a new terminal, run + + .. code-block:: bash + + sudo docker port chill_db 5432 + + This command will show on which port the docker container is listening, on your localhost. During the part :ref:`create-your-project` fill this port, and the address of the container (`localhost` or `127.0.0.1`). + Client requirements ^^^^^^^^^^^^^^^^^^^ @@ -86,6 +106,8 @@ You can test the installation by running `which composer` or `composer`: those c .. note:: See `the composer introduction`_ to learn how to install composer on Mac OS X and Windows +.. _create-your-project: + Create your project ^^^^^^^^^^^^^^^^^^^ From 66c33f7feb7e1e7c086744789c06ceaf9577c914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 1 May 2015 23:10:04 +0200 Subject: [PATCH 064/157] remove php/sension extensions if building on rtd --- source/conf.py | 54 +++++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/source/conf.py b/source/conf.py index 97de731ea..b2e25f73c 100644 --- a/source/conf.py +++ b/source/conf.py @@ -16,18 +16,22 @@ import sys import os +# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) -sys.path.append(os.path.abspath('./../_exts/sphinx-php')) +if not on_rtd: + sys.path.append(os.path.abspath('./../_exts/sphinx-php')) -# adding PhpLexer -from sphinx.highlighting import lexers -from pygments.lexers.compiled import CLexer -from pygments.lexers.web import PhpLexer + # adding PhpLexer + from sphinx.highlighting import lexers + from pygments.lexers.compiled import CLexer + from pygments.lexers.web import PhpLexer # -- General configuration ------------------------------------------------ @@ -40,11 +44,15 @@ from pygments.lexers.web import PhpLexer extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.todo', +] + +if not on_rtd: + extensions += [ 'sensio.sphinx.refinclude', 'sensio.sphinx.configurationblock', 'sensio.sphinx.phpcode', 'sensio.sphinx.bestpractice', -] + ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -110,21 +118,22 @@ pygments_style = 'sphinx' #keep_warnings = False # -- Settings for symfony doc extension --------------------------------------------------- -# enable highlighting for PHP code not between ```` by default -lexers['php'] = PhpLexer(startinline=True) -lexers['php-annotations'] = PhpLexer(startinline=True) -lexers['php-standalone'] = PhpLexer(startinline=True) -lexers['php-symfony'] = PhpLexer(startinline=True) -lexers['varnish3'] = CLexer() -lexers['varnish4'] = CLexer() -config_block = { -'varnish3': 'Varnish 3', -'varnish4': 'Varnish 4' -} -# use PHP as the primary domain -primary_domain = 'php' -# set url for API links -api_url = 'http://api.symfony.com/master/%s' +if not on_rtd: + # enable highlighting for PHP code not between ```` by default + lexers['php'] = PhpLexer(startinline=True) + lexers['php-annotations'] = PhpLexer(startinline=True) + lexers['php-standalone'] = PhpLexer(startinline=True) + lexers['php-symfony'] = PhpLexer(startinline=True) + lexers['varnish3'] = CLexer() + lexers['varnish4'] = CLexer() + config_block = { + 'varnish3': 'Varnish 3', + 'varnish4': 'Varnish 4' + } + # use PHP as the primary domain + primary_domain = 'php' + # set url for API links + api_url = 'http://api.symfony.com/master/%s' # -- Options for HTML output ---------------------------------------------- @@ -360,8 +369,7 @@ epub_exclude_files = ['search.html'] # If false, no index is generated. #epub_use_index = True -# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + #-- Options for todo From 7ae12df918d79611880847c1a2154231cbc42f91 Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Mon, 8 Jun 2015 10:04:30 +0200 Subject: [PATCH 065/157] Adding information about building the .css files --- source/installation/installation.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 5ba03109e..be57b7cb2 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -197,6 +197,21 @@ The main bundle require two scripts to be executed : Those will populate database, respectively, with countries (using ISO declaration) and languages (using, also, ISO informations). + +Building CSS (optionnal) +^^^^^^^^^^^^^^^^^^^^^^^^ + +A build version of the needed CSS file is provided within the main bundle `Resources/public/css/chillmain.css`. For rebuilding it : + + +.. code-block:: bash + #in the main bundle directory ( vendor/chill-project/main/ ) + cd Resources/ + npm install + grunt + + + Launch your server ^^^^^^^^^^^^^^^^^^ From e7ceb805c64550e66473d3490de44f881db00ebd Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Mon, 8 Jun 2015 10:08:21 +0200 Subject: [PATCH 066/157] Adding information about building the .css files - 2 --- source/installation/installation.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index be57b7cb2..dcc4a0924 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -201,10 +201,13 @@ Those will populate database, respectively, with countries (using ISO declaratio Building CSS (optionnal) ^^^^^^^^^^^^^^^^^^^^^^^^ +For this step, npm must be installed. + A build version of the needed CSS file is provided within the main bundle `Resources/public/css/chillmain.css`. For rebuilding it : .. code-block:: bash + #in the main bundle directory ( vendor/chill-project/main/ ) cd Resources/ npm install From 859e115de7df97030d1979bce4240e6ade712dee Mon Sep 17 00:00:00 2001 From: Jean Pierre Huart Date: Fri, 26 Jun 2015 17:28:22 +0200 Subject: [PATCH 067/157] Update installation.rst --- source/installation/installation.rst | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 5ba03109e..24913bc64 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -29,7 +29,7 @@ Server requirements * php 5.5 * If you run Chill in production mode, you should also install a web server (apache, ngnix, ...). We may use php built-in server for testing and development. -Within this documentation, we are going to describe installation on Unix systems (Unix, Mac OS). Windows installation ha not been tested. +Within this documentation, we are going to describe installation on Unix systems (Unix, Mac OS). Windows installation has not been tested. You won't need any web server for setting up an instance for a demonstration or development. @@ -39,7 +39,7 @@ You won't need any web server for setting up an instance for a demonstration or .. note:: - To avoid installation and configuration of a postgresql server, you may use `our docker image `_ to start and configure a database. + To avoid installation and configuration of a postgresql server, you may use `our docker image `_ to start and configure a database. After `docker installation `_, run : @@ -55,7 +55,7 @@ You won't need any web server for setting up an instance for a demonstration or sudo docker port chill_db 5432 - This command will show on which port the docker container is listening, on your localhost. During the part :ref:`create-your-project` fill this port, and the address of the container (`localhost` or `127.0.0.1`). + This command will show on which port the docker container is listening, on your localhost. During the part :ref:`create-your-project` this is the value to be used to fill the field 'database_port'. Client requirements ^^^^^^^^^^^^^^^^^^^ @@ -67,9 +67,18 @@ Preparation You will need those informations : -* The informations to access to your database: host, port, database name, and your credentials (username and password) ; +* The information to access to your database: host, port, database name, and your credentials (username and password) ; * a random string, which will be use to improve entropy in security. Choose anything you want (random character, your father's birthplace, ...). +If you have installed the docker database your information should be: + - database_host: 127.0.0.1 + - database_port: [see above] + - database_name: postgres + - database_user: postgres + - database_password: postgres + - locale: fr + - secret: champs-libres + Installation ------------ @@ -149,7 +158,7 @@ You may accept the default parameters of `debug_toolbar`, `debug_redirects` and .. note:: - At the end of your installation, composer will warn you to execute database migration script, with this message : :: + At the end of the installation, composer will warn you to execute database migration script, with this message : :: Some migration files have been imported. You should run `php app/console doctrine:migrations:status` and/or From 2b0872ab7dc895ddd261a1a60ce12b57b86cc9c1 Mon Sep 17 00:00:00 2001 From: Jean Pierre Huart Date: Fri, 26 Jun 2015 17:30:23 +0200 Subject: [PATCH 068/157] Update installation.rst Details to be deleted --- source/installation/installation.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 24913bc64..4116262ed 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -77,7 +77,6 @@ If you have installed the docker database your information should be: - database_user: postgres - database_password: postgres - locale: fr - - secret: champs-libres Installation ------------ From 4408eea3b318354e9042f63989294ac828f085e8 Mon Sep 17 00:00:00 2001 From: Jean Pierre Huart Date: Fri, 26 Jun 2015 18:13:58 +0200 Subject: [PATCH 069/157] Changes to optional css install --- source/installation/installation.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 53f245fb6..b5d83dc43 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -217,11 +217,16 @@ A build version of the needed CSS file is provided within the main bundle `Resou .. code-block:: bash #in the main bundle directory ( vendor/chill-project/main/ ) - cd Resources/ - npm install - grunt + cd vendor/chill-project/main/Resources/ + npm install grunt +Go back to your project root before doing next step + +.. code-block:: bash + + cd path/to/your/directory + Launch your server ^^^^^^^^^^^^^^^^^^ From 799b612a72823402c15f66f31f1f3a5f1356baf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sun, 28 Jun 2015 01:14:31 +0200 Subject: [PATCH 070/157] add access control model documentation --- .../_static_sources/access_control_model.dia | Bin 0 -> 2643 bytes source/conf.py | 4 + source/development/access_control_model.rst | 246 ++++++++++++++++++ source/development/index.rst | 1 + source/static/access_control_model.png | Bin 0 -> 17645 bytes 5 files changed, 251 insertions(+) create mode 100644 source/_static_sources/access_control_model.dia create mode 100644 source/development/access_control_model.rst create mode 100644 source/static/access_control_model.png diff --git a/source/_static_sources/access_control_model.dia b/source/_static_sources/access_control_model.dia new file mode 100644 index 0000000000000000000000000000000000000000..4f9ab7b8bcc6c112b76cfcc3660c96cba84eaa38 GIT binary patch literal 2643 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 literal 0 HcmV?d00001 diff --git a/source/conf.py b/source/conf.py index b2e25f73c..d527d1039 100644 --- a/source/conf.py +++ b/source/conf.py @@ -54,6 +54,10 @@ if not on_rtd: 'sensio.sphinx.bestpractice', ] +#add configuration for api url + +api_url = 'http://api.chill.social/master/%s' + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/source/development/access_control_model.rst b/source/development/access_control_model.rst new file mode 100644 index 000000000..8d5d8ec8b --- /dev/null +++ b/source/development/access_control_model.rst @@ -0,0 +1,246 @@ +.. Copyright (C) 2015 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Access controle model +********************** + +.. contents:: Table of content + :local: + +Concepts +======== + +Every time an entity is created, viewed or updated, the software check if the user has the permission to make this action. The decision is made with three parameters : + +- the type of entity ; +- the entity's center ; +- the entity'scope + +The user must be granted access to the action on this particular entity, with this scope and center. + +From an user point of view +-------------------------- + +The software is design to allow fine tuned access rights for complicated installation and team structure. The administrators may also decide that every user has the right to see all resources, where team have a more simple structure. + +Here is an overview of the model. + +Chill can be multi-center +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Chill is designed to be installed once for social center who work with multiple teams separated, or for social services's federation who would like to share the same installation of the software for all their members. + +This was required for cost reduction, but also to ease the generation of statistics agregated across federation's members, or from the central unit of the social center with multiple teams. + +Otherwise, it is not required to create multiple center: Chill can also work for one center. + +Obviously, users working in the different centers are not allowed to see the entities (_persons_, _reports_, _activities_) of other centers. But users may be attached to multiple centers: consequently they will be able to see the entities of the multiple centers they are attached to. + +Inside center, scope divide team +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Users are attached to one or more center and, inside to those center, there may exists differents scopes. The aim of those _scopes_ is to divide the whole team of social worker amongst different departement, for instance: the social team, the psychologist team, the nurse team, the administrative team, ... Each team is granted of different rights amongst scope. For instance, the social team may not see the _activities_ of the psychologist team. The administrative team may see the date & time's activities, but is not allowed to see the detail of those entities (the personal notes, ...). + +The administrator is responsible of creating those scopes and team. *He may also decide to not define a division inside his team*: he creates only one scope and all entities will belong to this scope, all users will be able to see all entities. + +As entities have only one scopes, if some entities must be shared across two different teams, the administrator will have to create a scope *shared* by two different team, and add the ability to create, view, or update this scope to those team. + +Example: if some activities must be seen and updated between nurses and psychologists, the administrator will create a scope "nurse and psy" and add the ability for both team "nurse" and "psychologist" to "create", "see", and "update" the activities belonging to scope "nurse and psy". + +The concepts translated into code +----------------------------------- + +.. figure:: /static/access_control_model.png + + Schema of the access control model + +Chill handle **entities**, like *persons*, *reports* (associated to *persons*), *activities* (also associated to *_persons*), ... On creation, those entities are linked to one center and, eventually, to one scope. They implements the interface `HasCenterInterface`. + +.. note:: + + Somes entities are linked to a center through the entity they are associated with. For instance, *activities* or *reports* are associated to a *person*, and the person is associated to a *center*. The *report*'s *center* is always the *person*'s *center*. + +Entities may be associated with a scope. In this case, they implement the `HasScopeInterface`. + +.. note:: + + Currently, only the *person* entity is not associated with a scope. + +At each step of his lifetime (creation, view of the entity and eventually of his details, update and, eventually, deletion), the right of the user are checked. To decide wether the user is granted right to execute the action, the software must decide with those elements : + +- the entity's type; +- the entity's center ; +- the entity's scope, if it exists, +- and, obviously, a string representing the action + +All those action are executed through symfony voters and helpers. + +How to check authorization ? +---------------------------- + +Just use the symfony way-of-doing, but do not forget to associate the entity you want to check access. For instance, in controller : + +.. code-block:: php + + class MyController extends Controller + { + + public function viewAction($entity) + { + $this->denyAccessUnlessGranted('CHILL_ENTITY_SEE', $entity); + + //... go on with this action + } + } + +And in template : + +.. code-block:: html+jinja + + {{ if is_granted('CHILL_ENTITY_SEE', entity) %}print something{% endif %} + +Retrieving reachable scopes and centers +---------------------------------------- + +The class :class:`Chill\\MainBundle\\Security\\Authorization\\AuthorizationHelper` helps you to get centers and scope reachable by a user. + +Those methods are intentionnaly build to give information about user rights: + +- getReachableCenters: to get reachable centers for a user +- getReachableScopes : to get reachable scopes for a user + +.. note:: + + The service is reachable through the Depedency injection with the key `chill.main.security.authorization.helper`. Example : + + .. code-block:: php + + $helper = $container->get('chill.main.security.authorization.helper'); + +.. todo:: + + Waiting for a link between our api and this doc, we invite you to read the method signatures `here `_ + +Adding your own roles +===================== + +.. warning:: + + This part is not fully implemented. The signature of the abstract class :class:`Chill\\Security\\Authorization\\ChillVoter` will change in the future. + +Extending Chill will requires you to define your own roles and rules for your entities. You will have to define your own voter to do so. + +.. seealso:: + + `How to Use Voters to Check User Permissions `_ + + From the symfony cookbook + + `New in Symfony 2.6: Simpler Security Voters `_ + + From the symfony blog + + + +To create your own roles, you will have to implement your own voter. This voter will have to extends the :class:`Chill\\MainBundle\\Security\\AbstractChillVoter`. Inside this class, you might use the :class:Chill\\MainBundle\\Security\\Authorization\\AuthorizationHelper to check permission (do not re-invent the wheel). This is a real-world example: + +.. code-block:: php + + namespace Chill\ReportBundle\Security\Authorization; + use Chill\MainBundle\Security\Authorization\AbstractChillVoter; + use Chill\MainBundle\Security\Authorization\AuthorizationHelper; + + + class ReportVoter extends AbstractChillVoter + { + const CREATE = 'CHILL_REPORT_CREATE'; + const SEE = 'CHILL_REPORT_SEE'; + const UPDATE = 'CHILL_REPORT_UPDATE'; + + /** + * + * @var AuthorizationHelper + */ + protected $helper; + + public function __construct(AuthorizationHelper $helper) + { + $this->helper = $helper; + } + + protected function getSupportedAttributes() + { + return array(self::CREATE, self::SEE, self::UPDATE); + } + protected function getSupportedClasses() + { + return array('Chill\ReportBundle\Entity\Report'); + } + protected function isGranted($attribute, $report, $user = null) + { + if (! $user instanceof \Chill\MainBundle\Entity\User){ + + return false; + } + + return $this->helper->userHasAccess($user, $report, $attribute); + } + } + +Then, you will have to declare the service and tag it as a voter : + +.. code-block:: yaml + + services: + chill.report.security.authorization.report_voter: + class: Chill\ReportBundle\Security\Authorization\ReportVoter + arguments: + - "@chill.main.security.authorization.helper" + tags: + - { name: security.voter } + + +Adding role hierarchy +--------------------- + +You should prepend Symfony's security component directly from your code. + +.. code-block:: php + + namespace Chill\ReportBundle\DependencyInjection; + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\Config\FileLocator; + use Symfony\Component\HttpKernel\DependencyInjection\Extension; + use Symfony\Component\DependencyInjection\Loader; + use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; + use Chill\MainBundle\DependencyInjection\MissingBundleException; + + /** + * This is the class that loads and manages your bundle configuration + * + * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html} + */ + class ChillReportExtension extends Extension implements PrependExtensionInterface + { + public function prepend(ContainerBuilder $container) + { + $this->prependRoleHierarchy($container); + } + + protected function prependRoleHierarchy(ContainerBuilder $container) + { + $container->prependExtensionConfig('security', array( + 'role_hierarchy' => array( + 'CHILL_REPORT_UPDATE' => array('CHILL_REPORT_SEE'), + 'CHILL_REPORT_CREATE' => array('CHILL_REPORT_SEE') + ) + )); + } + } + + diff --git a/source/development/index.rst b/source/development/index.rst index a071c6090..0a9f0b069 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -18,6 +18,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Instructions to create a new bundle Routing Menus + Access control model Message to users Localisation Database migrations diff --git a/source/static/access_control_model.png b/source/static/access_control_model.png new file mode 100644 index 0000000000000000000000000000000000000000..d896103c1b32d45fbd1d7b4e775e78eb8779da4f GIT binary patch literal 17645 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 literal 0 HcmV?d00001 From 54f48149536f8f95221fb59448800089ee11de2a Mon Sep 17 00:00:00 2001 From: Jean Pierre Huart Date: Tue, 30 Jun 2015 17:16:49 +0200 Subject: [PATCH 071/157] propose reorganisation of installation doc --- source/installation/index.rst | 6 +- .../install_additional_bundles.rst | 60 +++++++++ .../installation/install_postgres_server.rst | 26 ++++ ...s.rst => install_production_webserver.rst} | 9 +- source/installation/installation.rst | 117 ++++++++++-------- 5 files changed, 160 insertions(+), 58 deletions(-) create mode 100644 source/installation/install_additional_bundles.rst create mode 100644 source/installation/install_postgres_server.rst rename source/installation/{install_new_bundles.rst => install_production_webserver.rst} (71%) diff --git a/source/installation/index.rst b/source/installation/index.rst index 099628e02..d62680293 100644 --- a/source/installation/index.rst +++ b/source/installation/index.rst @@ -12,7 +12,7 @@ Free Documentation License". Instructions about installation -################################ +############################### You will learn here : @@ -20,7 +20,9 @@ You will learn here : :maxdepth: 2 How to install the software - Add new bundles to your installation + Add bundles to your installation + Install a PosgresSql server + Install production webserver .. todo:: diff --git a/source/installation/install_additional_bundles.rst b/source/installation/install_additional_bundles.rst new file mode 100644 index 000000000..f951c7d25 --- /dev/null +++ b/source/installation/install_additional_bundles.rst @@ -0,0 +1,60 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _install-additional-bundles: + +Install additional bundles +########################## + +There are four available bundles. + + - Main bundle + - Custom Fields bundle + - Person bundle + - Report bundle + +In Chill you are free to do what is most suitable for your activity. +By the way, the `Main bundle` is highly **required** as it provides the access control model (users, groups, and all concepts). So it should be installed. +The other bundles are optional, and if you decide to use them their installation will follow the same mechanism as the Main. + +So let's go into details on how to install the Main bundle. + +The Installation of the Chill project has already prepared everything to make this easy. +Open your terminal and run the followings: + +.. code-block:: bash + + cd path/to/your/directory + + # Go to the placeholder directory of the desired bundle (in this case `main`). + + cd vendor/chill-project/main + + # Install the bundle with composer: + + composer install + +As composer ends its task, it will notify you that `Some migration files have been imported. +You should run `php app/console doctrine:migrations:status` and/or `php app/console doctrine:migrations:migrate` to apply them to your DB.` + +So just do it: + +.. code-block:: bash + + cd path/to/your/directory + + php app/console doctrine:migrations:status + php app/console doctrine:migrations:migrate + +To be sure that install of the bundle has been done correctly, we will launch the unit tests related to it. + +.. code-block:: bash + + cd vendor/chill-project/main + phpunit + \ No newline at end of file diff --git a/source/installation/install_postgres_server.rst b/source/installation/install_postgres_server.rst new file mode 100644 index 000000000..37b9820ea --- /dev/null +++ b/source/installation/install_postgres_server.rst @@ -0,0 +1,26 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _install-postgres-server: + +Install PostgresSql server +########################## + +.. todo:: + + the section "Install PostresSql database" must be written. Help appreciated :-) + +.. note:: + + To avoid installation and configuration of a postgresql server, you may use `our docker image `_ to start and configure a database as decribed in the basic installation chapter. + This solution can be used also in a production environment. + +.. note:: + + Installing unaccent extension on ther server is possible with the package `postgresql-contrib-9.x` (replace 9.x with your server version). The extension may be enabled with running `CREATE EXTENSION unaccent;` in the database, with a superuser account. + diff --git a/source/installation/install_new_bundles.rst b/source/installation/install_production_webserver.rst similarity index 71% rename from source/installation/install_new_bundles.rst rename to source/installation/install_production_webserver.rst index 85b31d323..c7ada3700 100644 --- a/source/installation/install_new_bundles.rst +++ b/source/installation/install_production_webserver.rst @@ -6,11 +6,12 @@ A copy of the license is included in the section entitled "GNU Free Documentation License". -.. _install-new-bundles: +.. _install-production-webserver: -Install new bundles to your installation -######################################## +Install production webserver +############################ .. todo:: - the section "install new bundles" must be written. Help appreciated :-) + the section "Install production webserver" must be written. Help appreciated :-) + \ No newline at end of file diff --git a/source/installation/installation.rst b/source/installation/installation.rst index b5d83dc43..d44005803 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -15,64 +15,61 @@ Installation Basic installation `````````````````` - - Chill is written in PHP and use the `symfony framework`_. We take advantages of all the framework's feature, and installation should be as simple as installing symfony. +We are going to describe a basic installation on Unix systems (Unix, Mac OS). Windows installation has not been tested. + Requirements ------------ -Server requirements -^^^^^^^^^^^^^^^^^^^^ - -* a postgresql database, with the `*unaccent* extension`_ enabled. The minimum version is postgresql 9.4. Alternatively, you can use `the docker image provided `_ (see notes above) -* php 5.5 -* If you run Chill in production mode, you should also install a web server (apache, ngnix, ...). We may use php built-in server for testing and development. - -Within this documentation, we are going to describe installation on Unix systems (Unix, Mac OS). Windows installation has not been tested. - -You won't need any web server for setting up an instance for a demonstration or development. - -.. note:: - - Installing unaccent extension on ther server is possible with the package `postgresql-contrib-9.x` (replace 9.x with your server version). The extension may be enabled with running `CREATE EXTENSION unaccent;` in the database, with a superuser account. - -.. note:: - - To avoid installation and configuration of a postgresql server, you may use `our docker image `_ to start and configure a database. - - After `docker installation `_, run : - - .. code-block:: bash - - sudo docker run -P --name=chill_db chill/database - - This will download the chill/database image and start a new docker instance with the name `chill_db` and export the postgresql port `5432` on another random local port. - - In a new terminal, run - - .. code-block:: bash - - sudo docker port chill_db 5432 - - This command will show on which port the docker container is listening, on your localhost. During the part :ref:`create-your-project` this is the value to be used to fill the field 'database_port'. - Client requirements ^^^^^^^^^^^^^^^^^^^ Chill is accessible through a web browser. Currently, we focus our support on `Firefox`_, because firefox is open source, cross-platform, and very well active. The software should work with other browser (Chromium, Opera, ...) but some functionalities might break. +Server requirements +^^^^^^^^^^^^^^^^^^^ + +* a postgresql database, with the `*unaccent* extension`_ enabled. The minimum version is postgresql 9.4. You can use `the docker image provided `_. Using the docker image is also a solution for production site. Alternatively you can install a PosgresSql server see :ref:`install-postgres-server`. +* PHP 5.5. +* If you run Chill in production mode, you should also install a web server (apache, ngnix, ...) see :ref:`install-production-webserver`. For this basic installation meant for testing and/or development, we will make it simplier using the php built-in server. + + +Let's start by installing and configuring the docker database. +You will find all details concerning the installation of docker on their official site looking for your OS into the menu `Install/Docker Engine `_. + +Once docker is installed, run : + +.. code-block:: bash + + sudo docker run -P --name=chill_db chill/database + +This will download the chill/database image and start a new docker instance with the name `chill_db` and export the postgresql port `5432` on another random local port. + +.. _docker-database-port: + +The docker database port +^^^^^^^^^^^^^^^^^^^^^^^^ + +Open a new terminal and run + +.. code-block:: bash + + sudo docker port chill_db 5432 + +This command will show on which port the docker container is listening, on your localhost. During the part :ref:`create-your-project` this is the value to be used to fill the field 'database_port' as explained hereafter. + Preparation ----------- -You will need those informations : +To create your project, you will need the following information : -* The information to access to your database: host, port, database name, and your credentials (username and password) ; -* a random string, which will be use to improve entropy in security. Choose anything you want (random character, your father's birthplace, ...). +* how to access to your database: host, port, database name, and your credentials (username and password) ; +* a random string, which will be used to improve entropy in security. Choose anything you want (random character, your father's birthplace, ...). If you have installed the docker database your information should be: - database_host: 127.0.0.1 - - database_port: [see above] + - database_port: see :ref:`docker-database-port` - database_name: postgres - database_user: postgres - database_password: postgres @@ -95,20 +92,18 @@ Install composer on your system : curl -sS https://getcomposer.org/installer | php -Move composer.phar to your system -""""""""""""""""""""""""""""""""" +Install composer.phar globally +"""""""""""""""""""""""""""""" .. note:: - This part is not necessary, but this will be more convenient for the process. If you do not want to install composer on your system, you will have to replace the commands `composer` by `php composer.phar`. + This part is not mandatory, if you do not want to install composer globally, you will have to replace in the commands of this tutorial `composer` by `php composer.phar`. -Install composer globally on you system will made the installation process more convenient. To do this, simply run +Install composer globally on you system will made the installation process easier. To do this, simply run .. code-block:: bash sudo mv composer.phar /usr/local/bin/composer -Then, just run `composer` instead of `php composer.phar` - You can test the installation by running `which composer` or `composer`: those command should not raise any error. .. note:: @@ -123,8 +118,7 @@ Create your Chill project using composer: .. code-block:: bash - php composer.phar create-project chill-project/standard \ - path/to/your/directory --stability=dev + composer create-project chill-project/standard path/to/your/directory --stability=dev You should, now, move your cursor to the new directory @@ -163,7 +157,7 @@ You may accept the default parameters of `debug_toolbar`, `debug_redirects` and `php app/console doctrine:migrations:status` and/or `php app/console doctrine:migrations:migrate` to apply them to your DB. - We will proceed to this step some steps further. See :ref:`create-database-schema`. + We will proceed this step a bit later. See :ref:`create-database-schema`. Check your requirements ^^^^^^^^^^^^^^^^^^^^^^^ @@ -194,12 +188,13 @@ SQL queries will be printed into your console. Populate your database with basic information ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Once your database schema is ready, you should populate your database with some basic information. Those are provided through scripts and might depends from the bundle you choose to install (see :ref:`install-new-bundles`) +Once your database schema is ready, you should populate your database with some basic information. Those are provided through scripts and might depends from the bundle you choose to install (see :ref:`install-additional-bundles`) The main bundle require two scripts to be executed : .. code-block:: bash + php app/console doctrine:fixtures:load php app/console chill:main:countries:populate php app/console chill:main:languages:populate @@ -237,7 +232,25 @@ If everything was fine, you are able to launch your built-in server : php app/console server:run -Your server should now be available at `http://localhost:8000`. Type this address on your browser and you should see the homepage. +Your server should now be available at `http://localhost:8000`. Type this address on your browser and you should see the homepage. +The default login is 'center a_social' with password 'password'. + +Have fun exploring Chill. + + +Uninstall Chill +``````````````` + +.. todo:: + + the section "Uninstall Chill" must be written. Help appreciated :-) + +Uninstall the docker database +----------------------------- + +Uninstall the application +------------------------- + .. _the composer documentation: https://getcomposer.org/doc/ .. _the composer introduction: https://getcomposer.org/doc/00-intro.md From bf48f22bd9f8b0f51a5d6e1c3f27e0b8ee0456d7 Mon Sep 17 00:00:00 2001 From: Jean Pierre Huart Date: Thu, 2 Jul 2015 17:39:09 +0200 Subject: [PATCH 072/157] Install reviewed + update chill + install additional bundles --- source/bundles/custom-fields.rst | 12 +- source/bundles/main.rst | 4 +- source/bundles/person.rst | 8 +- source/bundles/report.rst | 4 +- source/development/create-a-new-bundle.rst | 4 +- source/installation/index.rst | 2 + .../install_additional_bundles.rst | 55 +++---- source/installation/installation.rst | 147 ++++++++++-------- source/installation/uninstall_chill.rst | 34 ++++ source/installation/update_chill.rst | 48 ++++++ 10 files changed, 207 insertions(+), 111 deletions(-) create mode 100644 source/installation/uninstall_chill.rst create mode 100644 source/installation/update_chill.rst diff --git a/source/bundles/custom-fields.rst b/source/bundles/custom-fields.rst index 9d3ad86b3..71d3e77e4 100644 --- a/source/bundles/custom-fields.rst +++ b/source/bundles/custom-fields.rst @@ -6,6 +6,8 @@ A copy of the license is included in the section entitled "GNU Free Documentation License". +.. _custom-fields-bundle: + Custom fields bundle ==================== @@ -42,7 +44,7 @@ Some entities needs a **default** custom fields group. For instance, the default In the future of the `person bundle`, other custom fields group will be added in forms accessible from the menu, allowing users to completely customize and separate their entities. Allow custom fields on a entity --------------------------------- +------------------------------- As a developer, you must allow your users to add custom fields on your entities. @@ -126,7 +128,7 @@ Add those file under `chill_custom_fields` section : * The class, which is a full FQDN class path Automatically, in DependencyInjection/Extension class -"""""""""""""""""""""""""""""""""""""""""""""""""""""" +""""""""""""""""""""""""""""""""""""""""""""""""""""" This is the preferred way for declaring customizable classes. @@ -262,7 +264,7 @@ In the `PrependExtensionInterface::prepend` function, the options key will be ad Note that `custom_fields_group_linked_custom_fields` does not create any input on `CustomFieldsGroup` creation : there aren't any fields associated with the custom fields just after the group creation... You have to add custom fields and associate them with the newly created group to see them appears. Rendering custom fields in a template --------------------------------------- +------------------------------------- Two function are available : @@ -310,7 +312,7 @@ Examples: Custom Fields's form ---------------------- +-------------------- You should simply use the 'custom_field' type in a template, with the group you would like to render in the `group` option's type. @@ -377,7 +379,7 @@ Example : Development tips ------------------ +---------------- If you want to test the rendering of a custom fields group, you may use this method : diff --git a/source/bundles/main.rst b/source/bundles/main.rst index 254317691..de2b890d7 100644 --- a/source/bundles/main.rst +++ b/source/bundles/main.rst @@ -5,7 +5,9 @@ with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". - + +.. _main-bundle: + Main bundle ########### diff --git a/source/bundles/person.rst b/source/bundles/person.rst index 091b00758..fde93b5f6 100644 --- a/source/bundles/person.rst +++ b/source/bundles/person.rst @@ -5,7 +5,9 @@ with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". - + +.. _person-bundle: + Person bundle ############# @@ -23,12 +25,12 @@ Entities provided Search terms -************* +************ The class `Chill\PersonBundle\Search\PersonSearch` provide the search module. Domain -======= +====== The search upon "person" is provided by default. The `@person` domain search may be omitted. diff --git a/source/bundles/report.rst b/source/bundles/report.rst index 524606353..d71caa0be 100644 --- a/source/bundles/report.rst +++ b/source/bundles/report.rst @@ -5,7 +5,9 @@ with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". - + +.. _report-bundle: + Report bundle ############# diff --git a/source/development/create-a-new-bundle.rst b/source/development/create-a-new-bundle.rst index 7c987a70d..087487ff0 100644 --- a/source/development/create-a-new-bundle.rst +++ b/source/development/create-a-new-bundle.rst @@ -6,8 +6,10 @@ A copy of the license is included in the section entitled "GNU Free Documentation License". +.. _create-new-bundle: + Create a new bundle -******************** +******************* Create your own bundle is not a trivial task. diff --git a/source/installation/index.rst b/source/installation/index.rst index d62680293..260b3e845 100644 --- a/source/installation/index.rst +++ b/source/installation/index.rst @@ -20,7 +20,9 @@ You will learn here : :maxdepth: 2 How to install the software + Update Chill Add bundles to your installation + Uninstall Chill Install a PosgresSql server Install production webserver diff --git a/source/installation/install_additional_bundles.rst b/source/installation/install_additional_bundles.rst index f951c7d25..a40bf7575 100644 --- a/source/installation/install_additional_bundles.rst +++ b/source/installation/install_additional_bundles.rst @@ -11,50 +11,39 @@ Install additional bundles ########################## -There are four available bundles. +A basic installation of Chill include four bundles: - - Main bundle - - Custom Fields bundle - - Person bundle - - Report bundle + - :ref:`main-bundle` + - :ref:`person-bundle` + - :ref:`report-bundle` + - :ref:`custom-fields-bundle` -In Chill you are free to do what is most suitable for your activity. -By the way, the `Main bundle` is highly **required** as it provides the access control model (users, groups, and all concepts). So it should be installed. -The other bundles are optional, and if you decide to use them their installation will follow the same mechanism as the Main. +but you can add as many as needed by your project, and if the bundle does not exists yet, you can create a new one, see :ref:`create-new-bundle` . + +In Chill you are free to do what is most suitable for your activity, so let's go into details on how to add an existing bundle. +We will add the bundle 'icpc2' that does something very important... -So let's go into details on how to install the Main bundle. - -The Installation of the Chill project has already prepared everything to make this easy. -Open your terminal and run the followings: +.. todo:: + Add description of the bundle + +Open your terminal let composer do the magic for you: .. code-block:: bash - cd path/to/your/directory - - # Go to the placeholder directory of the desired bundle (in this case `main`). - - cd vendor/chill-project/main - - # Install the bundle with composer: - - composer install + cd path/to/your/directory + composer require chill-main/icpc2 -As composer ends its task, it will notify you that `Some migration files have been imported. -You should run `php app/console doctrine:migrations:status` and/or `php app/console doctrine:migrations:migrate` to apply them to your DB.` +As composer ends its task, it could notify you that `Some migration files have been imported. +In this case You should run `php app/console doctrine:migrations:status` and/or `php app/console doctrine:migrations:migrate` to apply them to your DB.` So just do it: .. code-block:: bash - cd path/to/your/directory - - php app/console doctrine:migrations:status php app/console doctrine:migrations:migrate + +.. note:: + The following has to be automated: -To be sure that install of the bundle has been done correctly, we will launch the unit tests related to it. - -.. code-block:: bash - - cd vendor/chill-project/main - phpunit - \ No newline at end of file + Finally we should modify the AppKernel.php file adding 'new Chill\Icpc2Bundle\ChillIcpc2Bundle(),' in the $bundle array. + \ No newline at end of file diff --git a/source/installation/installation.rst b/source/installation/installation.rst index d44005803..fe4b90ae4 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -32,56 +32,13 @@ Server requirements * a postgresql database, with the `*unaccent* extension`_ enabled. The minimum version is postgresql 9.4. You can use `the docker image provided `_. Using the docker image is also a solution for production site. Alternatively you can install a PosgresSql server see :ref:`install-postgres-server`. * PHP 5.5. +* Composer. * If you run Chill in production mode, you should also install a web server (apache, ngnix, ...) see :ref:`install-production-webserver`. For this basic installation meant for testing and/or development, we will make it simplier using the php built-in server. - -Let's start by installing and configuring the docker database. -You will find all details concerning the installation of docker on their official site looking for your OS into the menu `Install/Docker Engine `_. - -Once docker is installed, run : - -.. code-block:: bash - - sudo docker run -P --name=chill_db chill/database - -This will download the chill/database image and start a new docker instance with the name `chill_db` and export the postgresql port `5432` on another random local port. - -.. _docker-database-port: - -The docker database port -^^^^^^^^^^^^^^^^^^^^^^^^ - -Open a new terminal and run - -.. code-block:: bash - - sudo docker port chill_db 5432 - -This command will show on which port the docker container is listening, on your localhost. During the part :ref:`create-your-project` this is the value to be used to fill the field 'database_port' as explained hereafter. - -Preparation ------------ - -To create your project, you will need the following information : - -* how to access to your database: host, port, database name, and your credentials (username and password) ; -* a random string, which will be used to improve entropy in security. Choose anything you want (random character, your father's birthplace, ...). - -If you have installed the docker database your information should be: - - database_host: 127.0.0.1 - - database_port: see :ref:`docker-database-port` - - database_name: postgres - - database_user: postgres - - database_password: postgres - - locale: fr - -Installation ------------- - -Chill is installed with `composer`_. +Let's start by installing composer as it is needed to create and update our Chill project. Install composer -^^^^^^^^^^^^^^^^ +"""""""""""""""" .. note:: If you do not know composer, it is a good idea to have a glance at `the composer documentation`_ @@ -109,6 +66,76 @@ You can test the installation by running `which composer` or `composer`: those c .. note:: See `the composer introduction`_ to learn how to install composer on Mac OS X and Windows +The docker database +""""""""""""""""""" + +Let's continue now by installing and configuring the docker database. +You will find all details concerning the installation of docker on their official site looking for your OS into the menu `Install/Docker Engine `_. + +Once docker is installed, run : + +.. code-block:: bash + + sudo docker run -P --name=chill_db chill/database + +This will download the chill/database image and start a new docker instance with the name `chill_db` and export the postgresql port `5432` on another random local port. +You can exit from the terminal and check if the docker database is running with the following command: + +.. code-block:: bash + + sudo docker ps + + >>>> CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + >>>> 08bbb62bd5e8 chill/database "/docker-entrypoint. 6 days ago Up 5 hours 0.0.0.0:32768->5432/tcp chill_db + +If the response does not show up 'chill_db', you can start and stop it via: + +.. code-block:: bash + + sudo docker start chill_db + >>>> chill_db + + sudo docker stop chill_db + >>>> chill_db + + +Installation +------------ + +Chill is installed with `composer`_. + +.. _preparation: + +Preparation +^^^^^^^^^^^ + +Before creating your project, make sure that you know the following information : + +* how to access to your database: host, port, database name, and your credentials (username and password) ; +* a random string, which will be used to improve entropy in security. Choose anything you want (random character, your father's birthplace, ...). + +.. note:: + + **If you have installed the docker database:** + + Open a terminal and run + + .. code-block:: bash + + sudo docker port chill_db 5432 + + This command will show on which port the docker container is listening, on your localhost. + This is the value to be used to fill the field 'database_port' hereafter. + + Your information should be: + + - database_host: 127.0.0.1 + - database_port: result of the command hereabove. + - database_name: postgres + - database_user: postgres + - database_password: postgres + - locale: fr + .. _create-your-project: Create your project @@ -127,7 +154,7 @@ You should, now, move your cursor to the new directory cd path/to/your/directory .. note:: - Until now, the stability of the project is set to "dev". Do not forget this argument, or composer will fail to download and create the project. + Until now, the stability of the project is set to "dev". Do not forget this argument, or composer will fail to download and create the project. Composer will download `the standard architecture`_ and ask you a few question about how to configure your project. @@ -137,9 +164,10 @@ Composer will download `the standard architecture`_ and ask you a few question a * `database_user` : the username to reach your database * `database_password` : your username's password * `locale`: the language, as iso code. Until now, only fr is supported -* `secret`: the secret string you prepared (see "preparation") +* `secret`: the secret string you prepared (see :ref:`preparation`) -You may accept the default parameters of `debug_toolbar`, `debug_redirects` and `use_assetic_controller` for a demonstration installation. For production, set them all to `false`. +You may accept the default parameters of `debug_toolbar`, `debug_redirects` and `use_assetic_controller` for a demonstration installation. +For production, set them all to `false`. .. note:: @@ -198,11 +226,11 @@ The main bundle require two scripts to be executed : php app/console chill:main:countries:populate php app/console chill:main:languages:populate -Those will populate database, respectively, with countries (using ISO declaration) and languages (using, also, ISO informations). +Those will populate database, respectively, with basic dummy data, countries (using ISO declaration) and languages (using, also, ISO informations). -Building CSS (optionnal) -^^^^^^^^^^^^^^^^^^^^^^^^ +Building CSS (optional) +^^^^^^^^^^^^^^^^^^^^^^^ For this step, npm must be installed. @@ -215,7 +243,6 @@ A build version of the needed CSS file is provided within the main bundle `Resou cd vendor/chill-project/main/Resources/ npm install grunt - Go back to your project root before doing next step .. code-block:: bash @@ -236,20 +263,6 @@ Your server should now be available at `http://localhost:8000`. Type this addres The default login is 'center a_social' with password 'password'. Have fun exploring Chill. - - -Uninstall Chill -``````````````` - -.. todo:: - - the section "Uninstall Chill" must be written. Help appreciated :-) - -Uninstall the docker database ------------------------------ - -Uninstall the application -------------------------- .. _the composer documentation: https://getcomposer.org/doc/ diff --git a/source/installation/uninstall_chill.rst b/source/installation/uninstall_chill.rst new file mode 100644 index 000000000..8949fa6b3 --- /dev/null +++ b/source/installation/uninstall_chill.rst @@ -0,0 +1,34 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _uninstall-chill: + +Uninstall Chill +``````````````` + +.. todo:: + + the section "Uninstall Chill" must be written. Help appreciated :-) + +.. _uninstall-docker-database: + +Uninstall the docker database +----------------------------- + +.. todo:: + + the section "Uninstall the docker database" must be written. Help appreciated :-) + +.. _uninstall-application: + +Uninstall the application +------------------------- + +.. todo:: + + the section "Uninstall the application" must be written. Help appreciated :-) diff --git a/source/installation/update_chill.rst b/source/installation/update_chill.rst new file mode 100644 index 000000000..63685521f --- /dev/null +++ b/source/installation/update_chill.rst @@ -0,0 +1,48 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _update-chill: + +Update Chill +############ + +Updating the application is very simple, thanks to the use of composer. + +.. code-block:: bash + + cd path/to/your/directory + + composer update + +Composer allow to control strictly the versions to be used in the application. +This allow to manage a production server, a staging one and a developpement one. + +For the production one, define very precisely the versions of each component in the `composer.json` file at the root of the application and require only `stable` versions. +In this situation even if an update of composer is launched, the sources will not be modified. + +For the staging and development one, let open choices to the versions or accept development versions and using `composer update` will automatically update the selected modules. +Once validated at staging level they can be sent in production re-precising the latest accepted versions. + + +.. note:: + It is advisable to keep the version of composer up to date. To do that, just launch the following command in a terminal. + + .. code-block:: bash + + composer self-update + + If this update create troubles (which doesn't happen often) you can always undo this update running: + + .. code-block:: bash + + composer self-update --rollback + + See the `the composer documentation`_ for all details. + + +.. _the composer documentation: https://getcomposer.org/doc/ From 23b669b731613f4cfb972a2586dfa2b57535d41b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 2 Jul 2015 23:28:25 +0200 Subject: [PATCH 073/157] fix some installation steps --- .../install_additional_bundles.rst | 7 +++-- source/installation/installation.rst | 28 ++++++------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/source/installation/install_additional_bundles.rst b/source/installation/install_additional_bundles.rst index a40bf7575..3af11fe6e 100644 --- a/source/installation/install_additional_bundles.rst +++ b/source/installation/install_additional_bundles.rst @@ -21,7 +21,8 @@ A basic installation of Chill include four bundles: but you can add as many as needed by your project, and if the bundle does not exists yet, you can create a new one, see :ref:`create-new-bundle` . In Chill you are free to do what is most suitable for your activity, so let's go into details on how to add an existing bundle. -We will add the bundle 'icpc2' that does something very important... + +We will add the bundle 'icpc2' that set `icpc code `_ available as custom field. .. todo:: Add description of the bundle @@ -45,5 +46,5 @@ So just do it: .. note:: The following has to be automated: - Finally we should modify the AppKernel.php file adding 'new Chill\Icpc2Bundle\ChillIcpc2Bundle(),' in the $bundle array. - \ No newline at end of file + Finally we should modify the AppKernel.php file adding `new Chill\\Icpc2Bundle\\ChillIcpc2Bundle(),` in the $bundle array as described `in the symfony documentation `_. + diff --git a/source/installation/installation.rst b/source/installation/installation.rst index fe4b90ae4..21a88282e 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -60,6 +60,7 @@ Install composer globally on you system will made the installation process easie .. code-block:: bash sudo mv composer.phar /usr/local/bin/composer + sudo chmod +x /usr/local/bin/composer You can test the installation by running `which composer` or `composer`: those command should not raise any error. @@ -79,7 +80,8 @@ Once docker is installed, run : sudo docker run -P --name=chill_db chill/database This will download the chill/database image and start a new docker instance with the name `chill_db` and export the postgresql port `5432` on another random local port. -You can exit from the terminal and check if the docker database is running with the following command: + +The db will start in your terminal. In another terminal, you can check if the docker database is running and showing the exposed port with the following command: .. code-block:: bash @@ -88,7 +90,7 @@ You can exit from the terminal and check if the docker database is running with >>>> CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES >>>> 08bbb62bd5e8 chill/database "/docker-entrypoint. 6 days ago Up 5 hours 0.0.0.0:32768->5432/tcp chill_db -If the response does not show up 'chill_db', you can start and stop it via: +You can start and stop the container it via: .. code-block:: bash @@ -223,31 +225,19 @@ The main bundle require two scripts to be executed : .. code-block:: bash php app/console doctrine:fixtures:load - php app/console chill:main:countries:populate - php app/console chill:main:languages:populate Those will populate database, respectively, with basic dummy data, countries (using ISO declaration) and languages (using, also, ISO informations). -Building CSS (optional) -^^^^^^^^^^^^^^^^^^^^^^^ - -For this step, npm must be installed. - -A build version of the needed CSS file is provided within the main bundle `Resources/public/css/chillmain.css`. For rebuilding it : +Preparing assets +^^^^^^^^^^^^^^^^ +You have to dump assets into the web directory. Even if the command should be run by Composer, you may run it manually : .. code-block:: bash - #in the main bundle directory ( vendor/chill-project/main/ ) - cd vendor/chill-project/main/Resources/ - npm install grunt - -Go back to your project root before doing next step - -.. code-block:: bash - - cd path/to/your/directory + php app/console assetic:dump + php app/console assets:install Launch your server From 44e244501354622178915dcdba2c20048d725a52 Mon Sep 17 00:00:00 2001 From: Jean Pierre Huart Date: Fri, 3 Jul 2015 17:39:21 +0200 Subject: [PATCH 074/157] small changes in install --- source/development/installation.rst | 28 ++++++++++++++-------------- source/installation/installation.rst | 28 ++++++++++++++++------------ 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/source/development/installation.rst b/source/development/installation.rst index 069d98df9..3a137724a 100644 --- a/source/development/installation.rst +++ b/source/development/installation.rst @@ -13,10 +13,10 @@ Installation for development Installation for development should let you able to have an access to the source code, upload the code to our CVS (i.e. `git`_), and working with `composer`_. -As Chill is divided into modules, each module has his own repository. +As Chill is divided into bundles (the Symfony name for 'modules'), each bundle has his own repository. Installation and big picture ------------------------------ +---------------------------- At first, you should install Chill as described in the :ref:`basic-installation` section. @@ -26,8 +26,7 @@ At first, You should add the `--prefer-source` argument when you create project. .. code-block:: bash - php composer.phar create-project chill-project/standard \ - path/to/your/directory --stability=dev --prefer-source + composer create-project chill-project/standard path/to/your/directory --stability=dev --prefer-source Secondly, if composer ask you the following question : :: @@ -35,7 +34,7 @@ Secondly, if composer ask you the following question : :: **You should answer `n` (no).** -This will install a project. All downloaded modules will be stored in the `/vendor` directories. In those directories, you will have access to the git commands. +This will install a project. All downloaded bundles will be stored in the `/vendor` directories. In those directories, you will have access to the git commands. .. code-block:: bash @@ -46,13 +45,13 @@ This will install a project. All downloaded modules will be stored in the `/vend origin git://github.com/Chill-project/Standard.git (fetch) origin git@github.com:Chill-project/Standard.git (push) -Deleted and added files after installation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Files cleaning after installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Composer will deleted unrequired files, and add some of them. This perfectly normal and will appears in your git index. But you should NOT delete those files. +Composer will delete unrequired files, and add some. This is perfectly normal and will appears in your git index. +But you should NOT delete those files. - -This should appears : +This is the expected 'git status' result: .. code-block:: bash @@ -68,7 +67,7 @@ This should appears : supprimé: app/SymfonyStandard/RootPackageInstallSubscriber.php modifié: app/check.php -You can ignore the locally using the `git update-index --assume-unchanged` command. +You can ignore the local changes using the `git update-index --assume-unchanged` command. .. code-block:: bash @@ -80,9 +79,10 @@ You can ignore the locally using the `git update-index --assume-unchanged` comma Working with your own fork ^^^^^^^^^^^^^^^^^^^^^^^^^^ -Ideally, you will work on a fork of the main github repository. To ensure that composer will download the code from **your** repository, you will have to adapt the `composer.json` file accordingly, using your own repository. +Ideally, you will work on a fork of the main github repository. +To ensure that composer will download the code from **your** repository, you will have to adapt the `composer.json` file accordingly, using your own repository. -Add the following lines to your composer.json file if you want to force composer to download from your own repository. This will force to use your own repository for the ChillMain bundle, insert in `composer.json` the following lines : +Add the following lines to your composer.json file if you want to force composer to download from your own repository: .. code-block:: json @@ -102,7 +102,7 @@ Editing the code and commiting You may edit code in the `vendor/path/to/the/bundle` directory. -After your edits, you should commit as usually : +Once satisfied with your changes, you should commit as usually : .. code-block:: bash diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 21a88282e..399e72ec7 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -31,7 +31,7 @@ Server requirements ^^^^^^^^^^^^^^^^^^^ * a postgresql database, with the `*unaccent* extension`_ enabled. The minimum version is postgresql 9.4. You can use `the docker image provided `_. Using the docker image is also a solution for production site. Alternatively you can install a PosgresSql server see :ref:`install-postgres-server`. -* PHP 5.5. +* PHP version >= 5.5. Check that extensions php5-intl and php5-pgsql are installed and that '*date.timezone*' is correctly defined in your php.ini. * Composer. * If you run Chill in production mode, you should also install a web server (apache, ngnix, ...) see :ref:`install-production-webserver`. For this basic installation meant for testing and/or development, we will make it simplier using the php built-in server. @@ -90,15 +90,19 @@ The db will start in your terminal. In another terminal, you can check if the do >>>> CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES >>>> 08bbb62bd5e8 chill/database "/docker-entrypoint. 6 days ago Up 5 hours 0.0.0.0:32768->5432/tcp chill_db -You can start and stop the container it via: +You can start the container it via: .. code-block:: bash sudo docker start chill_db >>>> chill_db - sudo docker stop chill_db - >>>> chill_db +.. note:: The commande to stop the docker container is: + + .. code-block:: bash + + sudo docker stop chill_db + >>>> chill_db Installation @@ -149,12 +153,6 @@ Create your Chill project using composer: composer create-project chill-project/standard path/to/your/directory --stability=dev -You should, now, move your cursor to the new directory - -.. code-block:: bash - - cd path/to/your/directory - .. note:: Until now, the stability of the project is set to "dev". Do not forget this argument, or composer will fail to download and create the project. @@ -192,6 +190,12 @@ For production, set them all to `false`. Check your requirements ^^^^^^^^^^^^^^^^^^^^^^^ +Move your cursor to the new directory + +.. code-block:: bash + + cd path/to/your/directory + You should check your installation by running .. code-block:: bash @@ -220,13 +224,13 @@ Populate your database with basic information Once your database schema is ready, you should populate your database with some basic information. Those are provided through scripts and might depends from the bundle you choose to install (see :ref:`install-additional-bundles`) -The main bundle require two scripts to be executed : +The main bundle require one script to be executed : .. code-block:: bash php app/console doctrine:fixtures:load -Those will populate database, respectively, with basic dummy data, countries (using ISO declaration) and languages (using, also, ISO informations). +Those will populate the database, with basic dummy data, countries (using ISO declaration) and languages (using, also, ISO informations). Preparing assets From 48ab9afa69dc7ac08401290c1909711dd9ea1fce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sun, 12 Jul 2015 11:16:38 +0200 Subject: [PATCH 075/157] add external requirements for readthedocs --- requirements.txt | 1 + source/conf.py | 43 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..73f5942ad --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +[-e] git+https://github.com/fabpot/sphinx-php.git@52f7bd2216cc22ef52494f346c5643bb2a74513f diff --git a/source/conf.py b/source/conf.py index d527d1039..51e4e1e77 100644 --- a/source/conf.py +++ b/source/conf.py @@ -28,10 +28,15 @@ if not on_rtd: sys.path.append(os.path.abspath('./../_exts/sphinx-php')) - # adding PhpLexer +# adding PhpLexer +try: from sphinx.highlighting import lexers from pygments.lexers.compiled import CLexer from pygments.lexers.web import PhpLexer + php_lexer_available = True +except ImportError: + php_lexer_available = False + print('specific import not available') # -- General configuration ------------------------------------------------ @@ -46,13 +51,33 @@ extensions = [ 'sphinx.ext.todo', ] -if not on_rtd: - extensions += [ - 'sensio.sphinx.refinclude', - 'sensio.sphinx.configurationblock', - 'sensio.sphinx.phpcode', - 'sensio.sphinx.bestpractice', - ] +try: + import sensio.sphinx.refinclude +except ImportError: + print('sensio.sphinx.refinclude is not available') +else: + extensions += ['sensio.sphinx.refinclude'] + +try: + import sensio.sphinx.configurationblock +except ImportError: + print('sensio.sphinx.configurationblock is not available') +else: + extensions += ['sensio.sphinx.configurationblock'] + +try: + import sensio.sphinx.phpcode +except ImportError: + print('sensio.sphinx.phpcode') +else: + extensions += ['sensio.sphinx.phpcode'] + +try: + import sensio.sphinx.bestpractice +except ImportError: + print('sensio.sphinx.bestpractice is not avaialble') +else: + extensions += ['sensio.sphinx.bestpractice'] #add configuration for api url @@ -122,7 +147,7 @@ pygments_style = 'sphinx' #keep_warnings = False # -- Settings for symfony doc extension --------------------------------------------------- -if not on_rtd: +if php_lexer_available: # enable highlighting for PHP code not between ```` by default lexers['php'] = PhpLexer(startinline=True) lexers['php-annotations'] = PhpLexer(startinline=True) From 7dcb4570501bd8104e9a247149016e43b848f86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sun, 12 Jul 2015 11:27:21 +0200 Subject: [PATCH 076/157] remove -e option for requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 73f5942ad..b73024f83 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -[-e] git+https://github.com/fabpot/sphinx-php.git@52f7bd2216cc22ef52494f346c5643bb2a74513f +git+https://github.com/fabpot/sphinx-php.git@52f7bd2216cc22ef52494f346c5643bb2a74513f#egg=sphinx-php From 70bc025ade5e1c239a85f7442429e934ba629539 Mon Sep 17 00:00:00 2001 From: Jean Pierre Huart Date: Tue, 14 Jul 2015 10:53:52 +0200 Subject: [PATCH 077/157] Changes proposed accepted and implemented --- source/installation/installation.rst | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 399e72ec7..4bba8f457 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -222,17 +222,14 @@ SQL queries will be printed into your console. Populate your database with basic information ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Once your database schema is ready, you should populate your database with some basic information. Those are provided through scripts and might depends from the bundle you choose to install (see :ref:`install-additional-bundles`) - -The main bundle require one script to be executed : +Once your database schema is ready, if you want to test the application you have the opportunity to populate your database with some basic data. +Those are provided through a script and might depends from the bundle you choose to install (see :ref:`install-additional-bundles`). +This script has not to be launched for a production server and will erase any existing data. It is meant only for testing the application. .. code-block:: bash php app/console doctrine:fixtures:load -Those will populate the database, with basic dummy data, countries (using ISO declaration) and languages (using, also, ISO informations). - - Preparing assets ^^^^^^^^^^^^^^^^ From a7146a5991ad336e36cc32fd6009a620193b05fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 9 Sep 2015 11:53:54 +0200 Subject: [PATCH 078/157] add very quick start with docker instruction --- source/installation/index.rst | 27 +++++++- .../very_quick_start_with_docker.rst | 63 +++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 source/installation/very_quick_start_with_docker.rst diff --git a/source/installation/index.rst b/source/installation/index.rst index 260b3e845..d869f22c8 100644 --- a/source/installation/index.rst +++ b/source/installation/index.rst @@ -11,18 +11,41 @@ A copy of the license is included in the section entitled "GNU Free Documentation License". -Instructions about installation +Installation ############################### -You will learn here : +Very quick install with docker +================================= .. toctree:: :maxdepth: 2 + Very quick install with docker + +Usage in production +==================== + +.. toctree:: + :maxdepth: 2 + How to install the software + +Update Chill and maintenance +============================== + +.. toctree:: + :titlesonly: + Update Chill Add bundles to your installation Uninstall Chill + +Database preparation +===================== + +.. toctree:: + :titlesonly: + Install a PosgresSql server Install production webserver diff --git a/source/installation/very_quick_start_with_docker.rst b/source/installation/very_quick_start_with_docker.rst new file mode 100644 index 000000000..5f65abe09 --- /dev/null +++ b/source/installation/very_quick_start_with_docker.rst @@ -0,0 +1,63 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Very quick start with docker +############################# + + +.. _quick-start-with-docker: + +We have created a `docker container `_ to let you test `Chill` easily. + +.. note:: + + We assume docker is already installed on your machine. If Docker is not installed, have a look at `the install page in the docker documentation `. + +Starting the containers +======================== + +Prepare a database: + +.. code-block:: bash + + docker run --rm --name=chill_database chill/database + +The first time you will run this command, the image will be downloaded from docker registry. Please be patient. + +Run the container containing the code and attach it to the database : + +.. code-block:: bash + + docker run --rm --link chill_database:db -p 8989:8000 --name=chill_php chill/demo-flavor + +The image will also be downloaded from docker registry on first run. + +You can then browse on `http://localhost:8989 `_ and login with the created users, like `center a_social`. + +Stopping the containers +======================= + +.. code-block:: bash + + docker stop chill_php + docker stop chill_database + +Limitations +============ + +* Those container should not be used in production +* The database should not be persisted or store persistant information: at each container startup, the container's data will be erased and replaced by new (partially) random fixtures + + +.. _the composer documentation: https://getcomposer.org/doc/ +.. _the composer introduction: https://getcomposer.org/doc/00-intro.md +.. _the standard architecture: https://github.com/Champs-Libres/chill-standard +.. _composer: https://getcomposer.org +.. _Firefox: https://www.mozilla.org +.. _symfony framework: http://symfony.com +.. _*unaccent* extension: http://www.postgresql.org/docs/current/static/unaccent.html From fdddf77a90060a1cde9f0c18bd3eae8eefe48681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 21 Sep 2015 13:31:59 +0200 Subject: [PATCH 079/157] fix doc about docker container demo --- .../very_quick_start_with_docker.rst | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/source/installation/very_quick_start_with_docker.rst b/source/installation/very_quick_start_with_docker.rst index 5f65abe09..158b2896b 100644 --- a/source/installation/very_quick_start_with_docker.rst +++ b/source/installation/very_quick_start_with_docker.rst @@ -16,7 +16,7 @@ We have created a `docker container `. + We assume docker is already installed on your machine. If Docker is not installed, have a look at `the install page in the docker documentation `_. Starting the containers ======================== @@ -37,7 +37,7 @@ Run the container containing the code and attach it to the database : The image will also be downloaded from docker registry on first run. -You can then browse on `http://localhost:8989 `_ and login with the created users, like `center a_social`. +You can then browse on `http://localhost:8989 `_ and login with the created users, like `center a_social` (the complete list is below). Password is always 'password'. Stopping the containers ======================= @@ -53,6 +53,23 @@ Limitations * Those container should not be used in production * The database should not be persisted or store persistant information: at each container startup, the container's data will be erased and replaced by new (partially) random fixtures +Users created +============== + +The passwords are always `password` : + +The user's login created are : + +* center a_social +* center a_administrative +* center a_direction +* center b_social +* center b_administrative +* center b_direction +* multi_center +* admin + + .. _the composer documentation: https://getcomposer.org/doc/ .. _the composer introduction: https://getcomposer.org/doc/00-intro.md From cb48b020f7122fce43a63ff4ca5ba9976bf5b5f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 29 Sep 2015 10:51:38 +0200 Subject: [PATCH 080/157] adding how to declare new roles refs #573 @0h30 adding documentation --- source/development/access_control_model.rst | 78 +++++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/source/development/access_control_model.rst b/source/development/access_control_model.rst index 8d5d8ec8b..8e31527de 100644 --- a/source/development/access_control_model.rst +++ b/source/development/access_control_model.rst @@ -129,12 +129,17 @@ Those methods are intentionnaly build to give information about user rights: Adding your own roles ===================== -.. warning:: - - This part is not fully implemented. The signature of the abstract class :class:`Chill\\Security\\Authorization\\ChillVoter` will change in the future. - Extending Chill will requires you to define your own roles and rules for your entities. You will have to define your own voter to do so. +To create your own roles, you should: + +* implement your own voter. This voter will have to extends the :class:`Chill\\MainBundle\\Security\\AbstractChillVoter`. As defined by Symfony, this voter must be declared as a service and tagged with `security.voter`; +* declare the role through implementing a service tagged with `chill.role` and implementing :class:`Chill\\MainBundle\\Security\\ProvideRoleInterface`. + +.. note:: + + Both operation may be done through a simple class: you can implements :class:`Chill\\MainBundle\\Security\\ProvideRoleInterface` and :class:`Chill\\MainBundle\\Security\\AbstractChillVoter` on the same class. See live example: :class:`Chill\\ActivityBundle\\Security\\Authorization\\ActivityVoter`, and similar examples in the `PersonBundle` and `ReportBundle`. + .. seealso:: `How to Use Voters to Check User Permissions `_ @@ -146,8 +151,71 @@ Extending Chill will requires you to define your own roles and rules for your en From the symfony blog +Declare your role +------------------ -To create your own roles, you will have to implement your own voter. This voter will have to extends the :class:`Chill\\MainBundle\\Security\\AbstractChillVoter`. Inside this class, you might use the :class:Chill\\MainBundle\\Security\\Authorization\\AuthorizationHelper to check permission (do not re-invent the wheel). This is a real-world example: +To declare new role, implement the class :class:`Chill\\MainBundle\\Security\\ProvideRoleInterface`. + +.. code-block:: php + + interface ProvideRoleInterface + { + /** + * return an array of role provided by the object + * + * @return string[] array of roles (as string) + */ + public function getRoles(); + + /** + * return roles which doesn't need + * + * @return string[] array of roles without scopes + */ + public function getRolesWithoutScope(); + } + + +Then declare your service with a tag `chill.role`. Example : + +.. code-block:: yaml + + your_service: + class: Chill\YourBundle\Security\Authorization\YourVoter + tags: + - { name: chill.role } + + +Example of an implementation of :class:`Chill\\MainBundle\\Security\\ProvideRoleInterface`: + +.. code-block:: php + + namespace Chill\PersonBundle\Security\Authorization; + + use Chill\MainBundle\Security\ProvideRoleInterface; + + class PersonVoter implements ProvideRoleInterface + { + const CREATE = 'CHILL_PERSON_CREATE'; + const UPDATE = 'CHILL_PERSON_UPDATE'; + const SEE = 'CHILL_PERSON_SEE'; + + public function getRoles() + { + return array(self::CREATE, self::UPDATE, self::SEE); + } + + public function getRolesWithoutScope() + { + return array(self::CREATE, self::UPDATE, self::SEE); + } + + } + +Implement your voter +-------------------- + +Inside this class, you might use the :class:`Chill\\MainBundle\\Security\\Authorization\\AuthorizationHelper` to check permission (do not re-invent the wheel). This is a real-world example: .. code-block:: php From 3e3ac1c841c34f462c33b3fa25c97ae488ddac06 Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Thu, 1 Oct 2015 10:35:36 +0200 Subject: [PATCH 081/157] Spelling correction --- source/bundles/custom-fields.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/bundles/custom-fields.rst b/source/bundles/custom-fields.rst index 71d3e77e4..d93e1c523 100644 --- a/source/bundles/custom-fields.rst +++ b/source/bundles/custom-fields.rst @@ -11,7 +11,7 @@ Custom fields bundle ==================== -This bundle provide the ability to add custom fields to existing entities. +This bundle provides the ability to add custom fields to existing entities. Those custom fields contains extra data and will be stored in the DB, along with other data's entities. It may be string, text, date, ... or a link to an existing or to-be-created other entities. @@ -28,7 +28,7 @@ Custom Fields concepts Custom fields are extra data which may be added to entities by user. If a developer implements custom fields on a entity, users will be able to add more fields on this entity. -Example: the `person bundle` allow to record `firstname`, `lastname`, `date of birth` fields. But users need to store information about the kind of house he has (if he owns his house, if he rents it, ...). Custom fields allow to create those fields. +Example: the `person bundle` allows to record `firstname`, `lastname`, `date of birth` fields. But users need to store information about the kind of house he has (if he owns his house, if he rents it, ...). Custom fields allows to create those fields. Automatically, those fields are added at the person form. They are also printed in the person view and in exports. @@ -43,8 +43,8 @@ Some entities needs a **default** custom fields group. For instance, the default In the future of the `person bundle`, other custom fields group will be added in forms accessible from the menu, allowing users to completely customize and separate their entities. -Allow custom fields on a entity -------------------------------- +Allow custom fields on an entity +-------------------------------- As a developer, you must allow your users to add custom fields on your entities. From 2604c618664a69e09279fae24e23bc4c3501de0a Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Thu, 1 Oct 2015 10:36:17 +0200 Subject: [PATCH 082/157] Initialization of is not necessary --- source/bundles/custom-fields.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/bundles/custom-fields.rst b/source/bundles/custom-fields.rst index d93e1c523..ff80fe09a 100644 --- a/source/bundles/custom-fields.rst +++ b/source/bundles/custom-fields.rst @@ -77,7 +77,7 @@ Create the field accordingly in the class logic : /** * @var array */ - private $customField = array(); + private $customField; /** * You must set a setter in order to save automatically custom From 3934d18df386e79052b1961189666d52a549ad93 Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Thu, 1 Oct 2015 11:35:44 +0200 Subject: [PATCH 083/157] Revisin and adding the ftwig function 'chill_custom_fields_group_widget' --- source/bundles/custom-fields.rst | 110 +++++++++++++++++++------------ 1 file changed, 67 insertions(+), 43 deletions(-) diff --git a/source/bundles/custom-fields.rst b/source/bundles/custom-fields.rst index ff80fe09a..cf6d27b61 100644 --- a/source/bundles/custom-fields.rst +++ b/source/bundles/custom-fields.rst @@ -108,13 +108,16 @@ Declare your customizable entity in configuration This step is necessary to allow user to create custom fields group associated with this entity. -Two methods are available : +Two methods are available. -* In your app/config.yml file. This is the easiest method, but discouraged because it will reduce the ease for installation. -* In your Extension class : a bit harder for devs, much easier for installers. +The recommended method is to do it in DependencyInjection/Extension class. It is recommended as your bundle will be well set up (for custom field) in every chill installation that use it. -In app/config.yml file -"""""""""""""""""""""" +The discouraged method is to declare via the app/config.yml file. This is very quick to set up for a given chill installation but it is not done automatically : in every chill installation that use your bundle, this step has to be performed. + +In app/config.yml file (discouraged) +"""""""""""""""""""""""""""""""""""" + +This method is discouraged but explained first as it helps to undersand the recommended method. Add those file under `chill_custom_fields` section : @@ -122,15 +125,15 @@ Add those file under `chill_custom_fields` section : chill_custom_fields: customizables_entities: - - { class: Chill\CustomFieldsBundle\Entity\BlopEntity, name: blop_entity } + - { class: Chill\YourBundleBundle\Entity\BlopEntity, name: blop_entity } * The `name` allow you to define a string which is translatable. This string will appears when chill's admin will add/retrieve new customFieldsGroup. * The class, which is a full FQDN class path -Automatically, in DependencyInjection/Extension class -""""""""""""""""""""""""""""""""""""""""""""""""""""" +Automatically, in DependencyInjection/Extension class (recommended) +""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -This is the preferred way for declaring customizable classes. +This is the recommended way for declaring customizable classes. You can prepend configuration of `custom fields bundle` from the class `YourBundle\DependencyInjection\YourBundleExtension`. **Note** that you also have to implements `Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface` on this class to make the `prepend` function being taken into account. @@ -138,38 +141,39 @@ Example here : .. code-block:: php - class ChillReportExtension extends Extension implements PrependExtensionInterface - { - /** - * - * - * @param ContainerBuilder $container - */ - public function prepend(ContainerBuilder $container) - { - $bundles = $container->getParameter('kernel.bundles'); - if (!isset($bundles['ChillCustomFieldsBundle'])) { - throw new MissingBundleException('ChillCustomFieldsBundle'); - } + class ChillYourBundleExtension extends Extension implements PrependExtensionInterface + { + /** + * @param ContainerBuilder $container + */ + public function prepend(ContainerBuilder $container) + { + $bundles = $container->getParameter('kernel.bundles'); + if (!isset($bundles['ChillCustomFieldsBundle'])) { + throw new MissingBundleException('ChillCustomFieldsBundle'); + } - $container->prependExtensionConfig('chill_custom_fields', - array('customizables_entities' => - array( - array( - 'class' => 'Chill\ReportBundle\Entity\Report', - 'name' => 'ReportEntity', - ) + $container->prependExtensionConfig('chill_custom_fields', + array('customizables_entities' => + array( + array( + 'class' => 'Chill\YourBundleBundle\Entity\BlopEntity', + 'name' => 'blop_entity',) ) ) - ); - } - } + ); + } + } + +* The `name` allow you to define a string which is translatable. This string will appears when chill's admin will add/retrieve new customFieldsGroup. +* The class, which is a full FQDN class path .. seealso:: `How to simplify configuration of multiple bundles `_ A cookbook page about prepending configuration. + Adding options to your custom fields groups ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -182,8 +186,8 @@ In `config.yml` the declaration should be : chill_custom_fields: customizables_entities: - - class: Chill\ReportBundle\Entity\Report - name: ReportEntity + class: Chill\YourBundleBundle\Entity\BlopEntity + name: BlopEntity options: # this will create a "myFieldKey" field as text, with a maxlength attribute to 150 (see http://symfony.com/doc/master/reference/forms/types/text.html) myFieldKey: {form_type: text, form_options: {attr: [maxlength: 150]}} @@ -192,11 +196,9 @@ In the `PrependExtensionInterface::prepend` function, the options key will be ad .. code-block:: php - class ChillReportExtension extends Extension implements PrependExtensionInterface + class ChillYourBundleExtension extends Extension implements PrependExtensionInterface { /** - * - * * @param ContainerBuilder $container */ public function prepend(ContainerBuilder $container) @@ -210,8 +212,8 @@ In the `PrependExtensionInterface::prepend` function, the options key will be ad array('customizables_entities' => array( array( - 'class' => 'Chill\ReportBundle\Entity\Report', - 'name' => 'ReportEntity', + 'class' => 'Chill\YourBundleBundle\Entity\BlopEntity', + 'name' => 'BlopEntity', 'options' => array( 'myFieldKey' => [ 'form_type' => 'text', 'form_options' => [ 'attr' => [ 'maxlength' => 150 ] ] )) @@ -263,14 +265,21 @@ In the `PrependExtensionInterface::prepend` function, the options key will be ad Note that `custom_fields_group_linked_custom_fields` does not create any input on `CustomFieldsGroup` creation : there aren't any fields associated with the custom fields just after the group creation... You have to add custom fields and associate them with the newly created group to see them appears. -Rendering custom fields in a template -------------------------------------- +Rendering custom fields and custom fields group in a template +------------------------------------------------------------- -Two function are available : +.. warning:: + Each custom field can be `active` or not. Only `active` custom fields has to be dislayed. + +For rendering custom fields, two function are available : * `chill_custom_field_widget` to render the widget. This function is defined on a customFieldType basis. * `chill_custom_field_label` to render the label. You can customize the label rendering by choosing the layout you would like to use. +For rendering custom fields group, a function is available : + +* `chill_custom_fields_group_widget to render the widget. It will display the custom fields of the group in a dd / dt structure. + **chill_custom_field_label** The signature is : @@ -292,8 +301,10 @@ Examples **chill_custom_field_widget** +The signature is : + * array **$fields** the array raw, as stored in the db -* CustomField|object|string $customFieldOrClass either a customField OR a customizable_entity OR the FQDN of the entity +* CustomField|object|string **$customFieldOrClass** either a customField OR a customizable_entity OR the FQDN of the entity * string **$slug** only necessary if the first argument is NOT a CustomField instance Examples: @@ -311,6 +322,19 @@ Examples: This feature is not fully tested. See `the corresponding issue `_ +**chill_custom_fields_group_widget** + +This function only display custom fields that are `active. + +The signature is : + +* array **$fields** the array raw, as stored in the db +* CustomFieldsGroup **$customFieldsGroup** the custom field group to render + +.. code-block:: jinja + + {{ chill_custom_fields_group_widget(entity.cFData, entity.customFieldsGroup) }} + Custom Fields's form -------------------- From a88e8cb6f7812688bc65a9ccb1c8788e3d49ec2b Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Fri, 9 Oct 2015 14:48:32 +0200 Subject: [PATCH 084/157] By convention store custom fields data in the parameter cFData -- close #639 --- source/bundles/custom-fields.rst | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/source/bundles/custom-fields.rst b/source/bundles/custom-fields.rst index cf6d27b61..fede84048 100644 --- a/source/bundles/custom-fields.rst +++ b/source/bundles/custom-fields.rst @@ -48,6 +48,10 @@ Allow custom fields on an entity As a developer, you must allow your users to add custom fields on your entities. +.. warning:: + For having custom fields, the class of the entity must contain a variable for storing the custom data. **By convention this variable must be called $cFData** + + Create a json field on your entity ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -59,7 +63,7 @@ Declare a json field in your database : type: entity # ... fields: - customField: + cFData: type: json_array Create the field accordingly in the class logic : @@ -77,18 +81,18 @@ Create the field accordingly in the class logic : /** * @var array */ - private $customField; + private $cFData; /** * You must set a setter in order to save automatically custom * fields from forms, using Form Component * - * @param array $customField + * @param array $cFData * @return BlopEntity */ - public function setCustomField(array $customField) + public function setCFData(array $cFData) { - $this->customField = $customField; + $this->cFData = $cFData; return $this; } @@ -98,9 +102,9 @@ Create the field accordingly in the class logic : * * @return array */ - public function getCustomField() + public function getCFData() { - return $this->customField; + return $this->cFData; } Declare your customizable entity in configuration From 06c15fdc34fcfe5324aede36e423694386383487 Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Fri, 9 Oct 2015 15:17:37 +0200 Subject: [PATCH 085/157] Adding info for macos & docker --- .../very_quick_start_with_docker.rst | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/source/installation/very_quick_start_with_docker.rst b/source/installation/very_quick_start_with_docker.rst index 158b2896b..c9c76aad1 100644 --- a/source/installation/very_quick_start_with_docker.rst +++ b/source/installation/very_quick_start_with_docker.rst @@ -20,8 +20,32 @@ We have created a `docker container `_ and login with the created users, like `center a_social` (the complete list is below). Password is always 'password'. +You can then browse on `http://localhost:8989 `_ and login with the created users, like `center a_social` (the complete list is below). Password is always 'password'. For Mac OS X, replace `localhost` by the IP of the docker VM (`boot2docker ip` or `docker-machine ip default`). Stopping the containers ======================= From 53683cfd5f6c124682f55640b2952c44fdd1d940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 12 Oct 2015 22:00:06 +0200 Subject: [PATCH 086/157] add button messages to users refs #633 --- source/development/index.rst | 2 +- .../{flashbags.rst => messages-to-users.rst} | 40 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) rename source/development/{flashbags.rst => messages-to-users.rst} (51%) diff --git a/source/development/index.rst b/source/development/index.rst index 0a9f0b069..165395fdf 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -19,7 +19,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Routing Menus Access control model - Message to users + Messages to users Localisation Database migrations Searching diff --git a/source/development/flashbags.rst b/source/development/messages-to-users.rst similarity index 51% rename from source/development/flashbags.rst rename to source/development/messages-to-users.rst index d6334a7a3..2eec9d6d8 100644 --- a/source/development/flashbags.rst +++ b/source/development/messages-to-users.rst @@ -6,10 +6,14 @@ A copy of the license is included in the section entitled "GNU Free Documentation License". +Messages to users +****************** + + .. _flashbags : -Flashbags: message to users -**************************** +Flashbags +========== The four following levels are defined : @@ -33,3 +37,35 @@ The four following levels are defined : `Flash Messages on Symfony documentation `_ Learn how to use flash messages in controller. + + +Buttons +======== + +Four actions are available to decorate `a` links and `buttons`. + +To add the action on button, use them as class along with `sc-button` : + +.. code-block:: html + + Create an entity + + + ++-----------+--------------+------------------------------------------------------------------------------+ +| Action | Class | Description | ++===========+==============+==============================================================================+ +| Submit | `bt-submit` | Submit a form. | ++-----------+--------------+------------------------------------------------------------------------------+ +| Create | `bt-create` | Link to a form to create an entity | ++-----------+--------------+------------------------------------------------------------------------------+ +| Reset | `bt-reset` | Reset a form | ++-----------+--------------+------------------------------------------------------------------------------+ +| Delete | `bt-delete` | Link to a form to delete an entity | ++-----------+--------------+------------------------------------------------------------------------------+ +| Edit | `bt-edit` | Link to a form to edit an entity | ++-----------+--------------+------------------------------------------------------------------------------+ +| Update | `bt-update` | Submitting this form will update the entity | ++-----------+--------------+------------------------------------------------------------------------------+ +| Action | `bt-action` | Generic link to an action | ++-----------+--------------+------------------------------------------------------------------------------+ From 7a603f19bdb808d6c8a158cb9897faf279817720 Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Fri, 30 Oct 2015 18:26:47 +0100 Subject: [PATCH 087/157] Changing issue tracker in README : git.framasoft.org --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cd5f9f39a..ba0a4f1c4 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ To compile this documentation : Contribute =========== -Issue tracker : https://redmine.champs-libres.coop/projects/chill/issues (certificate is located here : http://www.champs-libres.coop/ca.pem) +Issue tracker : https://git.framasoft.org/groups/Chill-project/issues Licence ======= From 74a18166efcf49bd450d5045664eddc7e25a0b78 Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Fri, 30 Oct 2015 18:27:31 +0100 Subject: [PATCH 088/157] Adding a page about the usage of the layouts / templates --- source/development/index.rst | 1 + source/development/layout-template-usage.rst | 125 +++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 source/development/layout-template-usage.rst diff --git a/source/development/index.rst b/source/development/index.rst index 0a9f0b069..4f2a7b977 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -18,6 +18,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Instructions to create a new bundle Routing Menus + Layout / Template usage Access control model Message to users Localisation diff --git a/source/development/layout-template-usage.rst b/source/development/layout-template-usage.rst new file mode 100644 index 000000000..409e2b1a2 --- /dev/null +++ b/source/development/layout-template-usage.rst @@ -0,0 +1,125 @@ +.. Copyright (C) 2015 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + + +Layout / Template usage +####################### + +We recommand the use of the existing layouts to ensure the consistency of the design. This section explains the different templates and how to use it. + +The layouts are twig templates. + +Organisation of the layouts +=========================== + +ChillMainBundle::layout.html.twig +--------------------------------- + +This is the base layout. It includes the most import css / js files. It display a page with + +* a horizontal navigation menu +* a place for content +* a footer + + +The layout containts blocks, that are : + +* title + + * to display title + +* css + + * where to add some custom css + +* navigation_section_menu + + * place where to insert the section menu in the navigation menu (by default the navigation menu is inserted) + +* navigation_search_bar + + * place where to insert a search bar in the navigation menu (by default the search bar is inserted) + +* top_banner + + * place where to display a banner below the navigation menu (this place is use to display the details of the person) + +* sublayout_containter + + * place between the header and the footer that can be used to create a new layout (with vertical menu for example) + +* content + + * place where to display the content (flash message are included outside of this block) + +* js + + * where to add some custom javascript + + +ChillMainBundle::layoutWithVerticalMenu.html.twig +------------------------------------------------- + +This layout extends `ChillMainBundle::layout.html.twig`. It replaces the block `layout_content` and divides this block for displaying a vertical menu and some content. + +It proposes 2 new blocks : + +* layout_wvm_content + + * where to display the page content + +* vertical_menu_content + + * where to place the vertical menu + + +ChillMainBundle::Admin/layout.html.twig +--------------------------------------- + +This layout extends `ChillMainBundle::layout.html.twig`. It hides the search bar, remplaces the `section menu` with the `admin section menu`. + +It proposes a new block : + +* admin_content + + * where to display the admin content + + +ChillMainBundle::Admin/layoutWithVerticalMenu.html.twig +------------------------------------------------------- + +This layout extends `ChillMainBundle::layoutWithVerticalMenu.html.twig`. It do the same changes than `ChillMainBundle::Admin/layout.html.twig` : hiding the search bar, remplacing the `section menu` with the `admin section menu`. + +It proposes a new block : + +* admin_content + + * where to display the admin content + +ChillPersonBundle::layout.html.twig +----------------------------------- + +This layout extend `ChillMainBundle::layoutWithVerticalMenu.html.twig` add the person details in the block `top_banner`, set the menu `person` as the vertical menu. + +It proposes 1 new block : + +* personcontent + + * where to display the information of the person + + +ChillMainBundle::Export/layout.html.twig +---------------------------------------- + +This layout extends `ChillMainBundle::layoutWithVerticalMenu.html.twig` and set the menu `export` as the vertical menu. + +It proposes 1 new block : + +* export_content + + * where to display the content of the export \ No newline at end of file From 4b0622f54ce74f5f08ac82c133fbc1805f2f7224 Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Fri, 30 Oct 2015 18:28:10 +0100 Subject: [PATCH 089/157] Adding information about getting the last version of the docker images --- source/installation/very_quick_start_with_docker.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/source/installation/very_quick_start_with_docker.rst b/source/installation/very_quick_start_with_docker.rst index c9c76aad1..43c21ee87 100644 --- a/source/installation/very_quick_start_with_docker.rst +++ b/source/installation/very_quick_start_with_docker.rst @@ -42,6 +42,15 @@ Configuration of `docker-machine` $ docker-machine start default $ eval "$(docker-machine env default)" + + +Getting the last version of the docker images +---------------------------------------------- + +.. code-block:: bash + + docker pull chill/database + docker pull chill/demo-flavor Prepare a database From d667c10d049f9b9b8334436ee674fce3cb26f950 Mon Sep 17 00:00:00 2001 From: Jean Pierre Huart Date: Wed, 25 Nov 2015 18:39:27 +0100 Subject: [PATCH 090/157] Installation of Postgressql server and Chill database --- .../installation/install_postgres_server.rst | 58 +++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/source/installation/install_postgres_server.rst b/source/installation/install_postgres_server.rst index 37b9820ea..014c68c77 100644 --- a/source/installation/install_postgres_server.rst +++ b/source/installation/install_postgres_server.rst @@ -11,16 +11,64 @@ Install PostgresSql server ########################## -.. todo:: +On a linux environment, installing Postgresql server is very easy. +Here follows the instructions for a debian based distribution (as Ubuntu). - the section "Install PostresSql database" must be written. Help appreciated :-) - +.. code-block:: bash + + sudo apt-get update + sudo apt-get install postgresql + +Having a look at the install messages, you will guess quickly which is the version installed. +Anyway you can check this with the following code: + +.. code-block:: bash + + psql --version + >>> psql (PostgreSQL) 9.4.5 + +To be able to add the unaccent extension to your database, +you will have to install the following package where x.x are the first 2 parts of the version, +9.4 in this example. + +.. code-block:: bash + + sudo apt-get install postgresql-contrib-x.x + +You are ready to play with Postgressql. + .. note:: To avoid installation and configuration of a postgresql server, you may use `our docker image `_ to start and configure a database as decribed in the basic installation chapter. This solution can be used also in a production environment. + -.. note:: +Install PostresSql database +########################### - Installing unaccent extension on ther server is possible with the package `postgresql-contrib-9.x` (replace 9.x with your server version). The extension may be enabled with running `CREATE EXTENSION unaccent;` in the database, with a superuser account. +Here follows as an example the instructions that has been used on Ubuntu 14.04 distribution to install the Chill database. +Feel free to customize it following your preferences, but do not forget to enable the `unaccent` extension on your database. + +.. code-block:: bash + + sudo su + su postgres + psql + CREATE USER chill_user WITH PASSWORD 'my_terrible_secret' CREATEDB; + CREATE DATABASE chill_db OWNER chill_user LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8'; + \q + psql -d chill_db + CREATE EXTENSION unaccent; + \q + exit + exit + +When you will use composer to install Chill, you will have to provide some database information. +If you follow this tutorial these will be: + - database_host: localhost + - database_port: 5432 + - database_name: chill_db + - database_user: chill_user + - database_password: my_terrible_secret + - locale: en \ No newline at end of file From 9fa208b93ade5d1f9d17dfe02244d8e7c7e2d0cd Mon Sep 17 00:00:00 2001 From: Jean Pierre Huart Date: Wed, 25 Nov 2015 18:48:19 +0100 Subject: [PATCH 091/157] gitignore modified --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index b5a3c2cc4..d3ea379cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +_exts build/* !build/git.txt *~ +.project From 2c08bf2c6cc4a8f957f1c4b56ff0d1c4d8b04420 Mon Sep 17 00:00:00 2001 From: Jean Pierre Huart Date: Tue, 8 Dec 2015 17:07:21 +0100 Subject: [PATCH 092/157] Doc to install as a developper --- source/development/installation.rst | 32 ++++++-- source/development/installation.rst~ | 110 --------------------------- 2 files changed, 25 insertions(+), 117 deletions(-) delete mode 100644 source/development/installation.rst~ diff --git a/source/development/installation.rst b/source/development/installation.rst index 3a137724a..fedba9f42 100644 --- a/source/development/installation.rst +++ b/source/development/installation.rst @@ -11,30 +11,35 @@ Installation for development **************************** -Installation for development should let you able to have an access to the source code, upload the code to our CVS (i.e. `git`_), and working with `composer`_. +Installation for development should allow: + + - access to the source code, + - upload the code to our CVS (i.e. `git`_) and + - work with `composer`_. As Chill is divided into bundles (the Symfony name for 'modules'), each bundle has his own repository. Installation and big picture ---------------------------- -At first, you should install Chill as described in the :ref:`basic-installation` section. +First, you should install Chill as described in the :ref:`basic-installation` section. Two things must be modified : -At first, You should add the `--prefer-source` argument when you create project. +First, add the `--prefer-source` argument when you create project. .. code-block:: bash composer create-project chill-project/standard path/to/your/directory --stability=dev --prefer-source -Secondly, if composer ask you the following question : :: +Second, when composer ask you the following question : :: Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? **You should answer `n` (no).** -This will install a project. All downloaded bundles will be stored in the `/vendor` directories. In those directories, you will have access to the git commands. +Once Chill is installed, all the downloaded bundles will be stored in the `/vendor` directories. +In those directories, you will have access to the git commands. .. code-block:: bash @@ -80,9 +85,10 @@ Working with your own fork ^^^^^^^^^^^^^^^^^^^^^^^^^^ Ideally, you will work on a fork of the main github repository. -To ensure that composer will download the code from **your** repository, you will have to adapt the `composer.json` file accordingly, using your own repository. +To ensure that composer will download the code from **your** repository, you will have to adapt the `composer.json` file accordingly, using your own repositories. -Add the following lines to your composer.json file if you want to force composer to download from your own repository: +For each Chill module that you have forked, add an indexed array into the "repositories" key of the composer.json file +at the root of the chill installation directory if you want to force composer to download from your own forked repositories: .. code-block:: json @@ -90,9 +96,21 @@ Add the following lines to your composer.json file if you want to force composer { "type": "git", "url": "git://github.com/your-github-username/ChillMain.git" + }, + { + "type": "git", + "url": "git://github.com/your-github-username/Chill-Person.git" } ] + +Then run composer update to load your forked code. +If it does not happen, delete the content of the chill/vendor/chill-project/my_forked_bundle and relaunch composer update and the code will be downloaded from your fork. + +.. code-block:: bash + + composer update + You may also `use aliases `_ to define versions. .. _editing-code-and-commiting : diff --git a/source/development/installation.rst~ b/source/development/installation.rst~ deleted file mode 100644 index b7633a751..000000000 --- a/source/development/installation.rst~ +++ /dev/null @@ -1,110 +0,0 @@ -.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS - Permission is granted to copy, distribute and/or modify this document - under the terms of the GNU Free Documentation License, Version 1.3 - or any later version published by the Free Software Foundation; - with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. - A copy of the license is included in the section entitled "GNU - Free Documentation License". - -.. _installation-for-development : - -Installation for development -**************************** - -Installation for development should let you able to have an access to the source code, upload the code to our CVS (i.e. `git`_), and working with `composer`_. - -As Chill is divided into modules, each module has his own repository. - -Installation and big picture ------------------------------ - -At first, you should install Chill as described in the :ref:`basic-installation` section. - -Two things must be modified : - -At first, You should add the `--prefer-source` argument when you create project. - -.. code-block:: bash - - php composer.phar create-project champs-libres/chill-standard \ - path/to/your/directory --stability=dev --prefer-source - -Secondly, if composer ask you the following question : :: - - Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? - -**You should answer `n` (no).** - -This will install a project. All downloaded modules will be stored in the `/vendor` directories. In those directories, you will have access to the git commands. - -.. code-block:: bash - - $ cd vendor/champs-libres/chill-main/ - $ git remote -v - composer git://github.com/Champs-Libres/ChillMain.git (fetch) - composer git://github.com/Champs-Libres/ChillMain.git (push) - origin git://github.com/Champs-Libres/ChillMain.git (fetch) - origin git@github.com:Champs-Libres/ChillMain.git (push) - -Working with your own fork -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. warning - - This section has not been tested yet. Feedback wanted. - -Ideally, you will work on a fork of the main github repository. To ensure that composer will download the code from **your** repository, you will have to adapt the `composer.json` file accordingly, using your own repository. - -Add the following lines to your composer.json file if you want to force composer to download from your own repository. This will force to use your own repository for the ChillMain bundle, insert in `composer.json` the following lines : - -.. code-block:: json - - "repositories": [ - { - "type": "git", - "url": "git://github.com/your-github-username/ChillMain.git" - } - ] - -You may also `use aliases `_ to define versions. - -.. _editing-code-and-commiting : - -Editing the code and commiting ------------------------------- - -You may edit code in the `vendor/path/to/the/bundle` directory. - -After your edits, you should commit as usually : - -.. code-block:: bash - - $ cd vendor/path/to/bundle - $ git status - Sur la branche master - Votre branche est à jour avec 'origin/master'. - - rien à valider, la copie de travail est propre - -.. warning - - The git command must be run from you vendor bundle's path (`vendor/path/to/bundle`). - -Tips -^^^^ - -The command `composer status` (`see composer documentation `_) will give you and idea of which bundle has been edited : - -.. code-block:: bash - - $ cd ./../../ #back to the root project directory - $ composer status - You have changes in the following dependencies: - /path/to/your/project/install/vendor/champs-libres/chill-main - Use --verbose (-v) to see modified files - - - - -.. _git: http://git-scm.org -.. _composer: https://getcomposer.org From 61052639a8ad7dfd394d3eae89cf022cdec2ef8b Mon Sep 17 00:00:00 2001 From: Jean Pierre Huart Date: Mon, 14 Dec 2015 14:31:19 +0100 Subject: [PATCH 093/157] Changes done to the postgresql install --- _exts/sphinx-php | 1 - .../installation/install_postgres_server.rst | 30 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) delete mode 160000 _exts/sphinx-php diff --git a/_exts/sphinx-php b/_exts/sphinx-php deleted file mode 160000 index 52f7bd221..000000000 --- a/_exts/sphinx-php +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 52f7bd2216cc22ef52494f346c5643bb2a74513f diff --git a/source/installation/install_postgres_server.rst b/source/installation/install_postgres_server.rst index 014c68c77..3db13dde1 100644 --- a/source/installation/install_postgres_server.rst +++ b/source/installation/install_postgres_server.rst @@ -12,12 +12,14 @@ Install PostgresSql server ########################## On a linux environment, installing Postgresql server is very easy. -Here follows the instructions for a debian based distribution (as Ubuntu). +Here follows the instructions for a debian based distribution (as Ubuntu) using the distribution repositories. +To have the latest version follow the instructions of the `Postgresql wiki`_. .. code-block:: bash sudo apt-get update - sudo apt-get install postgresql + sudo apt-get install postgresql + Having a look at the install messages, you will guess quickly which is the version installed. Anyway you can check this with the following code: @@ -50,18 +52,13 @@ Here follows as an example the instructions that has been used on Ubuntu 14.04 d Feel free to customize it following your preferences, but do not forget to enable the `unaccent` extension on your database. .. code-block:: bash - - sudo su - su postgres - psql - CREATE USER chill_user WITH PASSWORD 'my_terrible_secret' CREATEDB; - CREATE DATABASE chill_db OWNER chill_user LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8'; - \q - psql -d chill_db - CREATE EXTENSION unaccent; - \q - exit - exit + + sudo su + su postgres + # At the prompt of the following instruction, I have typed 'my_terrible_secret' as password + createuser --pwprompt chill_user + createdb -O chill_user chill_db + psql -d chill_db -c "CREATE EXTENSION unaccent;" When you will use composer to install Chill, you will have to provide some database information. If you follow this tutorial these will be: @@ -71,4 +68,7 @@ If you follow this tutorial these will be: - database_name: chill_db - database_user: chill_user - database_password: my_terrible_secret - - locale: en \ No newline at end of file + - locale: en + + + .. _Postgresql wiki: https://wiki.postgresql.org/wiki/Apt \ No newline at end of file From d23046edff6e60bd9f73872a823ac17008eb6973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 23 Dec 2015 20:43:30 +0100 Subject: [PATCH 094/157] fix link to documentation --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index ba0a4f1c4..7217acd28 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ Chill documentation Documentation of the Chill software. -This may be see here : https://chill.readthedocs.org - +The compiled documentation may be read at http://docs.chill.social Compilation into HTML ===================== From 38885e8a0d7e692ae41a7e77927d5d91e459a55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 23 Dec 2015 21:23:03 +0100 Subject: [PATCH 095/157] add doc for `show_emtpy_values_in_views` ref Chill-project/Chill-CustomFields#18 --- source/bundles/custom-fields.rst | 35 ++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/source/bundles/custom-fields.rst b/source/bundles/custom-fields.rst index fede84048..508b90f6f 100644 --- a/source/bundles/custom-fields.rst +++ b/source/bundles/custom-fields.rst @@ -13,7 +13,7 @@ Custom fields bundle This bundle provides the ability to add custom fields to existing entities. -Those custom fields contains extra data and will be stored in the DB, along with other data's entities. It may be string, text, date, ... or a link to an existing or to-be-created other entities. +Those custom fields contains extra data and will be stored in the DB, along with other data's entities. It may be string, text, date, ... or a link to an existing entities. In the database, custom fields are stored in json format. @@ -22,7 +22,12 @@ In the database, custom fields are stored in json format. The full specification discussed `here. `_ `JSON Type on postgresql documentation `_ + The documentation of json type, which is used to store data in the database. +.. contents:: Table of contents + :depth: 4 + :local: + Custom Fields concepts ---------------------- @@ -282,7 +287,7 @@ For rendering custom fields, two function are available : For rendering custom fields group, a function is available : -* `chill_custom_fields_group_widget to render the widget. It will display the custom fields of the group in a dd / dt structure. +* `chill_custom_fields_group_widget` to render the widget. It will display the custom fields of the group in a dd / dt structure. **chill_custom_field_label** @@ -328,7 +333,7 @@ Examples: **chill_custom_fields_group_widget** -This function only display custom fields that are `active. +This function only display custom fields that are `active`. The signature is : @@ -404,19 +409,27 @@ Example : } +Available configuration +------------------------ +Those options are available in the configuration, under the `chill_custom_field` key. -Development tips ----------------- +Example : -If you want to test the rendering of a custom fields group, you may use this method : +.. code-block:: yaml -1. Run the built-in server **from the custom-fields directory** : + chill_custom_field: + show_empty_values_in_views: false -.. code-block:: bash +show_empty_values_in_views *boolean*: + Allow to hide / show empty values in views. The aim of this configuration parameter is to hide (or show) empty values when :term:`custom fields group` are rendered. - ./run-server.sh + Default value : `true` -2. assuming that your custom fields id is `1`, go to your navigator and enter url : `http://localhost:8000/customfieldsgroup/test/render/1` +Glossary +-------- - +.. glossary:: + + custom fields group + A group of custom fields From b595efa0c904310f83619428ae02f20c5710e0c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 23 Dec 2015 21:48:57 +0100 Subject: [PATCH 096/157] add doc for `birthdate_not_after` configuration option --- source/bundles/person.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/source/bundles/person.rst b/source/bundles/person.rst index fde93b5f6..0d53f3108 100644 --- a/source/bundles/person.rst +++ b/source/bundles/person.rst @@ -50,3 +50,23 @@ Default ======= The default search is performed on firstname and/or lastname. Both are concatened before search. If values are separated by spaces, the clause `AND` is used : the search `dep ge` will match 'Gérard Depardieu` or 'Jean Depagelles', but not 'Charline Depardieu' (missing 'Ge' in word). + +Configuration options +********************* + +Those options are available under `chill_person` key. + +Example of configuration: + +.. code-block:: yaml + + chill_person: + validation: + birthdate_not_after: P15Y + +birthdate_not_after *string* + The period duration before today during which encoding birthdate is not possible. The period is a string matching the format of `ISO_8601`, which is also use to build `DateInterval classes `_. + + Example: `P1D`, `P18Y` + + Default value: `P1D` which means that birthdate before the current day (= yesterday) are allowed. From e64e1e0fb133b6807a9a036c340cd73bc6bf83d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 29 Dec 2015 16:39:12 +0100 Subject: [PATCH 097/157] add doc for `chill_custom_fields_is_empty` --- source/bundles/custom-fields.rst | 34 +++++++++++++++++++------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/source/bundles/custom-fields.rst b/source/bundles/custom-fields.rst index 508b90f6f..f56b0dc23 100644 --- a/source/bundles/custom-fields.rst +++ b/source/bundles/custom-fields.rst @@ -284,17 +284,18 @@ For rendering custom fields, two function are available : * `chill_custom_field_widget` to render the widget. This function is defined on a customFieldType basis. * `chill_custom_field_label` to render the label. You can customize the label rendering by choosing the layout you would like to use. +* `chill_custom_field_is_empty` indicates if the content of the custom fields is empty (return a boolean) For rendering custom fields group, a function is available : * `chill_custom_fields_group_widget` to render the widget. It will display the custom fields of the group in a dd / dt structure. -**chill_custom_field_label** +chill_custom_field_label +^^^^^^^^^^^^^^^^^^^^^^^^ The signature is : -* `CustomField|object|string` **$customFieldOrClass** either a customField OR a customizable_entity OR the FQDN of the entity -* `string` **$slug** only necessary if the first argument is NOT a CustomField instance +* `CustomField` **$customField** a customField instance * `array` **params** the parameters for rendering. Currently, 'label_layout' allow to choose a different label. Default is 'ChillCustomFieldsBundle:CustomField:render_label.html.twig' Examples @@ -303,18 +304,16 @@ Examples {{ chill_custom_field_label(customField) }} - {{ chill_custom_field_label(entity, 'slug') }} - - {{ chill_custom_field_label('Path\To\Entity', 'slug') }} -**chill_custom_field_widget** +chill_custom_field_widget +^^^^^^^^^^^^^^^^^^^^^^^^^ The signature is : * array **$fields** the array raw, as stored in the db -* CustomField|object|string **$customFieldOrClass** either a customField OR a customizable_entity OR the FQDN of the entity -* string **$slug** only necessary if the first argument is NOT a CustomField instance +* CustomField **$customField** a customField instance +* string **$documentType** the type of document. Default to `html`. Examples: @@ -322,16 +321,23 @@ Examples: {{ chill_custom_field_widget(entity.customFields, customField) }} - {{ chill_custom_field_widget(entity.customFields, entity, 'slug') }} +chill_custom_field_is_empty +^^^^^^^^^^^^^^^^^^^^^^^^^^^ - {{ chill_custom_field_widget(fields, 'Path\To\Entity', 'slug') }} +The signature is : -.. warning:: +* array **$fields** the array raw, as stored in the db +* CustomField **$customField** a customField instance - This feature is not fully tested. See `the corresponding issue `_ +Examples : + +.. code-block:: jinja + + {%- if chill_custom_field_is_empty(cFData, customField) == false -%} -**chill_custom_fields_group_widget** +chill_custom_fields_group_widget +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This function only display custom fields that are `active`. From 14e913cd7fa4c6e4179ca6e779f68cdecd404521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 9 Feb 2016 12:59:02 +0100 Subject: [PATCH 098/157] upgrade installation guide to 1.0 --- source/installation/installation.rst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 4bba8f457..11570eb7a 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -151,10 +151,8 @@ Create your Chill project using composer: .. code-block:: bash - composer create-project chill-project/standard path/to/your/directory --stability=dev - -.. note:: - Until now, the stability of the project is set to "dev". Do not forget this argument, or composer will fail to download and create the project. + composer create-project chill-project/standard path/to/your/directory ~1.0 + Composer will download `the standard architecture`_ and ask you a few question about how to configure your project. @@ -224,7 +222,7 @@ Populate your database with basic information Once your database schema is ready, if you want to test the application you have the opportunity to populate your database with some basic data. Those are provided through a script and might depends from the bundle you choose to install (see :ref:`install-additional-bundles`). -This script has not to be launched for a production server and will erase any existing data. It is meant only for testing the application. +**This script has not to be launched for a production server** and will erase any existing data. It is meant only for testing the application. .. code-block:: bash From 9267518909b901e6f6948fbf31f16b26dd2dbe4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 9 Feb 2016 13:17:20 +0100 Subject: [PATCH 099/157] add documentation about fields visibility in person --- source/bundles/person.rst | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/source/bundles/person.rst b/source/bundles/person.rst index 0d53f3108..0c31bdcd0 100644 --- a/source/bundles/person.rst +++ b/source/bundles/person.rst @@ -63,6 +63,15 @@ Example of configuration: chill_person: validation: birthdate_not_after: P15Y + person_fields: + # note: visible is the default config. This key may be omitted if visible is chosen. + nationality: hidden + email: hidden + place_of_birth: visible + phonenumber: hidden + country_of_birth: hidden + marital_status: visible + spoken_languages: hidden birthdate_not_after *string* The period duration before today during which encoding birthdate is not possible. The period is a string matching the format of `ISO_8601`, which is also use to build `DateInterval classes `_. @@ -70,3 +79,34 @@ birthdate_not_after *string* Example: `P1D`, `P18Y` Default value: `P1D` which means that birthdate before the current day (= yesterday) are allowed. + +person_fields *array* + This define the visibility of some fields. By default, all fields are visible, but you can choose to hide some of them. Available keys are : + + * `nationality` + * `country_of_birth` + * `place_of_birth` + * `phonenumber` + * `email` + * `marital_status` + * `spoken_languages` + + Possibles values: `hidden` or `visible` (all other value will raise an Exception). + + Default value : `visible`, which means that all fields are visible. + + Example: + + .. code-block:: yaml + + chill_person: + person_fields: + nationality: hidden + email: hidden + phonenumber: hidden + +.. note:: + If all the field of a "box" are hidden, the whole box does not appears. Example: if the fields `phonenumber` and `email` are hidden, the title `Contact information` will be hidden in the UI. + +.. note:: + If you hide multiple fields, for a better integration you may want to override the template, for a better appeareance. See `the symfony documentation `_ about this feature. From 3b4e770130202d5a4d6553e74265c41eedd07fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 9 Feb 2016 13:39:08 +0100 Subject: [PATCH 100/157] improve homepage and update links --- source/index.rst | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/source/index.rst b/source/index.rst index 20825f23d..73fc40f34 100644 --- a/source/index.rst +++ b/source/index.rst @@ -32,11 +32,27 @@ Contribute ========== -* `Issue tracker `_ (the certificate is available `here `_) -* source code is available here : +* `Issue tracker `_ You may want to dispatch the issue in the multiple projects. If you do not know in which project is located your bug / feature request, use the project Chill-Main. - * Chill-standard : https://github.com/Champs-Libres/chill-standard - * Chill-main : https://github.com/Champs-Libres/ChillMain +Source code is dispatched in multiple bundle, to improve re-usability. Each bundle has dependencies with other chill bundle, but the developer take care that bundles may not be installed if the user do not need it. + +Available bundles +------------------- + +* Chill-standard | https://git.framasoft.org/Chill-project/Chill-Standard This is the skeleton of the project. It does contains only few code, but information about configuration of your instance ; +* Chill-Main : https://git.framasoft.org/Chill-project/Chill-Main : the main, required bundle for all the subsequent chill bundles. It contains the framework to add features (like searching, timeline, ...). It also provides the user managements (authentification and authorization) ; +* Chill-Person : https://git.framasoft.org/Chill-project/Chill-Person This is the bundle which provides the possibility to create and add a person. +* Chill-CustomFields : https://git.framasoft.org/Chill-project/Chill-CustomFields This bundle allows you to create custom fields on other bundles. It provides the framework for this features, and modify the schema according to this. It is required by Chill-Person and Chill-Report. +* Chill-Report: https://git.framasoft.org/Chill-project/Chill-Report This bundle allow to add report about People recorded in your database ; +* Chill-Activity : https://git.framasoft.org/Chill-project/Chill-Activity This bundle allow to add activities about People recorded in your database ; +* Chill-ICPC2 : https://git.framasoft.org/Chill-project/Chill-ICPC2 This bundle provides a custom fields for `ICPC code `_ (international classification for primary care) + +You will also found the following projects : + +* The present documentation : https://git.framasoft.org/Chill-project/chill-documentation +* The website https://chill.social : https://git.framasoft.org/Chill-project/chill.social + +And various project to build docker containers with Chill. TODO in documentation ===================== From 786c3e6184c14d8c4fadc42b2f453a4a009b8be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 9 Feb 2016 21:34:23 +0100 Subject: [PATCH 101/157] [wip] first documentation for exports --- source/development/exports.rst | 140 +++++++++++++++++++++++++++++++++ source/development/index.rst | 1 + 2 files changed, 141 insertions(+) create mode 100644 source/development/exports.rst diff --git a/source/development/exports.rst b/source/development/exports.rst new file mode 100644 index 000000000..22cf426ab --- /dev/null +++ b/source/development/exports.rst @@ -0,0 +1,140 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + + +Exports +******* + +Export is an important issue for the Chill software : users should be able to : + +- compute statistics about their activity ; +- list "things" which make part of their activities. + +The `main bundle`_ provides a powerful framework to build custom queries with re-usable parts across differents bundles. + +.. contents:: Table of content + :local: + +.. seealso:: + + `The issue where this framework was discussed `_ + Provides some information about the pursued features and architecture. + +Concepts +======== + + +Some vocabulary: 3 "Export elements" +------------------------------------ + +Three terms are used for this framework : + +exports + provides some basic operation on the date. Two kind of exports are available : + + - computed data : it may be "the number of people", "the number of activities", "the duration of activities", ... + - list data : it may be "the list of people", "the list of activity", ... + +filters + The filters make a filter on the date: it removes some information the user doesn't want to introduce in the computation done by export. In other word, filters make a filter... + + Example of filter: "people under 18 years olds", "activities between the 1st of June and the 31st December", ... + +aggregators + The aggregator aggregates the data into some group (some software use the term 'bucket'). + + Example of aggregator : "group people by gender", "group people by nationality", "group activity by type", ... + + +Anatomy of an export +--------------------- + +An export may be explained as a sentence, where each part of this sentence refers to one or multiple exports element. Examples : + +**Example 1**: Count the number of people having at least one activity in the last 12 month, and group them by nationality and gender + +Here : + +- *count the number of people* is the export part +- *having at least one activity* is the filter part +- *group them by nationality* is the aggregator part +- *group them by gender* is a second aggregator part + +Note that : + +- aggregators, filters and exports are cross-bundle. Here the bundle activity provides a filter which apply on an export provided by the person bundle ; +- there may exists multiple aggregator or filter for one export. Currently, only one export is allowed. + +The result might be : + ++-----------------------+----------------+---------------------------+ +| Nationality | Gender | Number of people | ++=======================+================+===========================+ +| Russian | Male | 12 | ++-----------------------+----------------+---------------------------+ +| Russian | Female | 24 | ++-----------------------+----------------+---------------------------+ +| France | Male | 110 | ++-----------------------+----------------+---------------------------+ +| France | Female | 150 | ++-----------------------+----------------+---------------------------+ + +**Example 2**: Count the average duration of an activity with type "meeting", which occurs between the 1st of June and the 31st of December, and group them by week. + +Here : + +- *count the average duration of an activity* is the export part +- *activity with type meeting* is a filter part +- *activity which occurs between the 1st of June and the 31st of December* is a filter +- *group them by week* is the aggregator part + +The result might be : + ++-----------------------+----------------------+ +| Week | Number of activities | ++=======================+======================+ +| 2015-10 | 10 | ++-----------------------+----------------------+ +| 2015-11 | 12 | ++-----------------------+----------------------+ +| 2015-12 | 10 | ++-----------------------+----------------------+ +| 2015-13 | 9 | ++-----------------------+----------------------+ + +Authorization and exports +------------------------- + +Exports, filters and aggregators should not make see data the user is not allowed to see. + +In other words, developers are required to take care of user authorization for each export. + +It should exists a special role that should be granted to users which are allowed to build exports. For more simplicity, this role should apply on center, and should not requires special circles. + +How the magic works +=================== + +To build an export, we rely on the capacity of the database to execute queries with aggregate (i.e. GROUP BY) and filter (i.e. WHERE) instructions. + +An export is an SQL query which is initiated by an export, and modified by aggregators and filters. + +.. note:: + + **Example 1**: Count the number of people having at least one activity in the last 12 month, and group them by nationality and gender + + 1. The report initiate the query :code:`SELECT count(people.*) FROM people` + 2. The filter add a where and join clause : :code:`SELECT count(people.*) FROM people RIGHT JOIN activity WHERE activity.date IS BETWEEN now AND 6 month ago` + 3. The aggregator "nationality" add a GROUP BY clause and a column in the SELECT statement: :code:`SELECT people.nationality, count(people.*) FROM people RIGHT JOIN activity WHERE activity.date IS BETWEEN now AND 6 month ago GROUP BY nationality` + 4. The aggregator "gender" do the same job as the nationality aggregator : it adds a GROUP BY clause and a column in the SELECT statement : :code:`SELECT people.nationality, people.gender, count(people.*) FROM people RIGHT JOIN activity WHERE activity.date IS BETWEEN now AND 6 month ago GROUP BY nationality, gender` + + +.. todo:: + + Continue to explain the export framework + +.. _main bundle: https://git.framasoft.org/Chill-project/Chill-Main diff --git a/source/development/index.rst b/source/development/index.rst index b3e59b134..b480425fe 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -25,6 +25,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Database migrations Searching Timelines + Exports Testing manual/index.rst From 901fa399e9a59be4bb14f650f8ffeb4ad444ac72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 22 Feb 2016 21:08:47 +0100 Subject: [PATCH 102/157] add doc for UI : macros and CSS classes --- source/bundles/activity.rst | 46 +++++++++++++ source/bundles/index.rst | 1 + source/bundles/person.rst | 24 +++++++ source/conf.py | 7 +- source/development/index.rst | 10 ++- .../user-interface/css-classes.rst | 64 +++++++++++++++++++ .../layout-template-usage.rst | 0 7 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 source/bundles/activity.rst create mode 100644 source/development/user-interface/css-classes.rst rename source/development/{ => user-interface}/layout-template-usage.rst (100%) diff --git a/source/bundles/activity.rst b/source/bundles/activity.rst new file mode 100644 index 000000000..65dce71b7 --- /dev/null +++ b/source/bundles/activity.rst @@ -0,0 +1,46 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _activity-bundle: + +Activity bundle +############### + +This bundle provides the ability to record people in the software. This bundle is required by other bundle. + +.. contents:: Table of content + :local: + +Entities provided +***************** + +.. todo:: + + Describe the entities provided. + +Macros +****** + +Activity reason sticker +======================= + +Macro file + `ChillActivityBundle:ActivityReason:macro.html.twig` +Macro envelope + :code:`reason(r)` + + :code:`p` is an instance of :class:`Chill\ActivityBundle\Entity\ActivityReason` + +When to use this macro ? + When you want to represent an activity reason. +Example usage : + .. code-block:: html+jinja + + {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} + + {{ m.reason(r) }} diff --git a/source/bundles/index.rst b/source/bundles/index.rst index 2d05286c7..41b1b5fe5 100644 --- a/source/bundles/index.rst +++ b/source/bundles/index.rst @@ -18,6 +18,7 @@ You will find here documentation about bundles working with Chill. Custom Fields bundle Person bundle Report bundle + Activity bundle Your bundle here ? ------------------- diff --git a/source/bundles/person.rst b/source/bundles/person.rst index 0c31bdcd0..cdead037f 100644 --- a/source/bundles/person.rst +++ b/source/bundles/person.rst @@ -110,3 +110,27 @@ person_fields *array* .. note:: If you hide multiple fields, for a better integration you may want to override the template, for a better appeareance. See `the symfony documentation `_ about this feature. + +Macros +****** + +Sticker for a person +===================== + +Macro file + `ChillPersonBundle:Person:macro.html.twig` +Macro envelope + :code:`render(p, withLink=false)` + + :code:`p` is an instance of :class:`Chill\PersonBundle\Entity\Person` + + :code:`withLink` :class:`boolean` +When to use this macro ? + When you want to represent a person. +Example usage : + .. code-block:: html+jinja + + {% import "ChillPersonBundle:Person:macro.html.twig" as person_ %} + + {{ person_.render(person, true) }} + diff --git a/source/conf.py b/source/conf.py index 51e4e1e77..c101487f8 100644 --- a/source/conf.py +++ b/source/conf.py @@ -97,7 +97,7 @@ master_doc = 'index' # General information about the project. project = 'Chill Documentation' -copyright = '2014, Champs-Libres, published under the terms of the GNU Free Documentation License, Version 1.3' +copyright = '2014-2016, Champs-Libres, published under the terms of the GNU Free Documentation License, Version 1.3' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -106,7 +106,7 @@ copyright = '2014, Champs-Libres, published under the terms of the GNU Free Docu # The short X.Y version. version = '1.0' # The full version, including alpha/beta/rc tags. -release = '1.0.0ALPHA' +release = '1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -255,6 +255,7 @@ htmlhelp_basename = 'chill-docdoc' latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', +'papersize': 'a4paper' # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', @@ -335,7 +336,7 @@ texinfo_documents = [ epub_title = 'Chill Documentation' epub_author = 'Champs-Libres' epub_publisher = 'Champs-Libres' -epub_copyright = '2014, Champs-Libres' +epub_copyright = '2014-2016, Champs-Libres' # The basename for the epub file. It defaults to the project name. #epub_basename = 'chill-doc' diff --git a/source/development/index.rst b/source/development/index.rst index b480425fe..1f9e45c96 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -18,7 +18,6 @@ As Chill rely on the `symfony `_ framework, reading the fram Instructions to create a new bundle Routing Menus - Layout / Template usage Access control model Messages to users Localisation @@ -29,6 +28,15 @@ As Chill rely on the `symfony `_ framework, reading the fram Testing manual/index.rst +Layout and UI +************** + +.. toctree:: + :maxdepth: 2 + + Layout / Template usage + Classes and mixins + Help, I am lost ! ***************** diff --git a/source/development/user-interface/css-classes.rst b/source/development/user-interface/css-classes.rst new file mode 100644 index 000000000..a2effc2e2 --- /dev/null +++ b/source/development/user-interface/css-classes.rst @@ -0,0 +1,64 @@ +.. Copyright (C) 2016 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + + +CSS classes and mixins +###################### + +The stylesheet are based on the framework `ScratchCSS `_. + +We added some useful classes and mixins for the Chill usage. + +CSS Classes +************ + + +Statement "empty data" +====================== + +CSS Selector + :code:`.chill-no-data-statement` +In which case will you use this selector ? + When a list is empty, and a message fill the list to inform that the data is empty +Example usage + .. code-block:: html+jinja + + {{ 'No reason associated'|trans }} + + +Quotation of user text +======================= + +CSS Selector + :code:`blockquote.chill-user-quote` +In which case will you use this selector ? + When you quote text that were filled by the user in a form. +Example usage + .. code-block:: html+jinja + +
    {{ entity.remark|nl2br }}
    + + + +Mixins +****** + + +Entity decorator +================= + +Mixin + :code:`@mixin entity($background-color, $color: white)` +In which case including this mixin ? + When you create a `sticker`, a sort of label to represent a text in a way that the user can associate immediatly with a certain type of class / entity. +Example usage + .. code-block:: sass + + span.entity.entity-activity.activity-reason { + @include entity($chill-pink, white); + } diff --git a/source/development/layout-template-usage.rst b/source/development/user-interface/layout-template-usage.rst similarity index 100% rename from source/development/layout-template-usage.rst rename to source/development/user-interface/layout-template-usage.rst From ce6481b9f40ca465496649b394f01070b0ffa68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 14 Mar 2016 23:02:15 +0100 Subject: [PATCH 103/157] add doc for showing/hiding address in person details --- source/bundles/person.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/bundles/person.rst b/source/bundles/person.rst index cdead037f..155c91728 100644 --- a/source/bundles/person.rst +++ b/source/bundles/person.rst @@ -72,6 +72,7 @@ Example of configuration: country_of_birth: hidden marital_status: visible spoken_languages: hidden + address: visible birthdate_not_after *string* The period duration before today during which encoding birthdate is not possible. The period is a string matching the format of `ISO_8601`, which is also use to build `DateInterval classes `_. @@ -90,6 +91,7 @@ person_fields *array* * `email` * `marital_status` * `spoken_languages` + * `address` Possibles values: `hidden` or `visible` (all other value will raise an Exception). From cbfeb4c04db1da1903f845e896c5eab8a23838f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 14 Mar 2016 23:08:57 +0100 Subject: [PATCH 104/157] documenting the activity configuration options --- source/bundles/activity.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/source/bundles/activity.rst b/source/bundles/activity.rst index 65dce71b7..df88df674 100644 --- a/source/bundles/activity.rst +++ b/source/bundles/activity.rst @@ -23,6 +23,29 @@ Entities provided Describe the entities provided. + +Configuration options +********************* + +Those options are available under `chill_activity` key. + +Example of configuration: + +.. code-block:: yaml + + chill_activity: + form: + time_duration: + - { label: '12 minutes', seconds: 720 } + - { label: '30 minutes', seconds: 1800 } + +form.time_duration *array* + The duration which might be suggested when the user create or update an activity. The value must be an array of object, where each object must have a :code:`label` and a :code:`seconds` key. The label provide which is shown to user (the label will be translated, if possible) and the seconds the duration. + + Example: see the example above + + Default value: the values available are 5, 10, 15, 20, 25, 30, 45 minutes, and 1 hour, 1 hour 15, 1 hour 30, 1 hour 45 and 2 hours. + Macros ****** From 4743e8b3b82281da9aa53cfe4c6ac0b93d12169e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 14 Mar 2016 23:32:12 +0100 Subject: [PATCH 105/157] documenting group bundle + macros in main --- source/bundles/group.rst | 47 +++++++++++++++++ source/bundles/index.rst | 1 + source/bundles/main.rst | 24 +++++++++ .../bundles/group/group_classes_uml.png | Bin 0 -> 51066 bytes .../static/bundles/group/group_classes_uml.pu | 49 ++++++++++++++++++ 5 files changed, 121 insertions(+) create mode 100644 source/bundles/group.rst create mode 100644 source/static/bundles/group/group_classes_uml.png create mode 100644 source/static/bundles/group/group_classes_uml.pu diff --git a/source/bundles/group.rst b/source/bundles/group.rst new file mode 100644 index 000000000..6258d7a4b --- /dev/null +++ b/source/bundles/group.rst @@ -0,0 +1,47 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _group-bundle: + +Group bundle +############ + +Allow to group people in a group. This group may be a family, an activity group, ... + +.. contents:: Table of content + :local: + +Entities +******** + +.. figure:: /static/bundles/group/group_classes_uml.png + + +Macros +****** + +Group sticker +============== + +Macro file + `ChillGroupBundle:Group:macro.html.twig` +Macro name + :code:`_render` +Macro envelope + :code:`group`, instance of :class:`Chill\GroupBundle\Entity\CGroup` + +When to use this macro ? + When you want to represent group. +Example usage : + .. code-block:: html+jinja + + {% import 'ChillGroupBundle:Group:macro.html.twig' as m %} + + {{ m._render(g) }} + + diff --git a/source/bundles/index.rst b/source/bundles/index.rst index 41b1b5fe5..517af505d 100644 --- a/source/bundles/index.rst +++ b/source/bundles/index.rst @@ -19,6 +19,7 @@ You will find here documentation about bundles working with Chill. Person bundle Report bundle Activity bundle + Group bundle Your bundle here ? ------------------- diff --git a/source/bundles/main.rst b/source/bundles/main.rst index de2b890d7..7c5c2c011 100644 --- a/source/bundles/main.rst +++ b/source/bundles/main.rst @@ -22,3 +22,27 @@ This bundle provide : .. warning:: this section is incomplete. + + +Macros +****** + +Address sticker +=============== + +Macro file + `ChillMainBundle:Address:macro.html.twig` +Macro name + :code:`_render` +Macro envelope + :code:`address`, instance of :class:`Chill\MainBundle\Entity\Address` + +When to use this macro ? + When you want to represent an address. +Example usage : + .. code-block:: html+jinja + + {% import 'ChillMainBundle:Address:macro.html.twig' as m %} + + {{ m._render(address) }} + diff --git a/source/static/bundles/group/group_classes_uml.png b/source/static/bundles/group/group_classes_uml.png new file mode 100644 index 0000000000000000000000000000000000000000..6fe4db7d8965717a472488406ce65e90520cc7f1 GIT binary patch literal 51066 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ΞFe;sU^GCziE-|*s=P9A>yr^kKGgm+#V*Z+Yw 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%0AY5$Ati0->+`r&W3x)CUmrK-;8@p{XHRq! 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 literal 0 HcmV?d00001 diff --git a/source/static/bundles/group/group_classes_uml.pu b/source/static/bundles/group/group_classes_uml.pu new file mode 100644 index 000000000..56e53c770 --- /dev/null +++ b/source/static/bundles/group/group_classes_uml.pu @@ -0,0 +1,49 @@ +# diagramme de classe du module "groupe" + +@startuml + +title Diagramme de classe du module "groupe" + +package "PersonBundle" { + class Person + class Center +} + +package "GroupBundle" { + Person "1" <-- Membership + Membership "0..*" <--> "Group" + Type "1" --> "1..*" Role + Membership -right-> "1" Role + Group -right-> "1" Type + Group -left-> "1" Center +} + +class Membership { + - role + - person + - group +} +note left: Membership relie les groupes aux\npersonnes. Chaque membership a un\n role, le rôle est à choisir\nparmi ceux possibles pour le type de groupe + +class Group { + - type + - memberships + - name + - center +} +note left: Un groupe a un type qui est défini à sa création. + +class Type { + - roles +} +note right: Les types de groupe qu'il est possible de créer \nsont définis dans l'interface d'administration.\nExemple de type: "famille", "groupe de parole", "stage", ... + +class Role { + - name +} +note right: Pour chaque Type on définit des rôles\npossibles pour ce type de groupe. Par exemple, pour le\ntype "famille" on peut avoir les rôles parents et enfants. + + + + +@enduml From 534f11b15f8556ff18f71a280c896672c426b55f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 14 Mar 2016 23:34:46 +0100 Subject: [PATCH 106/157] fixing year in license --- source/bundles/group.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/bundles/group.rst b/source/bundles/group.rst index 6258d7a4b..2c18370f3 100644 --- a/source/bundles/group.rst +++ b/source/bundles/group.rst @@ -1,4 +1,4 @@ -.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS +.. Copyright (C) 2016 Champs Libres Cooperative SCRLFS Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; From 4f1a47c9209629389d0c49791f0ef848a0e55a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 14 Mar 2016 23:59:07 +0100 Subject: [PATCH 107/157] adding doc for delegated blocks ref Chill-project/Chill-Main#12 --- source/development/index.rst | 1 + .../user-interface/delegated-blocks.rst | 92 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 source/development/user-interface/delegated-blocks.rst diff --git a/source/development/index.rst b/source/development/index.rst index 1f9e45c96..4b6afaab0 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -36,6 +36,7 @@ Layout and UI Layout / Template usage Classes and mixins + Delegated blocks Help, I am lost ! diff --git a/source/development/user-interface/delegated-blocks.rst b/source/development/user-interface/delegated-blocks.rst new file mode 100644 index 000000000..e3f4e49ee --- /dev/null +++ b/source/development/user-interface/delegated-blocks.rst @@ -0,0 +1,92 @@ +.. Copyright (C) 2016 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Delegating rendering of block to other bundles +############################################## + +Sometimes, you may want to delegate part of your layout to another bundle(s), which might, or might not, installed. + +Examples : + +- you may want to show task in the top bar, only if the bundle "task" is installed (**Note**: this bundle does not exists... yet !) +- you may want to show the group belonging (see :ref:`group-bundle`) below of the vertical menu, only if the bundle is installed. + +This is possible using `the symfony dispatcher event `_. + +Inserting a delegated block inside of a template +================================================ + +Use the twig function :code:`chill_delegated_block`. + +Example : + +.. code-block:: html+jinja + + {{ chill_delegated_block('my_block', { 'person': person }) }} + +In this example, the block name is :code:`my_block`, and the context is an array : :code:`{ 'person': person }`. + +Subscribing to a delegated block +================================= + +Create a :code:`Subscriber` or a :code:`Listener` as `described in the Symfony documentation `_. + +You should listen to the event :code:`chill_block.block_name`, where `block_name` is the name of the delegated block. For instance, in the example above, the event will be :code:`chill_block.my_block`. + +The event passed as argument will be an instance of :class:`Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent`. The context will be available as an array, as described in subscriber example. You may add content to the page using the :method:`Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent::addContent` method. + +Example : + +.. code-block:: php + + namespace Chill\GroupBundle\Events; + + use Symfony\Component\EventDispatcher\EventSubscriberInterface; + use Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent; + + + class TemplatingPostVerticalMenuEventSubscriber implements EventSubscriberInterface + { + // constructor logic will take place here in a real world + + public static function getSubscribedEvents() + { + return array('chill_block.person_post_vertical_menu' => array( + array('processRendering', 0) // you may change the priority if you want your content to be inserted upper or below of the other content. + )); + } + + // here is where we add content to the event. + public function processRendering(DelegatedBlockRenderingEvent $event) + { + // we access the person using $event['person'] + $memberships = $memberships = $this->membershipRepository + ->findBy(array('person' => $event['person'])); + + // we add content to the templating using the templating engine + $event->addContent( + $this->templating + ->render('ChillGroupBundle:Membership:short_listing.html.twig', array( + 'memberships' => $memberships, + )) + ); + } + + } + +This tag is registered as a service : + +.. code-block:: yaml + + services: + chill_group.membership_rendering_event: + class: Chill\GroupBundle\Events\TemplatingPostVerticalMenuEventSubscriber + tags: + - { name: kernel.event_subscriber } + +You should have a look at the documentation of the bundle to know which delegated block are available and what is their context. From 2b0bb364f564b6224f90e759ba29dfe307b33d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 15 Mar 2016 00:09:05 +0100 Subject: [PATCH 108/157] documenting chill_block.person_post_vertical_menu event/delegated block --- source/bundles/person.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/source/bundles/person.rst b/source/bundles/person.rst index 155c91728..c06c868ec 100644 --- a/source/bundles/person.rst +++ b/source/bundles/person.rst @@ -135,4 +135,18 @@ Example usage : {% import "ChillPersonBundle:Person:macro.html.twig" as person_ %} {{ person_.render(person, true) }} + +Layout events and delegated blocks +*********************************** + +:code:`chill_block.person_post_vertical_menu` event +==================================================== + +This event is available to add content below of the vertical menu (on the right). + +The context is : + +- :code:`person` : the current person which is rendered. Instance of :class:`Chill\PersonBundle\Entity\Person` + + From 8eacdbc5fa664d38ece6b6a2b1146944717f0405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 15 Mar 2016 10:21:11 +0100 Subject: [PATCH 109/157] fix grammar + improve comprehension --- .../development/user-interface/delegated-blocks.rst | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/source/development/user-interface/delegated-blocks.rst b/source/development/user-interface/delegated-blocks.rst index e3f4e49ee..74dd97dc2 100644 --- a/source/development/user-interface/delegated-blocks.rst +++ b/source/development/user-interface/delegated-blocks.rst @@ -18,8 +18,8 @@ Examples : This is possible using `the symfony dispatcher event `_. -Inserting a delegated block inside of a template -================================================ +Inserting a delegated block inside a template +============================================== Use the twig function :code:`chill_delegated_block`. @@ -27,10 +27,12 @@ Example : .. code-block:: html+jinja - {{ chill_delegated_block('my_block', { 'person': person }) }} +
    {{ chill_delegated_block('my_block', { 'person': person }) }}
    In this example, the block name is :code:`my_block`, and the context is an array : :code:`{ 'person': person }`. +The :code:`div` will be filled with the html produced by the bundles which suscribed to the event :code:`chill_block.my_block`. + Subscribing to a delegated block ================================= @@ -40,6 +42,10 @@ You should listen to the event :code:`chill_block.block_name`, where `block_name The event passed as argument will be an instance of :class:`Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent`. The context will be available as an array, as described in subscriber example. You may add content to the page using the :method:`Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent::addContent` method. +.. warning:: + + The code inserted by the function :code:`chill_delegated_block` **should be html safe**. You are encouraged to use an instance of the templating engine (aka Twig) to produce clean html. + Example : .. code-block:: php From 94f05ea5f4a43998f0029b760489c497f81e286e Mon Sep 17 00:00:00 2001 From: Marc Ducobu Date: Tue, 22 Mar 2016 15:36:07 +0100 Subject: [PATCH 110/157] Creation of a page for the event bundle --- source/bundles/event.rst | 29 +++++++++++++++++++++++++++++ source/bundles/index.rst | 1 + 2 files changed, 30 insertions(+) create mode 100644 source/bundles/event.rst diff --git a/source/bundles/event.rst b/source/bundles/event.rst new file mode 100644 index 000000000..b902ee444 --- /dev/null +++ b/source/bundles/event.rst @@ -0,0 +1,29 @@ +.. Copyright (C) 2016 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _event-bundle: + +Event bundle +############ + +Template & Menu +=============== + +The event bundle has a special template with a specific menu for actions on +events. This menu is called `event`. + +ChillEventBundle::layout.html.twig +---------------------------------- + +This layout extends `ChillMainBundle::layoutWithVerticalMenu.html.twig` and add the menu `event` + +It proposes a new block : + +* event_content + + * where to display content relative to the event. diff --git a/source/bundles/index.rst b/source/bundles/index.rst index 517af505d..c83d96ab5 100644 --- a/source/bundles/index.rst +++ b/source/bundles/index.rst @@ -20,6 +20,7 @@ You will find here documentation about bundles working with Chill. Report bundle Activity bundle Group bundle + Event bundle Your bundle here ? ------------------- From 4d166498e52d1ea49d5898f63b6972f2b087655d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 1 Jun 2016 22:43:49 +0200 Subject: [PATCH 111/157] adding a link to macros --- source/bundles/activity.rst | 2 ++ source/bundles/group.rst | 2 ++ source/bundles/main.rst | 1 + source/bundles/person.rst | 2 ++ .../user-interface/layout-template-usage.rst | 18 +++++++++++++++++- 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/source/bundles/activity.rst b/source/bundles/activity.rst index df88df674..011983e0a 100644 --- a/source/bundles/activity.rst +++ b/source/bundles/activity.rst @@ -46,6 +46,8 @@ form.time_duration *array* Default value: the values available are 5, 10, 15, 20, 25, 30, 45 minutes, and 1 hour, 1 hour 15, 1 hour 30, 1 hour 45 and 2 hours. +.. _activity-bundle-macros: + Macros ****** diff --git a/source/bundles/group.rst b/source/bundles/group.rst index 2c18370f3..37ac8db39 100644 --- a/source/bundles/group.rst +++ b/source/bundles/group.rst @@ -22,6 +22,8 @@ Entities .. figure:: /static/bundles/group/group_classes_uml.png +.. _group-bundle-macros: + Macros ****** diff --git a/source/bundles/main.rst b/source/bundles/main.rst index 7c5c2c011..da195c98e 100644 --- a/source/bundles/main.rst +++ b/source/bundles/main.rst @@ -23,6 +23,7 @@ This bundle provide : this section is incomplete. +.. _main-bundle-macros: Macros ****** diff --git a/source/bundles/person.rst b/source/bundles/person.rst index c06c868ec..b56834689 100644 --- a/source/bundles/person.rst +++ b/source/bundles/person.rst @@ -113,6 +113,8 @@ person_fields *array* .. note:: If you hide multiple fields, for a better integration you may want to override the template, for a better appeareance. See `the symfony documentation `_ about this feature. +.. _person-bundle-macros: + Macros ****** diff --git a/source/development/user-interface/layout-template-usage.rst b/source/development/user-interface/layout-template-usage.rst index 409e2b1a2..4939e8f01 100644 --- a/source/development/user-interface/layout-template-usage.rst +++ b/source/development/user-interface/layout-template-usage.rst @@ -122,4 +122,20 @@ It proposes 1 new block : * export_content - * where to display the content of the export \ No newline at end of file + * where to display the content of the export + +Useful template and helpers +=========================== + +Macros +------ + +Every bundle may bring their own macro to print resources with uniformized styles. + +See : + +- :ref:`Macros in person bundle ` ; +- :ref:`Macros in activity bundle ` ; +- :ref:`Macros in group bundle ` ; +- :ref:`Macros in main bundle ` ; + From 20845bd7378adee1de921cbb443b4a9177634db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 1 Jun 2016 22:55:19 +0200 Subject: [PATCH 112/157] adding confirmation_template.html.twig --- .../user-interface/layout-template-usage.rst | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/source/development/user-interface/layout-template-usage.rst b/source/development/user-interface/layout-template-usage.rst index 4939e8f01..e67221e75 100644 --- a/source/development/user-interface/layout-template-usage.rst +++ b/source/development/user-interface/layout-template-usage.rst @@ -139,3 +139,39 @@ See : - :ref:`Macros in group bundle ` ; - :ref:`Macros in main bundle ` ; +Templates +--------- + +ChillMainBundle::Util:confirmation_template.html.twig +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This template show a confirmation template before making dangerous things. You can add your own message and title, or define those message by yourself in another template. + +The accepted parameters are : + +- `title` (string) a title for the page. Not mandatory (it won't be rendered if not defined) +- `confirm_question` (string) a confirmation question. This question will not be translated into the template, and may be printed as raw. Not mandatory (it won't be rendered if not defined) +- `form` : (:class:`Symfony\Component\Form\FormView`) a form wich **must** contains an input named `submit`, which must be a :class:`Symfony\Component\Form\Extension\Core\Type\SubmitType`. Mandatory +- `cancel_route` : (string) the name of a route if the user want to cancel the action +- `cancel_parameters` (array) the parameters for the route defined in `cancel_route` + + +Usage : + + +.. code-block:: html+jinja2 + + {{ include('ChillMainBundle:Util:confirmation_template.html.twig', + { + # a title, not mandatory + 'title' : 'Remove membership'|trans, + # a confirmation question, not mandatory + 'confirm_question' : 'Are you sure you want to remove membership ?'|trans + # a route for "cancel" button (mandatory) + 'cancel_route' : 'chill_group_membership_by_person', + # the parameters for 'cancel' route (default to {} ) + 'cancel_parameters' : { 'person_id' : membership.person.id }, + # the form which will send the deletion. This form + # **must** contains a SubmitType + 'form' : form + } ) }} From 05a404d5b41d446406ba7d48de675753195aa985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 1 Jun 2016 23:20:22 +0200 Subject: [PATCH 113/157] add information about logging --- source/development/index.rst | 1 + source/development/logging.rst | 50 +++++++++++++++++++ source/installation/index.rst | 2 +- .../install_production_webserver.rst | 12 ++++- 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 source/development/logging.rst diff --git a/source/development/index.rst b/source/development/index.rst index 4b6afaab0..f28ed424a 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -21,6 +21,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Access control model Messages to users Localisation + Logging Database migrations Searching Timelines diff --git a/source/development/logging.rst b/source/development/logging.rst new file mode 100644 index 000000000..5cace02c7 --- /dev/null +++ b/source/development/logging.rst @@ -0,0 +1,50 @@ +.. Copyright (C) 2016 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + + +Logging +******* + +.. seealso:: + + Symfony documentation: `How to user Monolog to write logs `_ + The symfony cookbook page about logging. + + +A channel for custom logging has been created to store sensitive data. + +The channel is named ``chill``. + +The installer of chill should be aware that this channel may contains sensitive data and encrypted during backup. + +Logging to channel `chill` +============================ + +You should use the service named ``chill.main.logger``, as this : + +.. code-block:: php + + $logger = $this->get('chill.main.logger'); + +You should store data into context, not in the log himself, which should remains the same for the action. + +Example of usage : + +.. code-block:: php + + $logger->info("An action has been performed about a person", array( + 'person_lastname' => $person->getLastName(), + 'person_firstname' => $person->getFirstName(), + 'person_id' => $person->getId(), + 'by_user' => $user->getUsername() + )); + +For further processing, it is a good idea to separate all fields (like firstname, lastname, ...) into different context keys. + +By convention, you should store the username of the user performing the action under the ``by_user`` key. + diff --git a/source/installation/index.rst b/source/installation/index.rst index d869f22c8..3d168fb80 100644 --- a/source/installation/index.rst +++ b/source/installation/index.rst @@ -28,7 +28,7 @@ Usage in production .. toctree:: :maxdepth: 2 - How to install the software + Installation in production Update Chill and maintenance ============================== diff --git a/source/installation/install_production_webserver.rst b/source/installation/install_production_webserver.rst index c7ada3700..dd2ff20cc 100644 --- a/source/installation/install_production_webserver.rst +++ b/source/installation/install_production_webserver.rst @@ -14,4 +14,14 @@ Install production webserver .. todo:: the section "Install production webserver" must be written. Help appreciated :-) - \ No newline at end of file + +.. warning:: + + Some sensitive data (like the person data, ...) might be logged in a special channel, called ``chill``. + + This channel will log events like items removed by a user, what where the details of this item, who removed it, ... + + You should take care of encrypting or discarding those data if required. + + For an how-to of how to encrypt those data, you may consult `the appropriate section of the symfony documentation `_ + From 372dbc94e33e6260f90166ba8ef20788df0871d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 8 Jul 2016 09:06:45 +0200 Subject: [PATCH 114/157] adding ldap bundle to doc --- source/bundles/index.rst | 1 + source/bundles/ldap.rst | 204 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 source/bundles/ldap.rst diff --git a/source/bundles/index.rst b/source/bundles/index.rst index c83d96ab5..2c4f3f17e 100644 --- a/source/bundles/index.rst +++ b/source/bundles/index.rst @@ -21,6 +21,7 @@ You will find here documentation about bundles working with Chill. Activity bundle Group bundle Event bundle + Ldap bundle (synchronisation between ldap and database) Your bundle here ? ------------------- diff --git a/source/bundles/ldap.rst b/source/bundles/ldap.rst new file mode 100644 index 000000000..ccea046a8 --- /dev/null +++ b/source/bundles/ldap.rst @@ -0,0 +1,204 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _ldap-bundle: + +LDAP bundle +########### + +This bundle binds the database with an ldap directory. + +The bundle synchronize the ldap directory with users in the database. It also +provides a way to check user credentials against the ldap directory. + + +.. contents:: Table of content + :local: + +Current limitations +******************* + +- The length of the ldap dn must be < 255 characters +- if the username extracted from the ldap is updated, the changes are not reflected in the database and remains the same + + +Entities provided +***************** + +This bundle provides only one entity : :class:`Chill\\LdapBundle\\Entity\\UserLdapBinding` + +How the synchronizer works ? +**************************** + +#. The synchronizer performs a query on :code:`dn` and :code:`query` defined in the :ref:`configuration `. +#. For each entry returned by the query, it looks if the :code:`dn` exists in the database + + #. If the entry does not exists : + + #. the synchronizer looks for user with same username as defined by :code:`username_attr`, and bind it with the :code:`dn` if it exists. + #. else, a user is created with username defined by :code:`username_attr` (if the ldap contains more than one attribute, the first attribute returned is used) + + #. if a user exists which is already binded with the :code:`dn`, the entry is ignored. + +#. The synchronizer looks for dn existing in database and which were not returned by the query performed in 1. + + #. If they exists, those user are set to :code:`enabled=false`: they are not allowed to login. + +Installation +************ + +This bundle requires : + +- PHP LDAP ext +- :code:`symfony/ldap` with minimal version 3.1. Note that, currently, Chill uses Symfony 2.8: you should add the dependency on this single package manually + +In your composer.json, for stable version : + +.. code-block:: json + + "require": { + // .. other dependencies + "symfony/ldap" : "~3.1", + "chill-project/ldap": "~1.0" + } + + + +And for dev version : + +.. code-block:: json + + "require": { + // .. other dependencies + "symfony/ldap" : "~3.1", + "chill-project/ldap": "dev-master@dev" + } + + +.. _configuration: + +Configuration +************** + +Configuration of the bundle +============================ + +.. code-block:: yaml + + # Default configuration for extension with alias: "chill_ldap" + chill_ldap: + server: # Required + + # the host of the ldap directory + host: ~ # Required, Example: localhost + + # the port to reach the ldap directory + port: 389 + + # the version of the ldap directory + version: 3 + + # Is the use of ssl required to establish connection + use_ssl: false + + # Is the use of startssl required to establish connection + use_starttls: false + + # the user to bind to dn directory. Required for searching existing users. + bind_dn: ~ # Required, Example: cn=user,dn=chill,dn=social + + # the user's password to bind to dn directory. + bind_password: ~ # Required, Example: paSSw0rD + user_query: # Required + + # The DN where the query is executed + dn: ~ # Example: ou=People,dc=champs-libres,dc=coop + + # The query which will allow to retrieve users + query: ~ # Example: (&(objectClass=inetOrgPerson)(userPassword=*)) + + # The attribute which will provide username (=login) + username_attr: cn + + +Example : + +.. code-block:: yaml + + chill_ldap: + server: + # host, bind_dn and bind_password are imported from parameters.yml + host: "%ldap_host%" + bind_dn: "%ldap_bind_dn%" + bind_password: "%ldap_bind_password%" + user_query: + dn: dc=champs-libres,dc=coop + query: "(&(objectClass=inetOrgPerson)(userPassword=*))" + + +Configuration of the security part of chill +============================================ + +Simply add the following config in the firewall of the security bundle : +`chill_ldap_form_login: ~`. This config is located in `app/config/security.yml` + +Example of a configuration : + +.. code-block:: yaml + + # in app/config/security.yml + + firewalls: + dev: + pattern: ^/(_(profiler|wdt)|css|images|js)/ + security: false + + default: + anonymous: ~ + # enable the login check by a form, agaisnt the database + form_login: + csrf_parameter: _csrf_token + csrf_token_id: authenticate + csrf_provider: form.csrf_provider + # enable the login check by a form, against the ldap + chill_ldap_form_login: ~ # this is the line you should add + + +Note that, if you enable the login check by form **and** by the ldap, +the password will be checked against the database **and** against the ldap. +If one of them match, the login will succeed. + +If you want to completely disable login check against the database, +simply remove the :code:`form_login` entry and all his options. + +.. _command-and-crontab: + +Command and crontab +=================== + +Synchronize the database : + +.. code-block:: bash + + php app/console chill:ldap:synchronize + + +For getting more debug message : + +.. code-block:: bash + + php app/console chill:ldap:synchronize -vvv + + + +You should run this command regularly (using crontab or +`systemd timer `_) +to synchronize ldap and database automatically. + + + From 1a98980ad0fe6c41c581d9248bfda84bbdfb65b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 8 Jul 2016 09:12:13 +0200 Subject: [PATCH 115/157] include installation.rst in a toctree --- source/installation/index.rst | 8 ++++++++ source/installation/installation.rst | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/source/installation/index.rst b/source/installation/index.rst index 3d168fb80..efb83d83b 100644 --- a/source/installation/index.rst +++ b/source/installation/index.rst @@ -14,6 +14,14 @@ Installation ############################### +Canonical installation +====================== + +.. toctree:: + :maxdepth: 1 + + Common installation + Very quick install with docker ================================= diff --git a/source/installation/installation.rst b/source/installation/installation.rst index 11570eb7a..63bc8b0c5 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation.rst @@ -6,8 +6,10 @@ A copy of the license is included in the section entitled "GNU Free Documentation License". -Installation -############ +.. _install: + +Install chill +############# .. _basic-installation: From 6a57cd8388deafcf08f969d554fd82f874d3da86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 8 Jul 2016 09:21:35 +0200 Subject: [PATCH 116/157] adding link to dev mailing list and manual, register bundles --- source/index.rst | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/source/index.rst b/source/index.rst index 73fc40f34..dfe5f2a39 100644 --- a/source/index.rst +++ b/source/index.rst @@ -27,17 +27,34 @@ Contents of this documentation: development/index.rst Bundles +Let's talk together ! +====================== + +Subscribe to the dev mailing-list to discuss your project and extend Chill with the feature you need! + +- `The dev mailing-list `_ +- `Read the archives of the mailing-list `_ + + Contribute ========== * `Issue tracker `_ You may want to dispatch the issue in the multiple projects. If you do not know in which project is located your bug / feature request, use the project Chill-Main. +* `The dev mailing-list `_ Source code is dispatched in multiple bundle, to improve re-usability. Each bundle has dependencies with other chill bundle, but the developer take care that bundles may not be installed if the user do not need it. +User manual +=========== + +An user manual exists in French and currently focuses on describing the main concept of the software. + +`Read (and contribute) to the manual `_ + Available bundles -------------------- +================= * Chill-standard | https://git.framasoft.org/Chill-project/Chill-Standard This is the skeleton of the project. It does contains only few code, but information about configuration of your instance ; * Chill-Main : https://git.framasoft.org/Chill-project/Chill-Main : the main, required bundle for all the subsequent chill bundles. It contains the framework to add features (like searching, timeline, ...). It also provides the user managements (authentification and authorization) ; @@ -46,6 +63,10 @@ Available bundles * Chill-Report: https://git.framasoft.org/Chill-project/Chill-Report This bundle allow to add report about People recorded in your database ; * Chill-Activity : https://git.framasoft.org/Chill-project/Chill-Activity This bundle allow to add activities about People recorded in your database ; * Chill-ICPC2 : https://git.framasoft.org/Chill-project/Chill-ICPC2 This bundle provides a custom fields for `ICPC code `_ (international classification for primary care) +* Chill-Group: https://git.framasoft.org/Chill-project/Chill-Group This bundle provides a way to create link between accompanyed people +* Chill-Event: https://git.framasoft.org/Chill-project/Chill-Event This bundle provides a way to create event and associate people to event through a "participation" +* Chill-Ldap: https://git.framasoft.org/Chill-project/Chill-Ldap Allow to synchronize the database with a ldap directory. +* Chill-ONEStat : https://framagit.org/Chill-project/Chill-ONEStat Provide statistics for the Belgian one "Centre de vacances" and "Ecoles de devoir". You will also found the following projects : From 53a6ca3d6b8402fcc0e5465903e05a84896c3f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 5 Sep 2016 23:16:42 +0200 Subject: [PATCH 117/157] adding doc about pagination --- source/development/index.rst | 1 + source/development/pagination.rst | 189 ++++++++++++++++++++ source/development/pagination/example.php | 42 +++++ source/static/puml/pagination-sequence.png | Bin 0 -> 15076 bytes source/static/puml/pagination-sequence.puml | 20 +++ 5 files changed, 252 insertions(+) create mode 100644 source/development/pagination.rst create mode 100644 source/development/pagination/example.php create mode 100644 source/static/puml/pagination-sequence.png create mode 100644 source/static/puml/pagination-sequence.puml diff --git a/source/development/index.rst b/source/development/index.rst index f28ed424a..043b32920 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -20,6 +20,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Menus Access control model Messages to users + Pagination Localisation Logging Database migrations diff --git a/source/development/pagination.rst b/source/development/pagination.rst new file mode 100644 index 000000000..52d0d81f1 --- /dev/null +++ b/source/development/pagination.rst @@ -0,0 +1,189 @@ +.. Copyright (C) 2016 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + + +Pagination +########## + +The Bundle :code:`Chill\MainBundle` provides a **Pagination** api which allow you to easily divide results list on different pages. + +A simple example +**************** + +In the controller, get the :class:`Chill\Main\Pagination\PaginatorFactory` from the `Container` and use this :code:`PaginatorFactory` to create a :code:`Paginator` instance. + + +.. literalinclude:: pagination/example.php + :language: php + + +Then, render the pagination using the dedicated twig function. + +.. code-block:: html+jinja2 + + {% extends "ChillPersonBundle::layout.html.twig" %} + + {% block title 'Item list'|trans %} + + {% block personcontent %} + + + + {# ... your items here... #} + +
    + + {% if items|length > paginator.getTotalItems %} + {{ chill_pagination(paginator) }} + {% endif %} + + +The function :code:`chill_pagination` will, by default, render a link to the 10 previous page (if they exists) and the 10 next pages (if they exists). Assuming that we are on page 5, the function will render a list to :: + + Previous 1 2 3 4 **5** 6 7 8 9 10 11 12 13 14 Next + +Understanding the magic +======================= + +Where does the :code:`$paginator` get the page number ? +------------------------------------------------------- + +Internally, the :code:`$paginator` object has a link to the :code:`Request` object, and it reads the :code:`page` parameter which contains the current page number. If this parameter is not present, the :code:`$paginator` assumes that we are on page 1. + +.. figure:: /static/puml/pagination-sequence.png + + The :code:`$paginator` get the current page from the request. + +Where does the :code:`$paginator` get the number of items per page ? +-------------------------------------------------------------------- + +As above, the :code:`$paginator` can get the number of items per page from the :code:`Request`. If none is provided, this is given by the configuration which is, by default, 50 items per page. + +:code:`PaginatorFactory`, :code:`Paginator` and :code:`Page` +************************************************************ + +:code:`PaginatorFactory` +======================== + +The :code:`PaginatorFactory` may create more than one :code:`Paginator` in a single action. Those :code:`Paginator` instance may redirect to different routes and/or routes parameters. + +.. code-block:: php + + // create a paginator for the route 'my_route' with some parameters (arg1 and arg2) + $paginatorMyRoute = $paginatorFactory->create($total, 'my_route', array('arg1' => 'foo', 'arg2' => $bar); + +Those parameters will override the current parameters. + +The :code:`PaginatorFactory` has also some useful shortcuts : + +.. code-block:: php + + // get current page number + $paginatorFactory->getCurrentPageNumber( ) + // get the number of items per page **for the current request** + $paginatorFactory->getCurrentItemsPerPage( ) + // get the number of the first item **for the current page** + $paginatorFactory->getCurrentPageFirstItemNumber( ) + + +Working with :code:`Paginator` and :code:`Page` +=============================================== + +The paginator has some function to give the number of pages are required to displayed all the results, and give some information about the number of items per page : + +.. code-block:: php + + // how many page count this paginator ? + $paginator->countPages(); // return 20 in our example + + // we may get the number of items per page + $paginator->getItemsPerPage(); // return 20 in our example + +A :code:`Paginator` instance create instance of :code:`Page`, each :code:`Page`, which is responsible for generating the URL to the page number it represents. Here are some possibilities using :code:`Page` and :code:`Paginator` : + +.. code-block:: php + + // get the current page + $page = $paginator->getCurrentPage(); + // on which page are we ? + $page->getNumber(); // return 5 in this example (we are on page 5) + // generate the url for page 5 + $page->generateUrl(); // return '/?page=5 + // what is the first item number on this page ? + $page->getFistItemNumber(); // return 101 in our example (20 items per page) + // what is the last item number on this page ? + $page->getLastItemNumber(); // return 120 in our example + + // we can access directly the next and current page + if ($paginator->hasNextPage()) { + $next = $paginator->getNextPage(); + } + if ($paginator->hasPreviousPage()) { + $previous = $paginator->getPreviousPage(); + } + + // we can access directly to a given page number + if ($paginator->hasPage(10)) { + $page10 = $paginator->getPage(10); + } + + // we can iterate over our pages through a generator + foreach ($paginator->getPagesGenerator() as $page) { + $page->getNumber(); + } + + // check that a page object is the current page + $paginator->isCurrentPage($page); // return false + +.. warning:: + + When calling a page which does not exists, the :code:`Paginator` will throw a `RuntimeException`. Example : + + .. code-block:: php + + // our last page is 10 + $paginator->getPage(99); // out of range => throw `RuntimeException` + + // our current page is 1 (the first page) + $paginator->getPreviousPage; // does not exists (the fist page is always 1) => throw `RuntimeException` + +.. note:: + + When you create a :code:`Paginator` for the current route and route parameters, the :code:`Page` instances will keep the same parameters and routes : + + .. code-block:: php + + // assuming our route is 'my_route', for the pattern '/my/{foo}/route', + // and the current route is '/my/value/route?arg2=bar' + + // create a paginator for the current route and route parameters : + $paginator = $paginatorFactory->create($total); + + // get the next page + if ($paginator->hasNext()) { + $next = $paginator->getNextPage(); + + // get the route to the page + $page->generateUrl(); // will print 'my/value/route?arg2=bar&page=2' + } + + +Having a look to the `full classes documentation may provide some useful information `_. + + +Customizing the rendering of twig's :code:`chill_pagination` +************************************************************ + +You can provide your own layout for rendering the pagination: provides your twig template as a second argument : + +.. code-block:: html+jinja + + {{ chill_pagination(paginator, 'MyBundle:Pagination:MyTemplate.html.twig') }} + +The template will receive the :code:`$paginator` as :code:`paginator` variable. Let's have a look `at the current template `_. + diff --git a/source/development/pagination/example.php b/source/development/pagination/example.php new file mode 100644 index 000000000..bfcac41eb --- /dev/null +++ b/source/development/pagination/example.php @@ -0,0 +1,42 @@ +getDoctrine()->getManager(); + // first, get the number of total item are available + $total = $em + ->createQuery("COUNT (item.id) FROM ChillMyBundle:Item item") + ->getSingleScalarResult(); + + // get the PaginatorFactory + $paginatorFactory = $this->get('chill_main.paginator_factory'); + + // create a pagination instance. This instance is only valid for + // the current route and parameters + $paginator = $paginatorFactory->create($total); + + // launch your query on item. Limit the query to the results + // for the current page using the paginator + $items = $em->createQuery("SELECT item FROM ChillMyBundle:Item item WHERE ") + // use the paginator to get the first item number + ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) + // use the paginator to get the number of items to display + ->setMaxResults($paginator->getItemsPerPage()); + + return $this->render('ChillMyBundle:Item:list.html.twig', array( + 'items' => $items, + 'paginator' => $paginator + ); + + } + +} + diff --git a/source/static/puml/pagination-sequence.png b/source/static/puml/pagination-sequence.png new file mode 100644 index 0000000000000000000000000000000000000000..7016be6e2d5208ead441892e04dfaf0c114b77a6 GIT binary patch literal 15076 zcmch81yoeu+wRy22#Uy1Dj*WVj~I{^fuUm%M5Lr)=#Va@BnG4#grQTqK}EU*X6Wwj z&btTy|L?osUH9I#?p=3XT}yDznSJ)z``vFm&-41q%ZlOMhTeuiAb1kuA_@@5Weo`A zlKjof;FHOuc|Pz1W+VE}M#tR3!9-8r1|p_sre~#Pqo?~=+u^aHjg18l3yXz`mYK~* zQxj%u^N&_Kdiv&8271h9dNw=;N@QR(Jx9fNHvhf{0d%c@H`?w+zqF8;pghnJH*;8%(eW6+&S*9K6K^MBA|OC3 zhy(T>odO$2&)a2-cHBCbf3fatbeR5$)o_OU58bcMJxc@nm`L#qA@QiL(uaSZu%wx} zGN#vuQ~JyvP&pKxzOLy=e`?Bhc`SlZ@yKg$Pb+w^yRU#*COWy66jOY?+0a32Qu3$L z<@;e3E)*7zR7p7d`>{kwOt?G|T+3~OHtxeuT2GxX$uFG)s2cw%AszAQ{$nuaT;nN8 z8vkVGSJuPKCvCffpZiCK^PI)?zq5vj`2G`G@<%eF?^VAKiD-)u!iNmsvFr2W0;*rn zPD`~B|J?qy|2)9a2xB1iL}vTS80D`|(|*Wl2;}9Vgou!${m^Rs9Yv-2=8VjY{ zTm{8=KnErC$^Gs1vs&Fyay$rRf)M$DWo~Tu>w%nz{o;UXqN}5{pz}MGcjTMqTc&$| zZYl2A=AVp_bPN?Ga=%s`R1z;bKd2iY3LxYBi4G)K|a> zsH9sLJf{n1DRpxmX!b|i&ktKFY{f5jDJdyo`}?WIArQ=hH+Z?6gsOtNKqT&YQM1Is zrlfTS$AN`~g)uQPb94GDHzDrOQNN?l3Y*&9r>yFZ2t3}}U6CgCY>E7^u(0R6l55RR zF5~X;3C$!E{V0}rlpBiVJ8QRAIO|B{i;s*>uYHx(FAsMbdb|RAQ{~< z3u`rvpIL*X4%@j|zCe|x3`^cGFE4XTf?Y$${yFqqx=NQQU*nuSeG8kzOVuu^Nr!B2 zZf$jZt} zNJuCt5&!FU9ydG#tH?UV%qdfQ>QPUz;;A)r8+Q60UrF6#T)|0+fPqTP0h+eb_GsV z&nvL6ua|#jaATjZ{u*8@8=AZz?+~nfb_uM;vGwi}_UOp5<+7Bdq}AE3AU1z2XH{37 z&R9^ax|yKi5*SNPg4`I16tN#pvpZSpNE6N07hxAO?h#%{dVu3 z+;4xRq50_}-011q-8{S8GAJjvf6@!5uHYVPw}tT()m=}$lPRM-1_YmyH4)5`N7MX&u!vhPAiygHagn?7Dg;p10kN4g|0{L7*a^n!5*Pt-f*N)9Hne}Ed#0RiUHx>_7Gzi%W zo;$RBx^hDVb;1ir-{daY!KRI>m zc)oBD?;xe?SgLojw!?&=KF=9={(8p#Xs1`lOps%(^)(8zMsd*TS#g}b*;U%)mkSz$2vcXuc@s>q_H z)O1)tAaNhp;o>wCTAB%;cPv$+eT#+sQ_cSO~=b^d2(7t zbznzN@7AT55OP`V?XE7&%-+@#cpq2ST2EA*Vk;@F&!u|@l*DsuIgMKmMulg?@Ux87 zlZMzz)qofhJrSmN{SvF0?5dYt|4epgj4LX~Po|;ivRQS;l0Po@Nenbgq+*e|nVFOS zKrWA?e~p-8-yODA2F@FM3q0mMn9B_sTd2u+kWtH1;UX8<-CO%_cs!RV--iUM(Gl(?M{z^E3EGm5xM51wH0J{Z2i1Z!L}xk znraaC;cPw-qgqYm-nQ&cawy@v^xf! zm$2nJV(&$MKl0@z6?Ch8$>TV6a>$NVuBgaga=Lp<+5lsype5eiKTGq@@e1+c=4NOM zw8P@yHhMc+vx`l(E8y#Bm9i1l{+bMx;92Zm!*AVsAA&d2R$ru8Pnqzs@7@%tGlD?# zgjWLlT}5zty@?R!6+Zk^xo9pz2Iy>07HF5Y+X73cf)nP##MU7RY5;_1`3gG01lg zVPtlX*Y5OC*5A5ySn0@IoTh&~Q1E03VP;LGp}!abUWI~n%J>F>zTxb+v$dtg27i{; z7T0q_PiY|(q)ZsaCLOycyvn^15)yvtl4iJgWQx3ZPY;ZhbsXzc_b}?H$zp1%mP-d7 z;3%lj>A2f%s-UH)m~x;bL*f!h>5^ZMe+hQxmb$oDpCyl$@f7E4-h44{xTWl-PuDaX z#d=QDd*#ZNvz>H@qj&F1khRi^MJI<{En{QC@ycjK$>3mJ=!fra4#EPhnxyO5vG;&` zcDd(U^Ym$>AytD$=rb3wQHro&3f`XL{JuOFp=DN@laj2M7|AqQ7sb{#WpK&{is?n` z9-wdQ=}!GNZ)1AsFK4n+KXl)AkK!`Y^HadrHGC@PuZ+IbbxF4;UVmEe5hPfAh77|q zU}rms^@Dc!{QhmYYvyf_=;R=I;dmQ%86-lAIzc6$pf{{NUpkL|Z`0~cd(iSgZ){@1 z>3R?e*u3-X0t1(uNE=!h(UX%m48Vz>o^Lm*n%59Nd^oqdO7ZAXxqb)K^k@m2?cw2} zS9zjdVU?<(6CE9`(-at<&-74AO>G2ZB^VoiWb4*!+m*|gtE;Okb9yyUJ>UQQk*A`f za&tSMnbCF8ibYab9qlaAzey9nqi0$8xzMOmsVFf~)5)o7FFfG*=t!f|rX|e_pDP}5 zDsmHf_3?)KE7WkAd8FAWvZU2sSXfwFJ2@sHJRF9^Wcc~rCS_q{ydP>hQjXa6vESU7 ztY;e=8sf2@YabYR=fkZzzy7Y#P*6@sBfX;;%RcFHz-?cJ7?Mn?~3Me-etk zLUx$c-2MVO2-%>kmNroQXEKdFGw~*{+N-s9GP;L{j~h{B>avlR{tZ)q+_+Hd4~Cp= z(qv74i}|z<96C8_!0{}^t*i=PN`$;`dAq!{w9{&nrf-x`S67$f!9X8oiM7uS=>G4= zL{zT-%!GIQp1nXoUt}i(d+7HZ(n{kl^!sxc~Z>fC*!F5rYD+bH0wC64_ew`Sa&u0|jZ!OYPrZ?8aUCn>ji$nW&Ya62`}mAG5Hm!|9Iq z*OlddMsdB8Q&hy%x*TIxrWm?a36K#LH0$K-`T?hf0i5v?r_VI{<+z}Aql8n4DY;Zd}ptt+!J@iIWiu%%;ry2r}BDqW?ZB^1;9|pIkNQ7c!cuBhz zIAW0mAjR^HIdzmc^u15l>ZrmL0c`3tlvlQoiAke+pue9B2E$G+k(u(Kk5Dl@6Ugc_ ztz|EMG3@v05+&|CktQI?YOEHu`cSd@o%0Ipv(NO|Cv)hW*%{F(br4^&0$0oso%o$H z)8S{gPI(Aky?yic?c0j9H19wnK2k1xXY@bw`+DEV!otH_7Q<q*8LuU9Gn{tDR!?56MN@iwe`u$fvt-F3QCz_+o zY6OLcMfpC2yC6hIyz_g`TVsj3?|UR&5qGbGa~K24;#T#uaw1^uew$_&UjUUq`5OPL zKuYeikot_Yw6rYEcXOw?S)X3LmA{zoVDFg&?8mEXu7XWf5>aUQ2f0g-P1pOdp2@;= zh`YTQ%BFmU9`bk$fxgA(U7v6nGDQKZ1W*WpS_W4exZ`RKd7b>pN&LMBkbCPHn-A@2 z-TALUe)A$z>h*A=1wt1s2H!wlRioiNYJYDkpxdwz)7;!@V`*w=$h0I?skUczx(!j| zA9HZH;@DYY+`m?Pyf!rSeixUEkB@6UE7XJhNEJfW3_A;(a7`@srt4^Hd*hMed>bKQ zaAKmM`zq-c_d|cZB}^s>v)aONgPyLgc*zlcK|ui-m)XpZi@L}hY4qm9hY8>XmF5)R zzI{kVCHNhwNd4l)3twN~soYxiID1wW7CAv$I=Uu!1g_vMAxx#Ep#h%d;P^N_Ep16A zG%jvsex6R#clEuh>MnQmTj@C&DXHa%VqM+Ct=UVLE@^0J>`_rLD-++pQ0) ziO*T>0?r2;3AwI<8NrcUkcDW(G-Mre|I=c3p6#AQpP@#3^lS_!?VkD~Ref+6D;ryn zcEL-{+Gs{{FgE~u5F|4OCn%$r{*1Fys&)CESqpvKN0bm3LmGtCf?V)0$rR)NoCT7)A z>6Pb!zCl5D3i|LiSWH_QKODKUz0JtX96@LlP%O{bH;V0QZx8VG?d~6$XE6t7z)>GF zAopl=#njkXN?LknZ||W)3NVuOR9{Nq3uzAT;Nf8vQd6ZOj}8tN@NABDjCMED!yZr| z*I^Nnkv?8tEh3ile#DG%UEAc${f5l3dyg^;GBTdOeEAOc?8OTi+#@5|A#QGN+)w^= z-aR@AccZMo^vwW{^0>LUAbLHgmOEK`^>$Xk7kQMV26w+=4bph{(4YK0iL$unIDG}5LT#+^^QIuDIs5|Lnn(;T(7)bIvj(*iO zYcx|H{RsS%`0y(K-d;_Ra&4ik9%|0X$K}SWt^g9lR-4|?O=6}iZES2DEHVfp=k6IE zb{hDu^(IYvu);dM?-3cfEO{K;+=EN7%SiGSr037UB!3c+%f3-iVAiO9PL;1-^;S`l z#?~f{)#K#r#1~Q2ge5ZUyJoGk!>d>Bk&%%xAs60jetP;o71j9AP}Z|PEViMs@jcT+ ziJQ;l5}YT0U4yAR{_ym?O24wco){B@9QPfdyUJGRxQkqbr)GWs{(Wo9D))Q6C~vRz z3zb3<=wo(CouQ#2$C0@+BliwYF8YXwh%`$#ppGgT$JeDVTtO5rS$_E}Ty3?RO*R zGK+I4GUcq2V0F<6z${oqE>;|`Rk5g7+VEl)+Bc1C$f23t8I&q_h#y;h)-I;K1RMIL z^U^F6{_&bLtJO?%kwI71CZlrR!O_u>GVSR!$Y-$!UkIs-w@OM%WU~-$*(nSXB5|6< zi1kc!58X#|rV*DzGK_rV`>I`yFV-)OUMOwYdBv(~(8M-W8;Tf8tHa)XrtqnS zU(_1LW9>Yw=T!`X1|*uJ%ogVHR|pBtK+M_L|ccVLThx)(&o zCX=1wib+dU5@D?Yr{j=g(>3XWH8U={eG|=0R`3S~+qKQ^Hhd8fC~TRKQh`*nGMnGu z1zBM6DGu#K?%2T6Qc^@jMd_{MxUJz;>$KdTYzg#%haOTJa^Zc#!!x8s$7V9{9UKtm zDtojjugmeVeRV~yN-3Cq%;e*RoswhYuO z0NBF(PEJk+mK^=)(W59};2oe|C;>6p-pNTyaBAZgHa4E+8gTM2d%JklirMu;M6=WJ zQdxUwwmHj6N+kTyB`R*AId0GoMc3k(`bJmviw04CyWvoC+p+R+?Ee0Kr088L0kuv+ zk$>=6KG-g6pxK+!yDi^=O?db)t#lQ{h{_Qt8*tH2cR}ojSA9d);ifwMV&hTZab`E( zf=r^4y84ULX$U9e`tTFh?`r=M4VZ`6@85EVs+UtqOG+Y5O>1lx6>My5*4^f=!vJOm zVgf^V3@=lm(LSPoc#GWG9eU%IfPlbgJnf4Y9pmHU+64>af7ZLM|M=3&-@Utx6IZx&*TXhqw z`PdcqIbZPYVJlBK%$9T==V?JfAj~p1vN^GLJZ0tMw*Q}DVH@O{7fyM9Qn%GHoN1}| z_gdp|vZ#MgZ?BY;6xdiCoc^N(u?{n<#l?dKqRmd7mtg+if-0=?7Il{z>-ss$4Xd}e zt^?N&vR>+Y3>S&(W5tVPcp@pus^rjW)%!KcQ3)KE3*+CmzuWXeIJIKuK=u4UnEsEv zt{yvx8JK}c-YaU1iZnue(Uq3N%~Y9O8E2KH?xtY1a0d2AKA59`gNMG!;AD=sX@2DJ z&m4<@qNaT7^{Y56ALgi*Q606jlC_{Y=e+7P^()71;lQ+Ur0UrWe&QFBOCu~}hcI%? zO-wk1#o0$PdeHS)n3nqjuYJ}ZSGa&F+q(|R1A14c)=k?7Y{P8h(#X?AJ z>M{kJXmwO@FhWhu8Kfkbn;G3Js;gtd!bv3unnXps*P_qsp6Q4|`f2(3T?TWTAGDCr z_R#9(WM>Pb+@bLb=oqjFTLt6Qr44pOav7J)nE~U|Q1eNEzNcEmg|%-<0p$`0UxiK4JKAv^6E1?#+;z)~h!KoGqO$_nri zA>cgzOK|1zXtDs6{NK=t<3cX@EpRtT2_N;y#Ww;+0uLk~H23%TX^SgRFbiA!I*I#H zgoK1ndn;NVXs{`67a{p;?GL=l@LKh2p+nBj&I2L|rM7(D>>0Q@WLW;dE>sTRhE)oI zSpU-n^U`ijO;dAxvfhV9vsO*U$k-TEy?x(hbwvb_lQoXJOFw?RGB;oU8yI0V85xG{ zQLG{PAkEEHDT0S%{>+6R{Y6t&oNk|r#qJF#r>5uSF>`YEX%~ox{wwJE)bxbVN}ZgZ zrh47Fb!%wpQHK)%B9TI$p0%&NS;|IUGOe>2}RXqSK#+$6SLqjf{-4PW}dDmM8bf$#e7tEvGyW z8KO*RLtQ+9IgV(+IuivokHY*(SR~D#x+KR$g@G)UVzfs>S~|cP^GhjN`Sy6ZrSKFV z-$@ben9KfX*89a+P=T0Y!le@h)Ts{fd7(b5tlZNPoz2J+NvEOV;nZpy9(-P2l{=;% zi;<|ga7+4UGT~c2+ej(Ra*HIBheol0KG*RsO>>myjIZakQE{;2`f|n?+PJVU8rkIG zOKh^2d4}$Wl2VAyF=EY~{b}n0EN`c+TA2*W@$m4TDSw?o|E_B(d$wx1r5(m0 zVMXB+KMK-`5^Qh@d!q7f!*eoqcyTI7z!b9QTf-Sc-VgTc6fTmNCbhLzRSE5AMsvXH zSStA_rE@+M$?a01`kEJ>vuS3!4tvOQ%GD8ae3Ed84LTa6$ot&#mTgEZ!}8BhotJCr zaP+eBQIPr`{_O0mrKKeic7N1P6JW;{_sQIYQEQY0aNf2vG`q2G?eYz00^5p0e%|l9$G;9w1rD6_HO3%ZUC>>F z@*|V_YO>KX^F$YcnGLkfB`ew7##yGSCKYs?4avVNDcI5=N_K#vGrNmh7w*zIxuKC3 z8k^Ua)2%gI(+-Uv3{q$AG&G1_*G9vZqM^s81W;JM0hy3yyCHGhpkyBK#aswCK0Pzo(*f4f-N{z=8Q4mPUai7u=?zp&e-Y{%%bW#q+=yfb;WtgJ zebKfS-eR~(f_yx`Q>h$X-Pvw_Y9t52aa>&7*RS`$31-!4G$c`sWL9S=BHMiown$4$ z%jy{=Ku)zAZpZqFnGET?38z$1v#|J{MF%3II4UzUbLq(skOdfK>u}`fxB^@=C$Rk~ zJF>r4Q4AETG)11?-mUM4S=w}axlKH3f5OA_$UKx-$p7ElII0?tNpd_ znLHr^&d;?`^-302j7f0!v$yEs-dK&a%F6eZxx@XO29zB>KdWzCf^C7aVkaz6i7maa zuTLtHc`;vjkGsd1*Li?QO^=`ll+<}RSLP8)d1Cwi{rebzE;eWvIPg}P2OlZtskJ06 zS2}$Kwa*2qeBo*nh3bUF4?vubB=WdZ#arF%@WPy(^NM_*Laa=Uc z{o;zG0b_emd;?bvN@4LE^YqB-A?FHP;iQw<=8HPj#v&oFajs{2crE(rHK<<>IXBL^ z2jh2IW1rueT1<@{!w*E%z1{9Fx%MBIgt5i%ffkPMmabU968x`KSh9?kIcMK@cxbfF3$ng8I!J zYJRwvdLC1@#`~torO~*O;jFW3%b9A;Cx;Nw=083tqHXgFY^MX0kKICcz3_Q!UDl@u zaF^^Wq1dp}ZIbc$+JE-_gHJa+8NgDMc{N##NT8r0B6Du5$7Q=UaL9&dLUbh84;M^E zIamUuLua_PjEMwpT`s76T-?Gj^SGiDEIjxPU?w`_^*S#Ba}R#S)smp}ykPrr^(Ai3 zKs}(7ZyD5N;eFS#Yt-lBE7RYWo%w}uw)ZAoc6I(bZ-qSOCT>|qO>d0U>UO5hX)qi9 zyP%wnS9$(-3o|S~U>1^{{)LkW6<-k%ZHr_fxOsD9YfIrx$VGSov2bd7T2)n5i4@N| zaS-#2N;zKxPzMVDWPAAVt%inKoy~C!O25E7DwMHYw}lAc649(ro+JTWNG3l!+aHB0 zdB;@vttFHWxOgF%I@{Hl2cf^%q>#ugR~e^1@Or?Krro=XVrzPK0-KtV0v0I3Zl-*14_y@>AnSvhiGr^qR*g`cmX6EBNf)z zoo=+4YVKPVKgH+wMSYN!0aC0g*XUW+FfskpxC~CtyU9Y0Xz?hus$U?GoO=sE1xIA@VTM>$>ryo|o_@}m)TINTtZ$SUqsBSLj*_;F<$|Ww!!sb@%V6V9jSc#(*HqnCn5SrnS276&yUpau*M;qooCLA2_|G z>1i}EGYbpBojajypFVwJe4Dd?f|?>ZqZ_p9;}AMv3mpq>8UslC^VHw;`uq8P_VBKVWuxq(w>ciD@bkS`dx*tir+y9B z*vV778|<|Mv$S=m`H~)K=E(aJUK=u*Z+wSu6Z(1|3o;N4fE9oxFDxw79+^`7welPG zH^gHM!oS)9PVdO#y@s+Y=*NjF!v5=aiI^@;4Vv z`|^NR^8rg~aq)Lw+|@Tk(dQ-WPIFEu2*e^(vz~PdxjmaT%dOlAv;1;*>o3l}LUz52 z`qz(y%kDoPf(mKizhdpd_>q5a;{Z_Dzpnik@E*vFAjlOM-^kCu@c!84{Hrg!fB*%y zD!?P}v66YFxv)#1I=Bw#3cKo9A3#?y%Qt0<;gRf->(v2vqD8Convw;>YuOhrB}R4; z!vyIXP5|32x0~R-KgZ{NMOO3+xg1apwy*j$s;htktmevi^gni&VECkLzx8!DxWaE* zE=CP*4l!h7-vbH-h-)-8yUwMrV-}3G#@y5$aMY*oSJ%MkI4>z6o_VXFUbay|f6UKw(4d)wIDyuUWY5>yQ!I1gTsgjtCeqS0IG8$tt5X8j9Y|Mqk|E5O~TW?4K zBp%Ogt#4wIQy(WM&H8C6N|wC zez`Gvl_v7JoJLvBspd(4c}Co{Y9^F?qj#74+qgsH5McSc5torW8UJv-0Uk})(z4*x zW^rVHWf1K$QevzOScSPDkYlfA8~5i)IO?V}E-fMHzyyN5_)HbHIx#92MaE~-Yz-I0u*827!((G(fUQ`tstizNdgyajW#pXxXAaH&5lJn; zqYIDDQy2KS<*NJtgEDP?Om+{Ty^TLzg+2o6Wv{oszJ8ZPP;UfKr!@$Z9_rW-z-HE| zMF$6WOTmyFz$Yua#OGH4Ateos2pk?nc{=8_K3MiIPIxgDdMuI+j6M2Ce?sQY*3k{O z^ZfSc`{&(a$2Y>yrFlHZvJmG?66)%sIVwe%o%Z&2fG@RFDy|Vfc#y&$s-4_ONDZ$B z;3GWE5>Uvx!0`fh$-%*ai)XOTta0ZS z;h1l(%}UqG?w52yAOjY}W}x~pCPDtkV>^q2TMuBj|2sNWIO2ud|8q9A8T;Jy-TxaA zRNvg{V^jK{KljC14;iJp%MOFw;>hl8C~;2$Nlc9*`mWn~mC|bkLA~X^)TE?yCRS!{ zJ*%zIqx_y;k{F-5`qW2C_DhOqVe|x<_)nNt$biIyed*qj0xGZbUp%hk@l%n&_5Ja# zV9P_sX+W_fG*Of;>z}SH3mneWEcXYs$H#vCx}gJ84^%geAP{H{xAXz3XiA=9p+>KE9}6lau?#wC7uq zmx0oE2YXG4yB{N~q4y==3tMlmoz;H3{YTI9 zOX5ymk1Qqahvej^vuN8Y4VnEtN+i3#Fbqnu0LKosYu7!<)v5^~1v-N{ztk_1ZcQRK zsdz-bqj+w|8C;qFa34yj9D!vq9}2Tt8^Ooi^iW5Iq}J^8oL!hNLZ%VPb>IA;myRS+Y?%JK z>G}QCAVF8oIL@6zx#!T zzB<84ImLwV^s7_9=X4iSsJX8vrC2i%_gVF&nPrXJX@1>%BnvcZSnDJhY`7^9uRG8x z_IQI$stjYOxcY@qlh^(qVaD^hSbq$$n*6*%o8{%`GFDizE84WL&~m=IS#h!%MLONy^T(O+K`W|T(I6*aH6mU z*xjbU80VHxtC!dK!De^5t7^eS!*my4*>r5dpfI(r_Sxuoq)Et!1(p9>AryN1<2NVW z>U^`;kb4JvLNt4RUml@d9yrOw>`;8PlZV@`i1*gMuiCqo+w^s#KMRRu0O}G1J&UH7 zmx~S%DxE3>FoXsB-miO6^o~iOpn$+wZeY+3Ox*THwl3nLrGRsIp}zIk)EBqq0cl~X z43_7#l#WzSMkxdi9xD@xxe62i@VY#n>PATfvsDutg>Gb+cu_G6t;&(@E#wT{bp#*EtokCbd}IY*OSJnb={jvW!$ zIE2NusGmVQf2v4OkQx7NP(b7L3!2n?pU;5Wrt6ugS3CEfpSl3SjTv8#=B79K6(CO= zE;TKBpR2}=Q?y+k=WFMm;ZmfFI`Kwo&96_`RIdx}6K`q*B?pNj>Nz80Xs_4Fi8FUX zUlj}|h#BJLTXB56G0L(b^K0bI*)}|Hygu=C9@YKtMhA+6eoa#XR7b7Oh{2(DI?1)c zcYNMDo-==Z2&f{cB|3|&MxZJ}Dl%Y?^%4}WUmkyPWt^s+fA$>48JEX=c6etnVzX2^ zZr%C&^qKbjN5yb9zXuKu^*6Z@Uvo%KuJ;V5xBV(+|74A6&PrO%IDLF0dUci_0AJe)!=e4zuIB?5!1_$>A^AStC$X6UGj72tUF6T!p4@w=Pc{4&VL~}Oae_Ba{6E9bF$({nz=vUG#}mx)T|kN za7?BBGn`SmcFZ@Rdb5%2sH*U!8r+JsIsc6yG9c90WZ`jTgq^3?5#N!FDX+Xwssfr! zw6rG2YMc(!6>&NkkCo@X-*y*ili`FZSS09o=~xnhmzs@c)zSKs;JO^rElv)JlN*I4 z-0oeI49uWZ3JLbOL9jId`V`8;p|Q49x$idMksf<<-T~`pyV|E8G~X@FJii2Lr93|k zCl{D_JUOm|CO9V;ATpp$gQzu-fg-1x7$1m?EL35jB2p{2n8X$RSS;sR=!!h%xtrd} zs%j?3D~8~#l-HC!xUnvncmPo0S|9*G1_eb36StK9PdX(4sC6+S&^Jy0 zuzo%%ikSXHHxkz<}_g-Jn#pspL+&786nDYG_)(ngSfXaYOR3H`YNVjWG zHU>Lr5AEDE8|_X{L;)-S0XnX_oaXx^fPy+{@F3pWJoz*o>m%A4W*4H68&A${<>~ek ztlEFHMBF1hGwtQwLOdAg>Ddx;bd6B17}VpoTd zk5Wm}incS&xc6Rbp)>9Gqb9=EXC#)oB`d%?{KLb;Z}2!UZb{G=&5kv7Dk?JYhP=cz z#c+vSbT0UB+y3ob7(F>r$eI(K)TN^ftUKSka?VUUyB^*8M!~%;3ujDt^ofzrN{2bO z#7yT|#xSGWnCj=$+CM90k#g~aL>nIXLGaVG%B8zp6gbPOLj1cG;ZO1LLWe3Cd_Oxg?+ft2v$$h17Pq$5Xd76^O^M3~7 z9xZc_NFBbUgzA;z;+__TMZR5GFearCq@MrVZ-9GVTd(6>+|jjiv|K9Bx+ocM#MzWy z<`aX1-voYpAH)Dix9|&ezI5}BpG7+=O^22mbD6t%Ax5=1l;icA)Ga}8gc-%|!Qy`4 zb|J2V$3_Xi>m7~2@%pBqvPQ9k7wCpiPjhb#l);<7L*=|xbmOg{9ESus?#at~DA1f! z4VcI@wDP0y$+!xRthro%%dCTfb4R!FpJp9*z&lueo!TIV&$HJh=*8j88^~+@C`VW@ z paginator: getCurrentPage() +activate paginator + +paginator -> Request: read the `page` parameter in GET request +activate Request +Request -> paginator +deactivate Request + +paginator -> paginator: construct a page object for current page number +paginator -> x: return the `page` +deactivate paginator + +@enduml + From fe8ade2ca8082968a51543a59875e9f2b85f1216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 7 Oct 2016 00:27:12 +0200 Subject: [PATCH 118/157] add documentation for widget --- source/development/index.rst | 2 +- .../user-interface/delegated-blocks.rst | 98 ----- source/development/user-interface/widgets.rst | 334 ++++++++++++++++++ .../widgets/ChillMainConfiguration.php | 64 ++++ .../widgets/ChillMainExtension.php | 59 ++++ ...ChillPersonAddAPersonListWidgetFactory.php | 69 ++++ .../widgets/ChillPersonAddAPersonWidget.php | 129 +++++++ .../widgets/ChillPersonExtension.php | 51 +++ 8 files changed, 707 insertions(+), 99 deletions(-) delete mode 100644 source/development/user-interface/delegated-blocks.rst create mode 100644 source/development/user-interface/widgets.rst create mode 100644 source/development/user-interface/widgets/ChillMainConfiguration.php create mode 100644 source/development/user-interface/widgets/ChillMainExtension.php create mode 100644 source/development/user-interface/widgets/ChillPersonAddAPersonListWidgetFactory.php create mode 100644 source/development/user-interface/widgets/ChillPersonAddAPersonWidget.php create mode 100644 source/development/user-interface/widgets/ChillPersonExtension.php diff --git a/source/development/index.rst b/source/development/index.rst index 043b32920..ea9f1fc60 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -38,7 +38,7 @@ Layout and UI Layout / Template usage Classes and mixins - Delegated blocks + Widgets Help, I am lost ! diff --git a/source/development/user-interface/delegated-blocks.rst b/source/development/user-interface/delegated-blocks.rst deleted file mode 100644 index 74dd97dc2..000000000 --- a/source/development/user-interface/delegated-blocks.rst +++ /dev/null @@ -1,98 +0,0 @@ -.. Copyright (C) 2016 Champs Libres Cooperative SCRLFS - Permission is granted to copy, distribute and/or modify this document - under the terms of the GNU Free Documentation License, Version 1.3 - or any later version published by the Free Software Foundation; - with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. - A copy of the license is included in the section entitled "GNU - Free Documentation License". - -Delegating rendering of block to other bundles -############################################## - -Sometimes, you may want to delegate part of your layout to another bundle(s), which might, or might not, installed. - -Examples : - -- you may want to show task in the top bar, only if the bundle "task" is installed (**Note**: this bundle does not exists... yet !) -- you may want to show the group belonging (see :ref:`group-bundle`) below of the vertical menu, only if the bundle is installed. - -This is possible using `the symfony dispatcher event `_. - -Inserting a delegated block inside a template -============================================== - -Use the twig function :code:`chill_delegated_block`. - -Example : - -.. code-block:: html+jinja - -
    {{ chill_delegated_block('my_block', { 'person': person }) }}
    - -In this example, the block name is :code:`my_block`, and the context is an array : :code:`{ 'person': person }`. - -The :code:`div` will be filled with the html produced by the bundles which suscribed to the event :code:`chill_block.my_block`. - -Subscribing to a delegated block -================================= - -Create a :code:`Subscriber` or a :code:`Listener` as `described in the Symfony documentation `_. - -You should listen to the event :code:`chill_block.block_name`, where `block_name` is the name of the delegated block. For instance, in the example above, the event will be :code:`chill_block.my_block`. - -The event passed as argument will be an instance of :class:`Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent`. The context will be available as an array, as described in subscriber example. You may add content to the page using the :method:`Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent::addContent` method. - -.. warning:: - - The code inserted by the function :code:`chill_delegated_block` **should be html safe**. You are encouraged to use an instance of the templating engine (aka Twig) to produce clean html. - -Example : - -.. code-block:: php - - namespace Chill\GroupBundle\Events; - - use Symfony\Component\EventDispatcher\EventSubscriberInterface; - use Chill\MainBundle\Templating\Events\DelegatedBlockRenderingEvent; - - - class TemplatingPostVerticalMenuEventSubscriber implements EventSubscriberInterface - { - // constructor logic will take place here in a real world - - public static function getSubscribedEvents() - { - return array('chill_block.person_post_vertical_menu' => array( - array('processRendering', 0) // you may change the priority if you want your content to be inserted upper or below of the other content. - )); - } - - // here is where we add content to the event. - public function processRendering(DelegatedBlockRenderingEvent $event) - { - // we access the person using $event['person'] - $memberships = $memberships = $this->membershipRepository - ->findBy(array('person' => $event['person'])); - - // we add content to the templating using the templating engine - $event->addContent( - $this->templating - ->render('ChillGroupBundle:Membership:short_listing.html.twig', array( - 'memberships' => $memberships, - )) - ); - } - - } - -This tag is registered as a service : - -.. code-block:: yaml - - services: - chill_group.membership_rendering_event: - class: Chill\GroupBundle\Events\TemplatingPostVerticalMenuEventSubscriber - tags: - - { name: kernel.event_subscriber } - -You should have a look at the documentation of the bundle to know which delegated block are available and what is their context. diff --git a/source/development/user-interface/widgets.rst b/source/development/user-interface/widgets.rst new file mode 100644 index 000000000..319ad3190 --- /dev/null +++ b/source/development/user-interface/widgets.rst @@ -0,0 +1,334 @@ +.. Copyright (C) 2016 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Widgets +############################################## + +Rationale +========= + +Widgets are useful if you want to publish content on a page provided by another bundle. + + +Examples : + +- you want to publish a list of people on the homepage ; +- you may want to show task in the top bar, only if the bundle "task" is installed (**Note**: this bundle does not exists... yet !) +- you may want to show the group belonging (see :ref:`group-bundle`) below of the vertical menu, only if the bundle is installed. + +The administrator of the chill instance may configure the presence of widget. Although, some widget are defined by default (see `prepending the configuration of the bundle which define the place `_). + +Concepts +======== + +A bundle may define *place(s)* where a widget may be rendered. + +In a single *place*, zero, one or more *widget* may be displayed. + +Some *widget* may require some *configuration*, and some does not require any configuration. + +Example: + +=========================================== ======== ============================= ======================================= +Use case place place defined by... widget provided by... +=========================================== ======== ============================= ======================================= +Publishing a list of people on the homepage homepage defined by :ref:`main-bundle` widget provided by :ref:`person-bundle` +=========================================== ======== ============================= ======================================= + + + +Creating a widget without configuration +======================================== + +To add a widget, you should : + +- define your widget, implementing :class:`Chill\MainBundle\Templating\Widget\WidgetInterface` ; +- declare your widget with tag `chill_widget`. + + +Define the widget class +----------------------- + +Define your widget class by implemeting :class:`Chill\MainBundle\Templating\Widget\WidgetInterface`. + +Example : + +.. code-block:: php + + namespace Chill\PersonBundle\Widget; + + use Chill\MainBundle\Templating\Widget\WidgetInterface; + + + /** + * Add a button "add a person" + * + */ + class AddAPersonWidget implements WidgetInterface + { + public function render( + \Twig_Environment $env, + $place, + array $context, + array $config + ) { + // this will render a link to the page "add a person" + return $env->render("ChillPersonBundle:Widget:homepage_add_a_person.html.twig"); + } + } + +Arguments are : + +- :code:`$env` the :class:`\Twig_Environment`, which you can use to render your widget ; +- :code:`$place` a string representing the place where the widget is rendered ; +- :code:`$context` the context given by the template ; +- :code:`$config` the configuration which is, in this case, always an empty array (see :ref:`creating-a-widget-with-config`). + +.. note:: + + The html returned by the :code:`render` function will be considered as html safe. You should strip html before returning it. See also `How to escape output in template `_. + + +Declare your widget +------------------- + +Declare your widget as a service and add it the tag :code:`chill_widget`: + +.. code-block:: yaml + + service: + chill_person.widget.add_person: + class: Chill\PersonBundle\Widget\AddAPersonWidget + tags: + - { name: chill_widget, alias: add_person, place: homepage } + + +The tag must contains those arguments : + +- :code:`alias`: an alias, which will be used to reference the widget into the config +- :code:`place`: a place where this widget is authorized + +If you want your widget to be available on multiple places, you should add one tag with each place. + +Conclusion +---------- + +Once your widget is correctly declared, your widget should be available in configuration. + +.. code-block:: bash + + $ php app/console config:dump-reference chill_main + # Default configuration for extension with alias: "chill_main" + chill_main: + [...] + # register widgets on place "homepage" + homepage: + + # the ordering of the widget. May be a number with decimal + order: ~ # Required, Example: 10.58 + + # the widget alias (see your installed bundles config). Possible values are (maybe incomplete) : person_list, add_person + widget_alias: ~ # Required + +If you want to add your widget by default, see :ref:`declaring-widget-by-default`. + +.. _creating-a-widget-with-config: + +Creating a widget **with** configuration +======================================== + +You can declare some configuration with your widget, which allow administrators to add their own configuration. + +To add some configuration, you will : + +- declare a widget as defined above ; +- optionnaly declare it as a service ; +- add a widget factory, which will add configuration to the bundle which provide the place. + + +Declare your widget class +------------------------- + +Declare your widget. You can use some configuration elements in your process, as used here : + +.. literalinclude:: ./widgets/ChillPersonAddAPersonWidget.php + :language: php + +Declare your widget as a service +-------------------------------- + +You can declare your widget as a service. Not tag is required, as the service will be defined by the :code:`Factory` during next step. + + +.. code-block:: yaml + + services: + chill_person.widget.person_list: + class: Chill\PersonBundle\Widget\PersonListWidget + arguments: + - "@chill.person.repository.person" + - "@doctrine.orm.entity_manager" + - "@chill.main.security.authorization.helper" + - "@security.token_storage" + # this widget is defined by the PersonListWidgetFactory + +You can eventually skip this step and declare your service into the container through the factory (see above). + +Declare your widget factory +--------------------------- + +The widget factory must implements `Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface`. For your convenience, an :class:`Chill\MainBundle\DependencyInjection\Widget\Factory\AbstractWidgetFactory` will already implements some easy method. + +.. literalinclude:: ./widgets/ChillPersonAddAPersonListWidgetFactory.php + :language: php + +.. note:: + You can declare your widget into the container by overriding the `createDefinition` method. By default, this method will return the already existing service definition with the id given by :code:`getServiceId`. But you can create or adapt programmatically the definition. `See the symfony doc on how to do it `_. + + .. code-block:: php + + public function createDefinition(ContainerBuilder $containerBuilder, $place, $order, array $config) + { + $definition = new \Symfony\Component\DependencyInjection\Definition('my\Class'); + // create or adapt your definition here + + return $definition; + } + +You must then register your factory into the :code:`Extension` class which provide the place. This is done in the :code: `Bundle` class. + +.. code-block:: php + + # Chill/PersonBundle/ChillPersonBundle.php + + use Symfony\Component\HttpKernel\Bundle\Bundle; + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Chill\PersonBundle\Widget\PersonListWidgetFactory; + + class ChillPersonBundle extends Bundle + { + public function build(ContainerBuilder $container) + { + parent::build($container); + + $container->getExtension('chill_main') + ->addWidgetFactory(new PersonListWidgetFactory()); + } + } + +.. _declaring-widget-by-default: + +Declaring a widget by default +============================= + +Use the ability `to prepend configuration of other bundle `_. A living example here : + +.. literalinclude:: ./widgets/ChillPersonExtension.php + :language: php + + +Defining a place +================ + +Add your place in template +-------------------------- + +A place should be defined by using the :code:`chill_widget` function, which take as argument : + +- :code:`place` (string) a string defining the place ; +- :code:`context` (array) an array defining the context. + +The context should be documented by the bundle. It will give some information about the context of the page. Example: if the page concerns a people, the :class:`Chill\PersonBundle\Entity\Person` class will be in the context. + +Example : + +.. code-block:: html+jinja + + {# an empty context on homepage #} + {{ chill_widget('homepage', {} }} + +.. code-block:: html+jinja + + {# defining a place 'right column' with the person currently viewed + {{ chill_widget('right_column', { 'person' : person } }} + + +Declare configuration for you place +----------------------------------- + +In order to let other bundle, or user, to define the widgets inside the given place, you should open a configuration. You can use the Trait :class:`Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait`, which provide the method `addWidgetConfiguration($place, ContainerBuilder $container)`. + +Example : + +.. literalinclude:: ./widgets/ChillMainConfiguration.php + :language: php + :emphasize-lines: 17, 30, 32, 52 + :linenos: + +.. _example-chill-main-extension: + +You should also adapt the :class:`DependencyInjection\*Extension` class to add ContainerBuilder and WidgetFactories : + +.. literalinclude:: ./widgets/ChillMainExtension.php + :language: php + :emphasize-lines: 25-39, 48-49, 56 + :linenos: + +- line 25-39: we implements the method required by :class:`Chill\MainBundle\DependencyInjection\Widget\HasWidgetExtensionInterface` ; +- line 48-49: we record the configuration of widget into container's parameter ; +- line 56 : we create an instance of :class:`Configuration` (declared above) + +Compile the possible widget using Compiler pass +----------------------------------------------- + +For your convenience, simply extends :class:`Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass`. This class provides a `doProcess(ContainerBuildere $container, $extension, $parameterName)` method which will do the job for you: + +- :code:`$container` is the container builder +- :code:`$extension` is the extension name +- :code:`$parameterName` is the name of the parameter which contains the configuration for widgets (see :ref:`the example with ChillMain above `. + +.. code-block:: php + + namespace Chill\MainBundle\DependencyInjection\CompilerPass; + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass; + + /** + * Compile the service definition to register widgets. + * + */ + class WidgetsCompilerPass extends AbstractWidgetsCompilerPass { + + public function process(ContainerBuilder $container) + { + $this->doProcess($container, 'chill_main', 'chill_main.widgets'); + } + } + +As explained `in the symfony docs `_, you should register your Compiler Pass into your bundle : + +.. code-block:: php + + namespace Chill\MainBundle; + + use Symfony\Component\HttpKernel\Bundle\Bundle; + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Chill\MainBundle\DependencyInjection\CompilerPass\WidgetsCompilerPass; + + + class ChillMainBundle extends Bundle + { + public function build(ContainerBuilder $container) + { + parent::build($container); + $container->addCompilerPass(new WidgetsCompilerPass()); + } + } + + diff --git a/source/development/user-interface/widgets/ChillMainConfiguration.php b/source/development/user-interface/widgets/ChillMainConfiguration.php new file mode 100644 index 000000000..2cc893cc3 --- /dev/null +++ b/source/development/user-interface/widgets/ChillMainConfiguration.php @@ -0,0 +1,64 @@ +setWidgetFactories($widgetFactories); + // we will need the container builder later... + $this->containerBuilder = $containerBuilder; + } + + /** + * {@inheritDoc} + */ + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('chill_main'); + + $rootNode + ->children() + + // ... + + ->arrayNode('widgets') + ->canBeDisabled() + ->children() + // we declare here all configuration for homepage place + ->append($this->addWidgetsConfiguration('homepage', $this->containerBuilder)) + ->end() // end of widgets/children + ->end() // end of widgets + ->end() // end of root/children + ->end() // end of root + ; + + + return $treeBuilder; + } + } + + diff --git a/source/development/user-interface/widgets/ChillMainExtension.php b/source/development/user-interface/widgets/ChillMainExtension.php new file mode 100644 index 000000000..4370f33f2 --- /dev/null +++ b/source/development/user-interface/widgets/ChillMainExtension.php @@ -0,0 +1,59 @@ +widgetFactories[] = $factory; + } + + /** + * + * @return WidgetFactoryInterface[] + */ + public function getWidgetFactories() + { + return $this->widgetFactories; + } + + public function load(array $configs, ContainerBuilder $container) + { + // configuration for main bundle + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); + + // add the key 'widget' without the key 'enable' + $container->setParameter('chill_main.widgets', + array('homepage' => $config['widgets']['homepage'])); + + // ... + } + + public function getConfiguration(array $config, ContainerBuilder $container) + { + return new Configuration($this->widgetFactories, $container); + } + +} diff --git a/source/development/user-interface/widgets/ChillPersonAddAPersonListWidgetFactory.php b/source/development/user-interface/widgets/ChillPersonAddAPersonListWidgetFactory.php new file mode 100644 index 000000000..e5bd236c9 --- /dev/null +++ b/source/development/user-interface/widgets/ChillPersonAddAPersonListWidgetFactory.php @@ -0,0 +1,69 @@ +booleanNode('only_active') + ->defaultTrue() + ->end(); + $node->integerNode('number_of_items') + ->defaultValue(50) + ->end(); + $node->scalarNode('filtering_class') + ->defaultNull() + ->end(); + + } + + /* + * return an array with the allowed places where the widget can be rendered + * + * @return string[] + */ + public function getAllowedPlaces() + { + return array('homepage'); + } + + /* + * return the widget alias + * + * @return string + */ + public function getWidgetAlias() + { + return 'person_list'; + } + + /* + * return the service id for the service which will render the widget. + * + * this service must implements `Chill\MainBundle\Templating\Widget\WidgetInterface` + * + * the service must exists in the container, and it is not required that the service + * has the `chill_main` tag. + */ + public function getServiceId(ContainerBuilder $containerBuilder, $place, $order, array $config) + { + return 'chill_person.widget.person_list'; + } + +} + diff --git a/source/development/user-interface/widgets/ChillPersonAddAPersonWidget.php b/source/development/user-interface/widgets/ChillPersonAddAPersonWidget.php new file mode 100644 index 000000000..876848d23 --- /dev/null +++ b/source/development/user-interface/widgets/ChillPersonAddAPersonWidget.php @@ -0,0 +1,129 @@ +personRepository = $personRepostory; + $this->authorizationHelper = $authorizationHelper; + $this->tokenStorage = $tokenStorage; + $this->entityManager = $em; + } + + /** + * + * @param type $place + * @param array $context + * @param array $config + * @return string + */ + public function render(\Twig_Environment $env, $place, array $context, array $config) + { + $qb = $this->personRepository + ->createQueryBuilder('person'); + + // show only the person from the authorized centers + $and = $qb->expr()->andX(); + $centers = $this->authorizationHelper + ->getReachableCenters($this->getUser(), new Role(PersonVoter::SEE)); + $and->add($qb->expr()->in('person.center', ':centers')); + $qb->setParameter('centers', $centers); + + + // add the "only active" where clause + if ($config['only_active'] === true) { + $qb->join('person.accompanyingPeriods', 'ap'); + $or = new Expr\Orx(); + // add the case where closingDate IS NULL + $andWhenClosingDateIsNull = new Expr\Andx(); + $andWhenClosingDateIsNull->add((new Expr())->isNull('ap.closingDate')); + $andWhenClosingDateIsNull->add((new Expr())->gte(':now', 'ap.openingDate')); + $or->add($andWhenClosingDateIsNull); + // add the case when now is between opening date and closing date + $or->add( + (new Expr())->between(':now', 'ap.openingDate', 'ap.closingDate') + ); + $and->add($or); + $qb->setParameter('now', new \DateTime(), Type::DATE); + } + + // adding the where clause to the query + $qb->where($and); + + $qb->setFirstResult(0)->setMaxResults($config['number_of_items']); + + $persons = $qb->getQuery()->getResult(); + + return $env->render( + 'ChillPersonBundle:Widget:homepage_person_list.html.twig', + array('persons' => $persons) + ); + } + + /** + * + * @return UserInterface + */ + private function getUser() + { + // return a user + } + +} diff --git a/source/development/user-interface/widgets/ChillPersonExtension.php b/source/development/user-interface/widgets/ChillPersonExtension.php new file mode 100644 index 000000000..869931184 --- /dev/null +++ b/source/development/user-interface/widgets/ChillPersonExtension.php @@ -0,0 +1,51 @@ +prependExtensionConfig('chill_main', array( + 'widgets' => array( + 'homepage' => array( + array( + 'widget_alias' => 'add_person', + 'order' => 2 + ) + ) + ) + )); + } +} + From 813f9e389f6439bc94d007ced33179f905d7ca04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 7 Oct 2016 00:31:39 +0200 Subject: [PATCH 119/157] fixing typo in widget page --- source/development/user-interface/widgets.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/development/user-interface/widgets.rst b/source/development/user-interface/widgets.rst index 319ad3190..1ce4bc591 100644 --- a/source/development/user-interface/widgets.rst +++ b/source/development/user-interface/widgets.rst @@ -18,10 +18,9 @@ Widgets are useful if you want to publish content on a page provided by another Examples : - you want to publish a list of people on the homepage ; -- you may want to show task in the top bar, only if the bundle "task" is installed (**Note**: this bundle does not exists... yet !) - you may want to show the group belonging (see :ref:`group-bundle`) below of the vertical menu, only if the bundle is installed. -The administrator of the chill instance may configure the presence of widget. Although, some widget are defined by default (see `prepending the configuration of the bundle which define the place `_). +The administrator of the chill instance may configure the presence of widget. Although, some widget are defined by default (see :ref:`declaring-widget-by-default`). Concepts ======== From 8ced86c60e46317b71f95b5770b05f358e42c4a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 28 Nov 2016 23:02:30 +0100 Subject: [PATCH 120/157] documentation for show/hide element --- source/development/index.rst | 1 + .../user-interface/js-functions.rst | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 source/development/user-interface/js-functions.rst diff --git a/source/development/index.rst b/source/development/index.rst index ea9f1fc60..c07ce0fbb 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -39,6 +39,7 @@ Layout and UI Layout / Template usage Classes and mixins Widgets + Javascript function Help, I am lost ! diff --git a/source/development/user-interface/js-functions.rst b/source/development/user-interface/js-functions.rst new file mode 100644 index 000000000..9baaf227e --- /dev/null +++ b/source/development/user-interface/js-functions.rst @@ -0,0 +1,47 @@ +.. Copyright (C) 2016 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + + +Javascript functions +#################### + +Some function may be useful to manipulate elements on the page. + +Show-hide elements according to checkbox +**************************************** + +The function ``chill.listenerDisplayCheckbox`` will make appears / disappears elements according to a checkbox (if the checkbox is checked, the elements will appears). + +Usage +===== + +The checkbox must have the data `data-display-target` with an id, and the parts to show/hide must have the data `data-display-show-hide` with the same value. + +On the same page, you should run the function ``chill.listenerDisplayCheckbox``. + +Example : + +.. code-block:: html + + + +
    + +
    + + + + + + +.. note:: Hint + + For forms in symfony, you could use the `id` of the form element, accessible through `{{ form.vars.id }}`. This id should be unique. + From 5ccafcb73fe43950d2b5c473fdf273bf49a6b955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 13 Mar 2017 00:40:16 +0100 Subject: [PATCH 121/157] adding useful snippets --- source/development/index.rst | 1 + source/development/useful-snippets.rst | 35 ++++++++++++ .../controller-secured-for-person.php | 55 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 source/development/useful-snippets.rst create mode 100644 source/development/useful-snippets/controller-secured-for-person.php diff --git a/source/development/index.rst b/source/development/index.rst index c07ce0fbb..f521ef369 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -28,6 +28,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Timelines Exports Testing + Useful snippets manual/index.rst Layout and UI diff --git a/source/development/useful-snippets.rst b/source/development/useful-snippets.rst new file mode 100644 index 000000000..435790b0f --- /dev/null +++ b/source/development/useful-snippets.rst @@ -0,0 +1,35 @@ + + + + +Useful snippets +############### + + +Security +******** + +Get the circles a user can reach +================================ + +.. code-block:: php + + use Symfony\Component\Security\Core\Role\Role; + + $authorizationHelper = $this->get('chill.main.security.authorization.helper'); + $circles = $authorizationHelper + ->getReachableCircles( + $this->getUser(), # from a controller + new Role('CHILL_ROLE'), + $center + ); + + +Controller +********** + +Secured controller for person +============================= + +.. literalinclude:: useful-snippets/controller-secured-for-person.php + :language: php diff --git a/source/development/useful-snippets/controller-secured-for-person.php b/source/development/useful-snippets/controller-secured-for-person.php new file mode 100644 index 000000000..501fb46c1 --- /dev/null +++ b/source/development/useful-snippets/controller-secured-for-person.php @@ -0,0 +1,55 @@ +get('chill.person.repository.person') + ->find($id); + + if ($person === null) { + throw $this->createNotFoundException("The person is not found"); + } + + $this->denyAccessUnlessGranted(PersonVoter::SEE, $person); + + /* @var $authorizationHelper \Chill\MainBundle\Security\Authorization\AuthorizationHelper */ + $authorizationHelper = $this->get('chill.main.security.' + . 'authorization.helper'); + + $circles = $authorizationHelper->getReachableCircles( + $this->getUser(), + new Role(ConsultationVoter::SEE), + $person->getCenter() + ); + + // create a query which take circles into account + $consultations = $this->getDoctrine()->getManager() + ->createQuery('SELECT c FROM ChillHealthBundle:Consultation c ' + . 'WHERE c.patient = :person AND c.circle IN(:circles) ' + . 'ORDER BY c.date DESC') + ->setParameter('person', $person) + ->setParameter('circles', $circles) + ->getResult(); + + return $this->render('ChillHealthBundle:Consultation:list.html.twig', array( + 'person' => $person, + 'consultations' => $consultations + )); + } +} + From 8a9a63905ebc8b0d482dc9bd8995502cab2f505c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 7 Apr 2017 23:05:42 +0200 Subject: [PATCH 122/157] add dependency injection / load routes --- source/development/useful-snippets.rst | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/source/development/useful-snippets.rst b/source/development/useful-snippets.rst index 435790b0f..98d07a1d1 100644 --- a/source/development/useful-snippets.rst +++ b/source/development/useful-snippets.rst @@ -5,6 +5,44 @@ Useful snippets ############### +Dependency Injection +******************** + +Configure route automatically +============================= + +Add the route for the current bundle automatically on the main app. + +.. code-block:: php + + namespace Chill\MyBundle\DependencyInjection; + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; + + + class ChillMyExtension extends Extension implements PrependExtensionInterface + { + // ... + + public function prepend(ContainerBuilder $container) + { + $this->prependRoutes($container); + + } + + public function prependRoutes(ContainerBuilder $container) + { + //add routes for custom bundle + $container->prependExtensionConfig('chill_main', array( + 'routing' => array( + 'resources' => array( + '@ChillMyBundle/Resources/config/routing.yml' + ) + ) + )); + } + Security ******** From 08f03a75b08281900fc58bd65f4e7b2308b1ca34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 7 Apr 2017 23:06:08 +0200 Subject: [PATCH 123/157] add forms - new date picker --- source/development/forms.rst | 31 +++++++++++++++++++++++++++++++ source/development/index.rst | 1 + 2 files changed, 32 insertions(+) create mode 100644 source/development/forms.rst diff --git a/source/development/forms.rst b/source/development/forms.rst new file mode 100644 index 000000000..e71e7bb55 --- /dev/null +++ b/source/development/forms.rst @@ -0,0 +1,31 @@ + +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _forms: + +Forms and form types +#################### + +Date picker +*********** + +Class + :class:`Chill\MainBundle\Form\Type\ChillDateType` +Extend + :class:`Symfony\Component\Form\Extension\Core\Type\DateType` + + +Usage : + +.. code-block:: php + + use Chill\MainBundle\Form\Type\ChillDateType; + + $builder->add('date' ChillDateType::class); + diff --git a/source/development/index.rst b/source/development/index.rst index f521ef369..b4b6786c2 100644 --- a/source/development/index.rst +++ b/source/development/index.rst @@ -18,6 +18,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Instructions to create a new bundle Routing Menus + Forms Access control model Messages to users Pagination From 4eb5f5a0c1d071a8fbf1f0bb06410b9382d68bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 11 Jul 2017 22:40:56 +0200 Subject: [PATCH 124/157] improve installation with docker and move static to _static --- .../access_control_model.png | Bin .../bundles/group/group_classes_uml.png | Bin .../bundles/group/group_classes_uml.pu | 0 .../code/installation/docker-compose.yml | 35 ++++++ source/_static/code/installation/nginx.conf | 71 +++++++++++ .../puml/pagination-sequence.png | Bin .../puml/pagination-sequence.puml | 0 source/bundles/group.rst | 2 +- source/development/access_control_model.rst | 2 +- source/development/pagination.rst | 4 +- source/installation/index.rst | 23 ++-- source/installation/install_with_docker.rst | 80 ++++++++++++ ...on.rst => installation_without_docker.rst} | 40 ++---- .../very_quick_start_with_docker.rst | 114 ------------------ 14 files changed, 212 insertions(+), 159 deletions(-) rename source/{static => _static}/access_control_model.png (100%) rename source/{static => _static}/bundles/group/group_classes_uml.png (100%) rename source/{static => _static}/bundles/group/group_classes_uml.pu (100%) create mode 100644 source/_static/code/installation/docker-compose.yml create mode 100644 source/_static/code/installation/nginx.conf rename source/{static => _static}/puml/pagination-sequence.png (100%) rename source/{static => _static}/puml/pagination-sequence.puml (100%) create mode 100644 source/installation/install_with_docker.rst rename source/installation/{installation.rst => installation_without_docker.rst} (90%) delete mode 100644 source/installation/very_quick_start_with_docker.rst diff --git a/source/static/access_control_model.png b/source/_static/access_control_model.png similarity index 100% rename from source/static/access_control_model.png rename to source/_static/access_control_model.png diff --git a/source/static/bundles/group/group_classes_uml.png b/source/_static/bundles/group/group_classes_uml.png similarity index 100% rename from source/static/bundles/group/group_classes_uml.png rename to source/_static/bundles/group/group_classes_uml.png diff --git a/source/static/bundles/group/group_classes_uml.pu b/source/_static/bundles/group/group_classes_uml.pu similarity index 100% rename from source/static/bundles/group/group_classes_uml.pu rename to source/_static/bundles/group/group_classes_uml.pu diff --git a/source/_static/code/installation/docker-compose.yml b/source/_static/code/installation/docker-compose.yml new file mode 100644 index 000000000..8048c190d --- /dev/null +++ b/source/_static/code/installation/docker-compose.yml @@ -0,0 +1,35 @@ +version: '2' +services: + fpm: + image: dev-activity-person-report + links: + - db + - redis + - logstash:gelf + environment: + - ADMIN_PASSWORD=admin +# add a link to custom.yml if needed +# volumes: +# - /tmp/custom.yml:/var/www/chill/app/config/custom.yml + redis: + image: redis + db: + image: chill/database + logstash: + image: logstash + command: logstash -e 'input { gelf { } } output { stdout{ } }' + expose: + - "12201/udp" + volumes: + - /var/log/logstash + nginx: + image: nginx + links: + - fpm + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + volumes_from: + - fpm:ro + ports: + - 8080:80 + diff --git a/source/_static/code/installation/nginx.conf b/source/_static/code/installation/nginx.conf new file mode 100644 index 000000000..bdc99fca0 --- /dev/null +++ b/source/_static/code/installation/nginx.conf @@ -0,0 +1,71 @@ +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + gzip off; + + #include /etc/nginx/conf.d/*.conf; + +upstream phpfcgi { + server fpm:9000; + # server unix:/var/run/php5-fpm.sock; #for PHP-FPM running on UNIX socket +} + +server { + listen 80; + + #server_name symfony2; + root /var/www/chill/web; + + error_log /var/log/nginx/symfony2.error.log; + access_log /var/log/nginx/symfony2.access.log; + + # strip app.php/ prefix if it is present + rewrite ^/app\.php/?(.*)$ /$1 permanent; + + location / { + index app.php; + try_files $uri @rewriteapp; + } + + location @rewriteapp { + rewrite ^(.*)$ /app.php/$1 last; + } + + # pass the PHP scripts to FastCGI server from upstream phpfcgi + location ~ ^/(app|app_dev|config)\.php(/|$) { + fastcgi_pass phpfcgi; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTPS off; + } +} +} + diff --git a/source/static/puml/pagination-sequence.png b/source/_static/puml/pagination-sequence.png similarity index 100% rename from source/static/puml/pagination-sequence.png rename to source/_static/puml/pagination-sequence.png diff --git a/source/static/puml/pagination-sequence.puml b/source/_static/puml/pagination-sequence.puml similarity index 100% rename from source/static/puml/pagination-sequence.puml rename to source/_static/puml/pagination-sequence.puml diff --git a/source/bundles/group.rst b/source/bundles/group.rst index 37ac8db39..5d601084d 100644 --- a/source/bundles/group.rst +++ b/source/bundles/group.rst @@ -19,7 +19,7 @@ Allow to group people in a group. This group may be a family, an activity group, Entities ******** -.. figure:: /static/bundles/group/group_classes_uml.png +.. figure:: /_static/bundles/group/group_classes_uml.png .. _group-bundle-macros: diff --git a/source/development/access_control_model.rst b/source/development/access_control_model.rst index 8e31527de..58fe44319 100644 --- a/source/development/access_control_model.rst +++ b/source/development/access_control_model.rst @@ -55,7 +55,7 @@ Example: if some activities must be seen and updated between nurses and psycholo The concepts translated into code ----------------------------------- -.. figure:: /static/access_control_model.png +.. figure:: /_static/access_control_model.png Schema of the access control model diff --git a/source/development/pagination.rst b/source/development/pagination.rst index 52d0d81f1..cc223a5ef 100644 --- a/source/development/pagination.rst +++ b/source/development/pagination.rst @@ -24,7 +24,7 @@ In the controller, get the :class:`Chill\Main\Pagination\PaginatorFactory` from Then, render the pagination using the dedicated twig function. -.. code-block:: html+jinja2 +.. code-block:: html+twig {% extends "ChillPersonBundle::layout.html.twig" %} @@ -55,7 +55,7 @@ Where does the :code:`$paginator` get the page number ? Internally, the :code:`$paginator` object has a link to the :code:`Request` object, and it reads the :code:`page` parameter which contains the current page number. If this parameter is not present, the :code:`$paginator` assumes that we are on page 1. -.. figure:: /static/puml/pagination-sequence.png +.. figure:: /_static/puml/pagination-sequence.png The :code:`$paginator` get the current page from the request. diff --git a/source/installation/index.rst b/source/installation/index.rst index efb83d83b..42d128412 100644 --- a/source/installation/index.rst +++ b/source/installation/index.rst @@ -12,23 +12,16 @@ Free Documentation License". Installation -############################### +############# -Canonical installation -====================== -.. toctree:: - :maxdepth: 1 - - Common installation - -Very quick install with docker -================================= +Install with docker +=================== .. toctree:: :maxdepth: 2 - Very quick install with docker + Install with docker Usage in production ==================== @@ -37,6 +30,14 @@ Usage in production :maxdepth: 2 Installation in production + +Installation without docker +=========================== + +.. toctree:: + :maxdepth: 1 + + Common installation Update Chill and maintenance ============================== diff --git a/source/installation/install_with_docker.rst b/source/installation/install_with_docker.rst new file mode 100644 index 000000000..3fe1aca20 --- /dev/null +++ b/source/installation/install_with_docker.rst @@ -0,0 +1,80 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +Install with docker +#################### + +The installation with docker is the canonical way to install Chill in production. + +Availables images and flavors +============================= + +We provide docker images which here : https://hub.docker.com/r/chill/standard/ + +There are differents "flavors" of docker, depending on which bundles are installed. Those flavors are available as tags, which are named according to bundles'names, with a prefix for the stable or dev versions. Those flavors are listed here : https://hub.docker.com/r/chill/standard/tags/. + + +How to install +============== + +Prerequisites +------------- + +Make sure you have installed ``docker`` and ``docker-compose`` installed. + + +Create your ``docker-compose`` project +-------------------------------------- + +1. Create an empty working directory (``mkdir chill && cd chill``) +2. Download or copy-paste the above :download:`docker-compose.yml file ` + +.. literalinclude:: /_static/code/installation/docker-compose.yml + :language: yaml + +3. Adapt eventually the image with your own requested flavor. +4. Download or copy-paster the above :download:`nginx.conf file `. This file must be located in the same directory of your ``docker-compose.yml`` file. + +.. literalinclude:: /_static/code/installation/nginx.conf + :language: nginx + +5. Launch the stack with ``docker-compose up``. Docker and docker-compose will download the container images, create an internal network and create the database schema. +6. Browse http://localhost:8080, you are ready to start ! + +To stop the container, you can type ``CTRL+C`` in the terminal to stop container **OR** run ``docker-compose stop`` in a separate terminal. + +On startup, you will only have access to the admin user: login ``admin``, password ``admin`` (as defined in ``docker-compose.yml`` file, under an environment variabled called ``ADMIN_PASSWORD``. + +Create fixtures (more user and fake data) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can create fixtures using the command ``docker-compose exec fpm php -d memory_limit=-1 doctrine:fixtures:load``. + +This will create those users, which password is always ``password``: + +* center a_social +* center a_administrative +* center a_direction +* center b_social +* center b_administrative +* center b_direction +* multi_center + +Create your own images +====================== + +You can create your own images, compile yourself and adapt the flavors to your needs. The source of the docker images are available here : https://framagit.org/Chill-project/docker-standard + + +.. _the composer documentation: https://getcomposer.org/doc/ +.. _the composer introduction: https://getcomposer.org/doc/00-intro.md +.. _the standard architecture: https://github.com/Champs-Libres/chill-standard +.. _composer: https://getcomposer.org +.. _Firefox: https://www.mozilla.org +.. _symfony framework: http://symfony.com +.. _*unaccent* extension: http://www.postgresql.org/docs/current/static/unaccent.html diff --git a/source/installation/installation.rst b/source/installation/installation_without_docker.rst similarity index 90% rename from source/installation/installation.rst rename to source/installation/installation_without_docker.rst index 63bc8b0c5..23e14b9f1 100644 --- a/source/installation/installation.rst +++ b/source/installation/installation_without_docker.rst @@ -6,14 +6,11 @@ A copy of the license is included in the section entitled "GNU Free Documentation License". -.. _install: -Install chill -############# +Install chill without docker +############################ -.. _basic-installation: - Basic installation `````````````````` @@ -45,34 +42,17 @@ Install composer .. note:: If you do not know composer, it is a good idea to have a glance at `the composer documentation`_ -Install composer on your system : +Install composer on your system, according to the instructions here : https://getcomposer.org/download -.. code-block:: bash - - curl -sS https://getcomposer.org/installer | php - -Install composer.phar globally -"""""""""""""""""""""""""""""" - -.. note:: - This part is not mandatory, if you do not want to install composer globally, you will have to replace in the commands of this tutorial `composer` by `php composer.phar`. - -Install composer globally on you system will made the installation process easier. To do this, simply run - -.. code-block:: bash - - sudo mv composer.phar /usr/local/bin/composer - sudo chmod +x /usr/local/bin/composer - -You can test the installation by running `which composer` or `composer`: those command should not raise any error. - -.. note:: - See `the composer introduction`_ to learn how to install composer on Mac OS X and Windows - -The docker database -""""""""""""""""""" +Install a database +"""""""""""""""""" Let's continue now by installing and configuring the docker database. + +.. note:: + If you do not want install a database with docker, you should create a postgresql database manually on your system, and add the extension ``UNACCENT``: ``CREATE EXTENSION "unaccent"``. + + You will find all details concerning the installation of docker on their official site looking for your OS into the menu `Install/Docker Engine `_. Once docker is installed, run : diff --git a/source/installation/very_quick_start_with_docker.rst b/source/installation/very_quick_start_with_docker.rst deleted file mode 100644 index 43c21ee87..000000000 --- a/source/installation/very_quick_start_with_docker.rst +++ /dev/null @@ -1,114 +0,0 @@ -.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS - Permission is granted to copy, distribute and/or modify this document - under the terms of the GNU Free Documentation License, Version 1.3 - or any later version published by the Free Software Foundation; - with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. - A copy of the license is included in the section entitled "GNU - Free Documentation License". - -Very quick start with docker -############################# - - -.. _quick-start-with-docker: - -We have created a `docker container `_ to let you test `Chill` easily. - -.. note:: - - We assume docker is already installed on your machine. If Docker is not installed, have a look at `the install page in the docker documentation `_. - -Starting the containers -======================== - -Mac OS X & docker ------------------ - -To use docker on Mac OS X you need to use `boot2docker` or `docker-machine` - -Configuration of `boot2docker` -`````````````````````````````` - -.. code-block:: bash - - $ boot2docker start - $ boot2docker shellinit - - -Configuration of `docker-machine` -````````````````````````````````` - -.. code-block:: bash - - $ docker-machine start default - $ eval "$(docker-machine env default)" - - -Getting the last version of the docker images ----------------------------------------------- - -.. code-block:: bash - - docker pull chill/database - docker pull chill/demo-flavor - - -Prepare a database ------------------- - -.. code-block:: bash - - docker run --rm --name=chill_database chill/database - -The first time you will run this command, the image will be downloaded from docker registry. Please be patient. - -Run the container containing the code and attach it to the database -------------------------------------------------------------------- - -.. code-block:: bash - - docker run --rm --link chill_database:db -p 8989:8000 --name=chill_php chill/demo-flavor - -The image will also be downloaded from docker registry on first run. - -You can then browse on `http://localhost:8989 `_ and login with the created users, like `center a_social` (the complete list is below). Password is always 'password'. For Mac OS X, replace `localhost` by the IP of the docker VM (`boot2docker ip` or `docker-machine ip default`). - -Stopping the containers -======================= - -.. code-block:: bash - - docker stop chill_php - docker stop chill_database - -Limitations -============ - -* Those container should not be used in production -* The database should not be persisted or store persistant information: at each container startup, the container's data will be erased and replaced by new (partially) random fixtures - -Users created -============== - -The passwords are always `password` : - -The user's login created are : - -* center a_social -* center a_administrative -* center a_direction -* center b_social -* center b_administrative -* center b_direction -* multi_center -* admin - - - -.. _the composer documentation: https://getcomposer.org/doc/ -.. _the composer introduction: https://getcomposer.org/doc/00-intro.md -.. _the standard architecture: https://github.com/Champs-Libres/chill-standard -.. _composer: https://getcomposer.org -.. _Firefox: https://www.mozilla.org -.. _symfony framework: http://symfony.com -.. _*unaccent* extension: http://www.postgresql.org/docs/current/static/unaccent.html From b0ca01b2ec524a77b006a6934c23bd0a03683bde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sat, 19 Aug 2017 23:11:31 +0200 Subject: [PATCH 125/157] add missing file --- source/installation/nginx.conf | 71 ++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 source/installation/nginx.conf diff --git a/source/installation/nginx.conf b/source/installation/nginx.conf new file mode 100644 index 000000000..bdc99fca0 --- /dev/null +++ b/source/installation/nginx.conf @@ -0,0 +1,71 @@ +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + gzip off; + + #include /etc/nginx/conf.d/*.conf; + +upstream phpfcgi { + server fpm:9000; + # server unix:/var/run/php5-fpm.sock; #for PHP-FPM running on UNIX socket +} + +server { + listen 80; + + #server_name symfony2; + root /var/www/chill/web; + + error_log /var/log/nginx/symfony2.error.log; + access_log /var/log/nginx/symfony2.access.log; + + # strip app.php/ prefix if it is present + rewrite ^/app\.php/?(.*)$ /$1 permanent; + + location / { + index app.php; + try_files $uri @rewriteapp; + } + + location @rewriteapp { + rewrite ^(.*)$ /app.php/$1 last; + } + + # pass the PHP scripts to FastCGI server from upstream phpfcgi + location ~ ^/(app|app_dev|config)\.php(/|$) { + fastcgi_pass phpfcgi; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTPS off; + } +} +} + From a052dd7d7cbceff946ff0bebdd12fb3c0c900f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sat, 19 Aug 2017 23:11:51 +0200 Subject: [PATCH 126/157] documentation of message to users --- source/development/messages-to-users.rst | 100 ++++++++++++++++++----- 1 file changed, 79 insertions(+), 21 deletions(-) diff --git a/source/development/messages-to-users.rst b/source/development/messages-to-users.rst index 2eec9d6d8..a935ee855 100644 --- a/source/development/messages-to-users.rst +++ b/source/development/messages-to-users.rst @@ -6,8 +6,8 @@ A copy of the license is included in the section entitled "GNU Free Documentation License". -Messages to users -****************** +Messages to users, flashbags and buttons +**************************************** .. _flashbags : @@ -42,9 +42,9 @@ The four following levels are defined : Buttons ======== -Four actions are available to decorate `a` links and `buttons`. +Some actions are available to decorate ``a`` links and ``buttons``. -To add the action on button, use them as class along with `sc-button` : +To add the action on button, use them as class along with ``sc-button`` : .. code-block:: html @@ -52,20 +52,78 @@ To add the action on button, use them as class along with `sc-button` : -+-----------+--------------+------------------------------------------------------------------------------+ -| Action | Class | Description | -+===========+==============+==============================================================================+ -| Submit | `bt-submit` | Submit a form. | -+-----------+--------------+------------------------------------------------------------------------------+ -| Create | `bt-create` | Link to a form to create an entity | -+-----------+--------------+------------------------------------------------------------------------------+ -| Reset | `bt-reset` | Reset a form | -+-----------+--------------+------------------------------------------------------------------------------+ -| Delete | `bt-delete` | Link to a form to delete an entity | -+-----------+--------------+------------------------------------------------------------------------------+ -| Edit | `bt-edit` | Link to a form to edit an entity | -+-----------+--------------+------------------------------------------------------------------------------+ -| Update | `bt-update` | Submitting this form will update the entity | -+-----------+--------------+------------------------------------------------------------------------------+ -| Action | `bt-action` | Generic link to an action | -+-----------+--------------+------------------------------------------------------------------------------+ ++-----------+----------------+------------------------------------------------------------------------------+ +| Action | Class | Description | ++===========+================+==============================================================================+ +| Submit | ``bt-submit`` | Submit a form. Use only if action is not "save". | ++-----------+----------------+------------------------------------------------------------------------------+ +| Create | ``bt-create`` | - Link to a form to create an entity (alias: ``bt-new``) | +| | or ``bt-new`` | - Submitting this form will create a new entity | ++-----------+----------------+------------------------------------------------------------------------------+ +| Reset | ``bt-reset`` | Reset a form | ++-----------+----------------+------------------------------------------------------------------------------+ +| Delete | ``bt-delete`` | - Link to a form to delete an entity | +| | | - Submitting this form will remove the entity | ++-----------+----------------+------------------------------------------------------------------------------+ +| Edit | ``bt-edit`` or | Link to a form to edit an entity | +| | ``bt-update`` | | ++-----------+----------------+------------------------------------------------------------------------------+ +| Save | ``bt-save`` | Submitting this form will save change on the entity | ++-----------+----------------+------------------------------------------------------------------------------+ +| Action | ``bt-action`` | Generic link to an action | ++-----------+----------------+------------------------------------------------------------------------------+ +| Cancel | ``bt-cancel`` | Cancel an action and go back to another page | ++-----------+----------------+------------------------------------------------------------------------------+ + +Styling buttons +--------------- + +Small buttons, mainly to use inline + +.. code-block:: html + +
    + +You can omit content and show a button with an icon only : + +.. code-block:: html + + + +You can hide content and show it only on hover + +.. code-block:: html + + Showed when mouse pass on + +You can customize the icon : + +.. code-block:: html + + Button with custom icon + +Grouping buttons +---------------- + +Grouping buttons can be done using ``ul.record_actions`` element (an ``ul`` list with class ``record_actions``): + +.. code-block:: html + + + +The element with the ``cancel`` class will be set in first position. + +Inside table, the space between elements will be shorter. + +You can add the class ``record_actions_small`` if you want shorter space between elements. + From 70832d45e2553c788628f68613677f178cfffee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sat, 19 Aug 2017 23:12:03 +0200 Subject: [PATCH 127/157] add documentation of homepage widget --- source/bundles/person.rst | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/source/bundles/person.rst b/source/bundles/person.rst index b56834689..676dbd6cc 100644 --- a/source/bundles/person.rst +++ b/source/bundles/person.rst @@ -150,5 +150,34 @@ The context is : - :code:`person` : the current person which is rendered. Instance of :class:`Chill\PersonBundle\Entity\Person` +Widgets +******* +Add a list of person on homepage +================================ + +The bundle provide a way to add a list of accompanyied person on the homepage: + +.. code-block:: yaml + + chill_main: + widgets: + homepage: + - + order: 10 + widget_alias: person_list + person_list: + # customize the number of items + number_of_items: 20 + + # only active + only_active: true + + # you can add some filtering class, which will implements + # Chill\PersonBundle\PersonListWidget\PersonFilteringInterface + filtering_class: "\Hepc\HomepagePersonFiltering" + + # when the view is overriden, you can add some custom fields + # to the view + custom_fields: [school-2fb5440e-192c-11e6-b2fd-74d02b0c9b55] From dec55131dff202cd17727cc13d56925da9ae734e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Sat, 19 Aug 2017 23:12:33 +0200 Subject: [PATCH 128/157] [wip] documentation of exports --- .../_static/code/exports/BirthdateFilter.php | 123 +++++++++++++ source/_static/code/exports/CountPerson.php | 118 +++++++++++++ source/_static/puml/exports/form_steps.png | Bin 0 -> 71903 bytes source/_static/puml/exports/form_steps.puml | 82 +++++++++ .../puml/exports/processing_export.png | Bin 0 -> 182386 bytes .../puml/exports/processing_export.puml | 164 ++++++++++++++++++ .../development/export_form-fullpage.png | Bin 0 -> 313337 bytes source/development/exports.rst | 126 ++++++++++++-- 8 files changed, 602 insertions(+), 11 deletions(-) create mode 100644 source/_static/code/exports/BirthdateFilter.php create mode 100644 source/_static/code/exports/CountPerson.php create mode 100644 source/_static/puml/exports/form_steps.png create mode 100644 source/_static/puml/exports/form_steps.puml create mode 100644 source/_static/puml/exports/processing_export.png create mode 100644 source/_static/puml/exports/processing_export.puml create mode 100644 source/_static/screenshots/development/export_form-fullpage.png diff --git a/source/_static/code/exports/BirthdateFilter.php b/source/_static/code/exports/BirthdateFilter.php new file mode 100644 index 000000000..91a5dc0f6 --- /dev/null +++ b/source/_static/code/exports/BirthdateFilter.php @@ -0,0 +1,123 @@ +add('date_from', DateType::class, array( + 'label' => "Born after this date", + 'data' => new \DateTime(), + 'attr' => array('class' => 'datepicker'), + 'widget'=> 'single_text', + 'format' => 'dd-MM-yyyy', + )); + + $builder->add('date_to', DateType::class, array( + 'label' => "Born before this date", + 'data' => new \DateTime(), + 'attr' => array('class' => 'datepicker'), + 'widget'=> 'single_text', + 'format' => 'dd-MM-yyyy', + )); + + } + + // the form created above must be validated. The process of validation + // is executed here. This function is added by the interface + // `ExportElementValidatedInterface`, and can be ignore if there is + // no need for a validation + public function validateForm($data, ExecutionContextInterface $context) + { + $date_from = $data['date_from']; + $date_to = $data['date_to']; + + if ($date_from === null) { + $context->buildViolation('The "date from" should not be empty') + //->atPath('date_from') + ->addViolation(); + } + + if ($date_to === null) { + $context->buildViolation('The "date to" should not be empty') + //->atPath('date_to') + ->addViolation(); + } + + if ( + ($date_from !== null && $date_to !== null) + && + $date_from >= $date_to + ) { + $context->buildViolation('The date "date to" should be after the ' + . 'date given in "date from" field') + ->addViolation(); + } + } + + + // here, we alter the query created by Export + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data) + { + $where = $qb->getDQLPart('where'); + // we create the clause here + $clause = $qb->expr()->between('person.birthdate', ':date_from', + ':date_to'); + + // we have to take care **not to** remove previous clauses... + if ($where instanceof Expr\Andx) { + $where->add($clause); + } else { + $where = $qb->expr()->andX($clause); + } + + $qb->add('where', $where); + // we add parameters from $data. $data contains the parameters from the form + $qb->setParameter('date_from', $data['date_from']); + $qb->setParameter('date_to', $data['date_to']); + } + + // here, we create a simple string which will describe the action of + // the filter in the Response + public function describeAction($data, $format = 'string') + { + return array('Filtered by person\'s birtdate: ' + . 'between %date_from% and %date_to%', array( + '%date_from%' => $data['date_from']->format('d-m-Y'), + '%date_to%' => $data['date_to']->format('d-m-Y') + )); + } + + +} diff --git a/source/_static/code/exports/CountPerson.php b/source/_static/code/exports/CountPerson.php new file mode 100644 index 000000000..bc76f96d6 --- /dev/null +++ b/source/_static/code/exports/CountPerson.php @@ -0,0 +1,118 @@ + + */ +class CountPerson implements ExportInterface +{ + /** + * + * @var EntityManagerInterface + */ + protected $entityManager; + + public function __construct( + EntityManagerInterface $em + ) + { + $this->entityManager = $em; + } + + public function getType() + { + return Declarations::PERSON_TYPE; + } + + public function getDescription() + { + return "Count peoples by various parameters."; + } + + public function getTitle() + { + return "Count peoples"; + } + + public function requiredRole() + { + return new Role(PersonVoter::STATS); + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = array()) + { + // we gather all center the user choose. + $centers = array_map(function($el) { return $el['center']; }, $acl); + + $qb = $this->entityManager->createQueryBuilder(); + + $qb->select('COUNT(person.id) AS export_result') + ->from('ChillPersonBundle:Person', 'person') + ->join('person.center', 'center') + ->andWhere('center IN (:authorized_centers)') + ->setParameter('authorized_centers', $centers); + ; + + + return $qb; + } + + public function getResult($qb, $data) + { + return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getQueryKeys($data) + { + // this array match the result keys in the query. We have only + // one column. + return array('export_result'); + } + + public function getLabels($key, array $values, $data) + { + + // the Closure which will be executed by the formatter. + return function($value) { + switch($value) { + case '_header': + // we have to process specifically the '_header' string, + // which will be used by the formatter to show a column title + return $this->getTitle(); + default: + // for all value, we do not process them and return them + // immediatly + return $value; + }; + } + + public function getAllowedFormattersTypes() + { + return array(FormatterInterface::TYPE_TABULAR); + } + + public function buildForm(FormBuilderInterface $builder) { + // this export does not add any form + } + + public function supportsModifiers() + { + // explain the export manager which formatters and filters are allowed + return array(Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN); + } + +} diff --git a/source/_static/puml/exports/form_steps.png b/source/_static/puml/exports/form_steps.png new file mode 100644 index 0000000000000000000000000000000000000000..80f22136328b0a1fa1088148f3e129bf17a199d6 GIT binary patch literal 71903 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

    tcsaR zYc78N3c$mM`s+`SHmD}`l0?krSbf1I%^krk(% z9jC90)=?>-Cx%cI$J!^ z7Cl7B$@H%b2j9ZSwa>Zz<7Ure-%3V8#GDyxS#kO(mdAGnuoj&33|BVxUx_hw-{j!a zQH7vU`B4@2>Gf|o^rE|2TVn0~E5;o%jZ1Pp^Y#H~M9K9u>JoV+pLUT3U{7~K$ogvZ&A2SH!N{f31?mY+XZIM$&VLf2AEG(+g> z{~RJf&!LO^UhUX&;S)Uhzx+S* z@bQirYvU{BL#}A@_JoER)iHJ{e=|UZ@$v|DjSm3Fu59l6Ox<gwDx~>UF+|ClVT$F~vVJte{Y@Y;6fHT&S@%o#OjpW!6nzH@mGS++ z_q3^~prAm>+l%Nfh^LJ{Yz7^1=t9wsu2t!@#}?6W`FJHl$8gUaZAT7yOUW2MULKy( zr0z_us`2c{YHB~*J`1g_tudF!EJzISIYYv*UDtRcr0i2%*r5P%W(gfsvLWoZE*c-3-2Y4}5< z4e(kgkUFw-h3GmTKY8K>U7KoUH&$*cDm}05HU5N!TSEdM2fX<0vq+Dl+N6&As5VIg z;jU5?E&?JFcy? zBjWE%$*V%X1c*H0@cr|#AwfVM!g%cgw_0db+Hmp}^S=FxS)jtPn){u(5nj`iD-5@mufP=OC z-52+gj&+KX|EVy$f)`qEnD5d+B#)}d@cTvO`x~k*E-nk#u`Dn{i|Hi>^V;XytIQY} zcr_|lJ`-pCQ9XxWOuTutoh#b|j8@0%fs+B?e4WdBTJvy65j;T#ksJcEQkWj3yz)BB zYD5zpGDtz5q@KbXQbI>*#WnVJ51>gL!G0?%({$dR0DRx^z0AqC0^yC7j0c1U(L!_Z z6IO%Cv-8qXNevVO78DUV)bSg1WGi*kT$Vy5pHGsO>&2t8Tdr0>?luVlI8V~;fGc4@ z88Ae2gE2-|@T=)T1P6ceKh9O0CD`xPG7G?Z+f${oI|$+m)+kDX z(*JaOMf{Sr%m6Y$Yz zkGp9nN$Y>PNOu>GdlaN12Qfh-O@2h_`frj9t`=5PlOV$)xC^0wEoSO2Cc{>!~p zN4(IjG8*)d#1}k860N!sM&nOtlD58H_}vtE4R?Nb*Aat5WT~!?*L7T$dkhFRp8fd_ z#2wvT=4CYb1E>)2p4?Eijg;G@c|YG16gzIOf`|gJJ_zza4Itu2o(DtXJ1{6>4#%@- zA!(k9`{f{Ql>Rgw>(x`!nU{}uJm#Hj7ol?6o(Nf6T?LJ+io=6laBdRRt$nZ}at(w* zDa})`uIN8qr~%d!qqjY?oUtz6+Ongfq5_?|6##wpHs<@Bot*)A26Yx_+wV>k^jOs` zJus{`HX54&q5(%ICr~R2J$eM_QwjdTT-_WqC^t+PjGGDct8JjcLrgB|9cKaljex?0 z(J(mAh~4GDF=s7xeF(%n(5K0B+TFO$Z4=3E77gdU^WcGHjrY~n&g$yNP~mWn<5pQN zi^bUE_5@Zz&{m2Q4deo#dn|e8PUhrgijCf*X^_TbytoQ!yV$abK`zD=CB<&omGtEc z^^C!P$&Gb^WMz%>ak7;(rdLWjT>p`L!*zZ`@Uo>)t%o}zN`QRMZ9F?@wre}F2KMEuyB-Mzr0rj`fh-- znMnLQmC{S=tuy6F`>7B~`}p|8kAgTwKtPbg@?yPfUNuOs>6wAS40tx6$u(?kH$hTn zAF>lFSF0Vfq?K$dbFj?`L6P!3hnoQw#h;Rw4$nuJVTfP2v}btO1*mVvGDtt({dldu zx%sMzSd}&g-n9^9gz1?R>vnma5)ELt9J>i9Q|qLGf8x%aZ4Q4Jc$Wnp(w8Wlrfg+K zO0TLA4$Z0N8K0ayn2~lN2PIl?V?66?(ACw|jo;x>UDJTRXx@i?`SPVYrGp^0p6D7I zbwBGsA&-U=Q6{qclK&47Sxz|8a5P6ct66bGLW0!=P)vAg4wULL&z`;D3bW=X4Fk^w z*n(9}P0e_v{DEFfxQX%%qZmuV7?7V7?{5tJ7>?2R-c0IhgOH@=Td~4-yPbB^61* z=h%xx-UWxP#Y319rJ@3W1qeaYhAQ?!%6wMTo&Od*`*H?!(*x0C3u|&hfj}Oy^+yXBe+s?YsP)#MSC3)P&O&fkR>(;INUR%g#2t2x$ec zlctu3lM_^GP_8<4M`nxsq^F7=bbkoda!l6Rkwu^w-yUWYV zTjRUEyDJ^Zv4W&>k#{Mn+4!TN)dRix=9Zie2XMrXnuez4T3J~cAX5{a4?{^b9W7=$ z;#adhTgll?BUvo~wtgv?%L1STdc*7LP5{B!P}WD%_bw$(FVB(xYi86Z32L6QZL1#j z>orUh5fccGhTWRGpCSYEaOH}EVO<5W`9GCHu4b*t5H&~+ZAW3;RaiV7oLQ4PWHFgr ziY8=E+awi$3I}?)cEQxHB4_`j=aAgcjXWx#O0Ze&E%)t4dKH;lRHmReP~ETRR8Z1J zhv#cZ(#Zd1-#r4wqhLk?YG_wIz#xN_>drskUV^%c$z#_$d()-|oF@kz&`TH2qc1ckJW7rz z(oe|&^pkn)3WNJ`9U{JLC0%snqEE z!FlK7e4I%H058$qo(*Z~+Ph|pTn}m%CZ#_*flf(~&I=iB?Jwt}?WN0dm-(wh0Sn^g zgaSlIOALQ2ouSU(NeI-2U9B?^*C-q2E*RRd9Yj;*6i{9DMvvoqJn^) zcLGaiySc=ueX7{$Y)uvY2NDM-22POpd$oTsM);@F8vy^M$S?sOBmVm()8JDSxo&c` z0Jflw2RVTzM^*084+61ARK?NFM?bgfcRyf8vN)gR&O-3@Vg0( zjfN%siI_*Q1YID1YA40~1I1Te1wJtXoEE$@QIsjle^_qfg!Ve2Y+BrZa|Tj@YF)4` zIOZSE_gDj2=D56ZeRDTNEVyPm-n|QEeP`b8N_<;k3x&4tW7r?Xd?Cj`%=M9DZUL+T zhn?j^72bpu|1S(q#JZ$^muBxR!XZaKOF>rzm^`q{feL&WoI368RVvzeCMU<@uwe|S zN@?jlosD&Pwbfkp)XMdY%Iu3ys;UY?$y3cN>8@8j{^uEX^Wh=_ptRas7shW9$Z^B$1QJfHj){_fbX z$RC~TeqhNnXU_Oj@iXC30Rc%m@7Z(bqBtxbk(Zl~x@BZ6WgsbDB_pqpkcd}-uYgce zQqD47E9;r}Q>|YAPkGIg6>4R`!OmWwT41{RrT!O)<>M>bJAm+~YtFBkfP)SWIH?7L zz7GkR`t^&3SW=fVRaxoCTR)-oaA>{+o$&?-)eml>djUloBcr2QO85rg(;Z+0s4^Gd znvc(ijP3z-M=2M8c2!`h8hvhje5%2k^Q&eZNJcUzbDE^yM@J9EiXi5CG7w}efKnB7 z`r}1_{zsRAHel-JN#nA`dTqBKrx`Aq7uTS zE)~UfTbe;Lf*LEeh_ANc_5<#H@G%Rw-@G^|jK$VoG=Dcd|MynzEvJlimcoY2=Q^M} zpMMwSl>aY9c|=t2v;S61GxC-NVD04jzKVwdEg%h@^QwL`MG1T>~6nq710y z%jC93*@-_=GJ|$R7uJcQk%RAdJZ5#al#J#nDlFUr#1GJ#?ayf*el4r|B%JyVbkiKS zMm+%euKg)_@^uD#%UynhobtXm+Necx3VW%;u!kXyfJijN{10Z7#`q7dK+azTmUIMQ zkcEYPAbbMiDWj$PNrXl>$8tc!1D}GU?d@9ITMoY5n7xSzpg<5`qbNq%w#MbTRF6$O zFvfY`4@gEdKZHm?OFG-o<$e9RO8J9T$vKsEqU7+7(&7A1`kg#gsTFERUj%7;d726& zgVlgqp{*MxG;niA@8-2Kswr2*+tR6?YR1r&hD#Y~QkLD$0xLrv0Yuf!@4r z94EB!{M)09T}03i|e4Bu*Rokw()ZAZ^&UNtQ{w!n2H8HM9nlX8 z=_f?^8@Z)jB_d+cu0D_C;lm{$0Owe3`-E?Cuo9}D@*Sin`S#=<0&p-bC^-1lblXjE zh&dgqu+(g^w`Bo(tlr)i^RHaMM3>}nQ|C>D4d3)g@)oO_^IdmZ0t{BKx zH8nLs75e`Dd%)&)O0%2v&8s%Ggc9a-J{A`KlLEhYRN$8Y3IH^;Qq$8v-T^JTa8?6Z zq>8ox&qH-qqjV>zqd}!BhP$G7B)4rkXsQD#yh1p(5iy82hwC$)tDT_d#s}JlUqBbK zpJ#hK5PUZ$!K^O}CIUpnfJ{iS?aS5`n;U+K^VN(b%ei{zN({Y1h!SyCJox?z0Myse z^B1X9U%|cR(7!2nx{edoD*=!xaBGU=HibVFH`GB(5Dc`^+S=OC zyug#3LhXrB&}{%3=$wQEVK|rb-WCuRd~JI35NB?5R7OLCiGKlTH`CQifR+VtEqnb& z8D;C>EV|iW3fyX*duc8?KFPW23DPkDB524P^=4|hr+LxVNCf1sN2B-5C(dJzEeL{l zJ47mx+hHShkM-WYX&_JTSbm2?0yv(Ddfy@4jbs_4F#`*WELJrQ4bzSJQUkeTuH>JD zKDra&Ze?XPLC*;Q%=iirqO96=qUVGc!?#?CiB>Q&=PCLD#oW{QEAjT3dRl-)!kdG- zgYUo8h=lrA%%zur9EbjlA_`>&SfqR(C3Pf%D#X1e8+;RC^DKJUcLmTm$z1rtKL`nz zs;|m9dcPOOXb}&l+bG7O_f$6=eP~7RU~k)WQj)sPco>LF8@!)qqQq@|bI zW062xiS#7{)$k`ibcU{|I82sH98UzC4qD8Zn3%Z!ak4ogqI}|?V%H#r!oC{$ekSec zi5P1lpBL*6xr2#A zRe<|+_$srkc@R)-tgMKNi_dk$`>0jjFJ*gzs|17w4aTZ!SpZg@$H#YGYi8fy+Z*4l zzSj&q1Dv93O;;>T1xbeD)VV5r!4hl0U8X0J6ZVoj&l64 zZ*(D#k{L&kk#80(cszdTE-Q3N>2p=-{vX=jI}pqE4Ih6LsfbD$iAMvK>>^u364FFc zl&l6y_ISkGkV;f2m4-CzvS-T7EQut0&+P5_9ruIkeLwH#^ZkDR`1Qy87SFiv>%Ok@ zJg(zBj`P}adBcPEA4*u_Vl_fbKgKF^#Z+!}T-j{2r|GJ{6?qk(Va&k|n^$d}?;3h0 zIlZgsm>l;Toj1eH!%lra!&2Xf^f;D}8}#V)1U(#d79%t_pj60mwj1M`_C4E~RsCbB zMrI8gVec9lHzL%Q(52sDP~Tu1x_o@9Exw#yGMzfvBPeqF4fDN~oj@_YmqeXcO+|zA zL(H`}b*d7`8OqN6+JlS zidEfvY@)EZSypT^1HTqM3UgQoC?@AR2)e+V*4Nkf_4TQEvB&b0Z^tx69eTg;>!~`+ z#3sKUmma5HW3(<%LN&3%uchD~T6yX1-%p4w1g1WP!zLC#Y@V@WqQ{ee`Nmfj>n(oh zO`JlQ#2-}dcIgWkb4kclfXnm}Nisu#iT2h~7(8ffYz)AS*~AeR_{I>$%>{l7iYl=L zq|a1X?HnXaDZLDW+J4PCiq$e!g@L_$-6|#?%GnLImkIfeZR=6H884z-_2k|~6V^P| z!TX|nIa8l4;Wwk!Iy&_a72YNvE;TKvyCE8y&1w;Tt#aHu!?oATfMAy1>Rz^VZr~20 zPPACJO#Qs8bBs&OQ2C7Px{431N@4ff6!N^jGE5KdDu)u~eujn5>^426?_$>%tX3GR zw9{4?r%WFH8M^#O#NF$oCE3mnRmNawDY%)76!Rw>HV z#p@V_{=%*vvmFbbGDX~@D0Dedn;zXIbEhY@m{RnHE*b9mxrWPQ{KNbCKS2sZBWw>Q zzJ1}7MfEJ3BsV!-J?OK6)OX35`NBafg>TOt&09kKZAUL`WJ5_dXxrF=q$2nXEx*0V z%34e!@$K&uSFXP0eeveP2nV+Ak$pkMB3%Pi*4nA_pTlB}4Ynjb)9%`u768(_##L8L zuvI$9zTWe3M^)@;{|$hvSMNA8-;!=LdTiv;@y$5Y)giy&F2(-nM*jYZMZ@ZO%DjhD z?0ah{x?-k~>TFFwz zbK@oK+otlbFjzwnLudr?K1lHoE(e0!v0;3aGg4{J>eb}AGRyUE*rZQ22}@F@UQq@3 zJ;t}U%qd*3*v`!4@we@vZ-TtlltSh%{?w$RI8Sy%Li_2Oide%otgZ0-nUhbfFE-Q9 zOqJ`-%zOD#pE)zn7m=^7)qgB=F=1{Y^CqYBe$&5LWwcn|J|!bj*?P`7#`|1mLya3p zIUg%Ks&3EbOZaK;L3|YP1WG#d4u31ov7cO0vMTi%@vg>=!&izJ?z7FzoYjX&jZDOw zh#b&I8$f0*k6Hr!=N=#)huBGGcP9LcPm36$54XhU6I_v-{M||C>*Zv+CE_QUan~v; zrVboS;!X%~%Xd693B3hLB`}Fu{E21EfBU*lZ-HQPDc#l9K8#k^N`2G&x37u@m5&C1 zv5U})-5q$2(yH%9Tsv{MTZk*TIB>I1yrd|jOtHpW?f$pA(EupV1myDrFLi3Y|5^mnkdG^S*Dk$1P#IBy=_D#?;Jd5fo;ZS`v01azchsLSElpl_;#*=(RMnMSiUH z8Tbs8bFm^JJ&GPJEG&fCq`fAcqNbLI`cHdD#}VFK#3D?Rm0_G=Qx!(uv3iw$s{RKb zg@lAa{xF~@9B`FlDKOGCG1{O1@gox7vhf77PfzLQ&U-+a8r9K1mjy2e<);T3<(*8H z;}E#5?=E&VxDNk`W`NkLb=z@KmMOj2sPpGEGmhVvcQ_x`?tVVopEWS(U~p9X!XpaC z;(CuBZd!g`eP8-WwWQ1v`!74J=VbB_vt`+%PNgx&=}FtZ!-G?Mc%@Yn0Yp>H=Dax;tJ2U zP!zNDw6)o5Y$m)mZ{@0gzVO%c3FA~_I;p#Ay%qr%m&uQE@>%BQDDPp=fo;~v$moo0 zz&a6;o5fms8_%rPwE6~0 zy#(q-vZdR1nIkb{j)<;b@5xi{(DIN27ZQ1qU;AjqMDXfMhjc31k7w=L;Cy+xZ{H4F zhH}PjZ@RwA&diMmt@vF%)?$`&JtaiRPgY4C)zR^<8#aQV`o4tZ5~qe;^?MGuhT%_m zcwwWV)%6f1YQI9mmvtQJt#*ec{T3$^S8XHZTlY)6@zv5GYR0q!U$B~Xox5Zh{%9l;$-eEOuMh;Hv+FGyLXT|Zpf733q(#CEYksMs5pN>`p1P*IKJE&AZ)aQ zmpYzymAY;K&)Ek4gc%Z!py#3|1<1FtX){GFLQ_){Ea*M4kDtGKa4%v_ZQ)2UFKETh7 zM(&3Mv>vSt<2`c{(Ph0P+YE-%JqAFRnCO5S98_Ic@I0H$O0Xx^vp5C$JV>{#8S_|q zQ_zq=TRpI^btzLxMTI@Ar9(tep}vaP*lhI3vHjKK0(3NJgD}_1t@o0L%Ma*`qhCA0 zv*yn5xmc@GUDxJPIIs5g;J3F!wk_>pJ@9CLqxsph#V3QPvDDDUv+ z80B27-x8v8%)uBN-FwBB4JJRx?6xV^7N@rS{Ce>&_Z}(U@(1sXSJE;*atM*Sh+NZh z2MW%}hzN+rT@WY|Q071KuqL}ONonAv+Htw4^B>wje=7FA*Ehs;WV4_Zm#XA}1!hBw zm;6p;MeF~GQS^R&KnC#vZ~9+*u)hf@4hVnCZrW$VRk%Pu`x60)B#~ec&^5c4Mcwaz=d1R?(lYFY2#Uh9Jtyv zQR#w6E(fEwg>LETfon<$QVAo}?iZzH=4m3&WG?1!AwsFu&T9whZizB;ZC7RRCxa~? zkujxcPu1C|%Ne95T^Kl$ZXaEC$>tS_bPLOB@0RV^ed5TrW?tKsTwK4YMtJh;xY}lE zcFdbnqUtMJjIZ&Q{yHgpdukiQ9Fp>iUm4h~HPcsID4EsC(Chx-8&@d+%mkmMclX5!~9b7p>iUtx#3;yf{WlDoQ9U2k?G zX_6t~x`)YS6V^{3X~R$2PU1^=N1&mpImDEhTgba~`Kvk8pTyuch2^%IpFEcn-^ZUo zDltINifJ^Wy^EfMGzP446(GrQK8fDH*8P1vRy9`R)2E$PRMXRPt)e7SZI#bt%Q_%7 zW;(apIm1^HO{F*pFKPR`eRdj&kvAPQJ#oK~{D@S>GXx|+ZJ(N&b_&rm3 z2S0clrvnE{4+nV#?{Cr>3|Sve?>jZ;!YkUs8CM82OLJRATCDHx_1-DP$6GD&@MwU$ zx!@Sn;>)yGOeu7m&%UCHl45@96w-I!C5~f6CY7;n&b;y|Ffh=x@O~MD@37s#3sY8J zo=$PRr4Z2+f;}T)9X33GK$wm+gH7TCSB~TT9-b&t!Xt^jP)|6Yz5Yx&Xjaf{XNVQuj1aGCd|^zGD05$cWUTN`7^}OiD6^ zItABhT7Qw;aVJ)}hB2l|hDLk;g~Wuouxylb`Z22T%2#XrEq|YJ`8P08>+!wnXODuu zL(^SusWV$3M3n1Eu`UJ%u>F3v&2EX5QUIs4g}#`iiyAGL?9YQCMiKk{xi4UCKLgTg z{cttve0PO(q&9EvLL(7K*fr191pS;0V2=NGelXJ;XfHB6JUlWI!iMHstG4^HpN^GZ zAKC#!QP60CDOic2S7@N2np1S)JO)L3J+aP2ADo_Xa;F-X<>2G zh56cK?i%mlZ%!3lA0X|B4kuZ9!o&kDojZ?t zWUM@$cY<3ZN19voClX=q>w zzw}5GkehnCSJY@mCK7Ia;36TlY2@J$!Ys;Fd@YAHRePg6Sn zmdir&%-|++16#PCU|7-|Nt1&4&CdOSH5yf}ALp3b^WAGPLi6+khYzDnpC{_lrq0A6 zf`_%~%Hwpayg4MdWvqO!Nz?JhXZ(Gd@qX0BjvV=Zel%G6@!AzztY}^!IP`<$9VABL z%X%_`M_o1QO+M^i8t14h?z{U@vk;%~FjmzoXbT40N(HlADDV4BV+!v)Ked@$ddlJb z>y9RW0}&yiBeAE-`2F?!$kbkPZ(%mmN{y3!m}lHrAMSHlS8QrMZ7{Bf<^T;no(sM^ zRjeWc4bp<|Y_wXXCk14vdi@~F;A#xWN`b)s#zQ21iB+#M~&giHP+{jVq z-mLZA<7|G#R)LwZ9IO#39;E?KjZ$1opeHWvp|TGoL^}&61KcEqd^Hj>jp?OgT2{)DeN1yFx1rP}b;>;A4Mb z{V=a>_8H$b=U>4c_epJ3MFbb*$%O0wUt)E|$eO4UVv9pJJ?Di)?V z{gdyZEK^zZU4`532MfPwr_5XKIE4~~>p^N@^=P` zC+MhB=n}3!BVc42_7l^SA;ykdePWwNXA4Jp&3dkeeQc8qGf;zJOxX#mpPfD}as=1?^l1!1_IWB#A2?oW!Ni==(Ht!nkqgy1T za+9Chx86VY%)9&LA*aXbAS&F~&SC|#o}J4Bx7gG-0tCY4C)*Y+1bPW~w5&A5hG!0W<7}5yW8HllI!;KBn zT8rkNwc=tdWG>`=3Z7yb=ajsBuwro~4OsmNpq}iL*~}M{F@wB+!Jci;&Yw~?^T^*~ zW&$`J-q~W5GBuxgW=uDBKl`ZJUAAQUqmYvCfB>5P3epoerF?#^UbBO4CG$vbX1@ky z)9Z~tj&LmJ*K7=fO91=lMpv07mAKn2#n)#;?2~=QE!DE2qXD~a^P^0DH1^cpxNF46 z3^|z;jIpQAUI$w0Z17I|@G09_LvE${#4)r2>BbIebaOZ@Bu_nU0FH!$2v z8|)|h@^UVG@yXWGd?X;%Xf(jV$9!lG4Qh)1LKZojUSsR{8wv%~R8`6292aOF{qNk- zKqH&9w5iF`hMsfes&rTT9g62%>%Y2tdn=Ba;rI*WGi=guA^E=I_C5ns{oo0!H0WX#4|h+Oj|;%jy)gdg#Pi1i3zI&}FP2p1z+Mi%##F zBhY5_8Ta;2h@@pQ=F$KuFwzMvJEt3u-2R)Eot+5t%xqxA!%6Z=zDx zux!BkA#YWQH<%Oze0n3+#=4=&=5Ic7rFi~}R zi;gIO=g8V~K+@Bx2ks$EcamY*DNc7{zag_{c31F8{06}tV$aw+ ziVZL%oYytcb2t!~m0T5N^1xfIO7G{N$Y#J;(Q&{GX+3|GJYJEb=i1ZwCSL@tNRFJ5 z9q9r@I>Ih6x%GL~nTjXRr=N&L&(nzY!)2!)(tjuFuxDf|3{QO9UsU|F@iJ{s%<%b( zMt!v?3YMrZrcI4?oH#(CyTmgM-*4vs-lvqjlrTRC1`%w_wc;Kisd{p7dsSjTqjVTo zi_y|}KiBYp!a!+Y^b~ZMi_1Z-iENSp0KakLhPlZ+wu~IVOKOz8>!rE1o3-uyn>lZP z?HcnDF=yX%Krq9$p0a3T#h$-i*@+jg?q&WbR1)4c$Z;#3Igz%%IM%PIF@v+Q8iJz- zVXy*o=a-*zjBcq8p*SyD92gNAdU`NbV!cu}^U5th2~{kdHc*XoO$465haFR#vywn3 zF^$`HNdgdI9@iz|5g*2nZ{OyoL8I5L*Kc;i34j7q2kiFq%ZR<$m0Pm`|ANM|-77X3 zL>XpaUK0HQCPsRpr?)|yfauXwG$=R8=HLdggSF7n(SbDv-mfCA(FpVHt>a)j=tB_w z8X!BHBv8~Mo`MI%QiA~rqBwS1eWo}ToSG^4o(0qK_RURcKC~f})Q>NdT@c4c`IL`B z*O7<6X5pbbb)UcjX<}wD*`|W50)3|YS342;xBm*~vLXBU7u|=y01mMz6uAhpf8HX! zw0JEe|oBNHZMnh@XG;EGc%M+RL7nC6zmr${nC^J zXoR0P$lRU+zN+Nr`=9a>{ogbZq+n>18D0dU$lR2m zPbw2-%~okqoKJKqDLm}!kl4oEH#SWYT-Y*0_5iMwytInkb7ZA%;&J2Q<6GApcAdI~ zve?;C8?GmdX2^ykcuq59g9`8F*MF3pK0XtWX?pi@Q7C$RR;qQHj*f0$nl6GwJB?eO z2sawJ%>l84%U%6q#a%$^g)N|RLe*8%2faA3dK%)-(pJyR@}{vs#ETgL1mw(=DC0rY zoSLa)VuEISP@)R5K=hrIlIoW(3FQZnzd1`OAanakS9VKO{JE<`}E%8cy_C`M< z2WJVDn0>TT7(om)K13f2>LkiZexe4vn_dVXiCZaezjyi8?yG`E+QTbS>sb~1EgRCc zhYQj>Fg^K>;Nnw`6PU-r6!kS~-|>tGcoR}o2TGhk&4*NOt#KVpkK5RBz7UicTl!+$ zImvA!l6l`{;wwoz>V9VX>)4X3`#8w znUdBC+E;%38yQ0su_)dw1i`{l$yvlO#sub%$WTR_`^2Yt4<>KvB8FDcSw`>|E_vT@ z!&0Z6r>Kc^Q|aE?fH; zoEQyA9}7*F-CyE9mzkv>P1fW)rcR(VN$?w}am8|3aS3W2wfkYyC!UbB?ZV6i#-+S( zSX894qtQsBQ?$|Oo$&)_j{F?wzS}`nNWki%smt^~v!b*D56WhbP@$8kzRnV|M9J`K z?zbkR6BPIK+J14*9&X(<=TWq_L$#_L0-jnURmIjvwnYlnf{Wus8mw$_kwOs(C(sOq zxGE;mlqdi-6cj?a^pCERlRh(DDknyd_SsjRHSQ0wkte-fltR$tW+VYyO%~UZLh6Ie z!(?~o>-k$^R5p^W;rvp95DJn7S3nO`Ql0}VzM_5Pu>IkhrzkA)fKtRzsT7?@MX*tB zG+@)D%6>%wRbRS3@%LYiR~GHrNIr1r(BKF16wXozBAg&-Hh1du=`!Ysh+7Yx%QwqDYhQ5$-b8US3WLFS%G-$Z3wB;FjvXww%E4SQ= ziDVKBS~!IendJq2|=CkP}b&}rSQ4&j1a$#hkeH!wv^2~efo4{+E{zV z*QM&5Qc7*Q?>~~`96z3jc`ndop?7*{?*!-Y?L@6Qw-?;l+2Pq_jE5 z?ST;($<(n(YSOD$>t7wL87;D#DyEJqW$XZ4D}7KCya-KvL`Fumr~_I=l&r7&t58eU zFRJeffves|s3F=T5|dUpi7d+HzI=VF72SoaaVRzbmHNi? zzEFe74d$)@r5okSDk=gJ9Pc}8jJaGoE9SSw(?1bqr0=Xk6oLcW(sBQeH(lj6#d&=D zzoo19kFDWbO$?0Biz>1#$7n>?Xu!p}jF(*&2oy#>opLSU;y0@HuI;(|yh^>Z$J=Ev z)If7=2nS%Ai&4?EXY2}PHQK_F7>TO@lb&^KDN*07 zP(P8@d&4qjEEFmw!X1vO2F{9+oIV(CX|qeBdGhVYoXqNNHT^MumQS=2{Q)rSphiT^ z)Eo_z*f@?vpHRVsIg*y9s5>QOF!~X+*=G=gcVv8{Fh)_B((R#h_`T@QS zVULihK$So|q`KaDEvr|X`g@xvXI|fLpD=TvDXuuW#l|1-Ni*BT>WXz%Ui?wp`t#A%_pq_*q&4Y;Hl9pLq_G8=E_|`#HWz|KrQB$#)O>7vt(SWPkJwIRPcGu_F)q7qd!rD(2X0yvjO?N529lLmR5Fi_@-(~)!Gw@*D!Tn* zL2Amh|M(Z`1ML*jTAzB|Tq(2EKp=o^*_wdT^K~Pquc9DffWHPl z3y2Bb&)fD?PiAvl$~%u(eQYxN8fl<}4u^)S8Qr{2ozdDI!NxoS%fnv1GRGQE)nXE; zuQ6NTDu*2p7)HntvJ^%1V$4uFCYmcP*U^uL^n*N6%Lvg)^!94(zw=A&*qs)DDeyh; zD8uhAyY>gT4^bLFy=tpU*6?yErB^Y>lUZnr7T&gdUza!T!L*o5s}oyIKg>B-rys|C z=K^dtSs$@$$dA-|^p2`|UDBji*ms7BkjF}$(fv4(jZNIn>zvUTB(NG)&i#d8OO(Z| zQ9SUL@7v{k?+WrLid#%aw#4P28(;a?uTUKnqnA6fdYjk%f!=@RPSg{fH4xEsBJ{s~ z`4WR7*b&Rr*fLr3SQhZ~yc6y3%(n`ovD})pf*Ib1YX4!8bjncW=?w9rf9PI3zCJnu zEA6mvTGD>)^A&1p%d|g~hG=r5^zoz=N6am+F#;d{`Er-+nL%c)fx};xH}3i$0|M2< ziBj0B{zK#vtYL``f?ZXKl2Vh+ftG&${)}wcgSVDP)}ZB!ds}S{B12D2y3(1t3f_+P z_7kPp)d4)I8+^G#HkuZ;_?cV;;=BN@LL_2@z>%V07EwY8qqke%1mofP-MQh0bbSt| zZSL{sAFV_Q=7{&hp4|bND_C|frP-3ZP*eM-#ZM#nRFfcval4AG z-X%$TLn87iPp?yGCRT8B8B-1&G%2cdtw=4hZk(HW#ge+1CP9;d>NZ?6j2Ugvx1*s9`*8b>!nC> zM9)~*1kGdj?EiZFa*cjoBWLNd!Hq+{FPpfH_q#I7t@nI?YGh*?`L_Vj^aFn!v+TFSUf;s zA$d7yr>2m02@o-4Wzn%H!>vC<|6Z<|&>^I3@o9tMwejI1EB!XKqGlCk?rE#drI_-w|SAFzV0H%=hxC( zjnVEcyhv-q_KJuPG-Z*`BbB3x*Ke;OVz|ZmKXVqLRPb7tOJt}ROG@9d4R?A-w2`!9UQcy3xE@ZhML+OEb7Y(rfXWg z-pBbVI8+49c9YSd9A$XJ@wR?(LZ!^kA|-)XifVO?p5Xcpv2*!g2EiOz5;gk~gO49A z6a_w{uHF4QHA*2w#^c=a%`2BR0PbIny>*4xah)#T_RiiTYif-?@=Fk#4fj6d8iP#xc5qqfX<}q{L8U^Ds6b7E?xMojr>cl(uXL6^G zX6MDuWG_OJGZSk6$eYaHKV3v0&NOgg5MSNk6tBmTwU#zG#v*Wer09|KnEtY@ofG++ zsYcY#L4-QT|6Jt@BK2#s{BMl#Y_70w6#8O@p=yZItR*@<(LPm}_*7JQJ{OgpcDymA zHhi2t`(QGN4wBlAtp~$hk7gS`7-}Pu{cYwY>-RG#XZ#bQSg{{7h;ucLKkYGpp{wip zjHtWRwy}bAk-1B3upUTDe7xxvnoh$Ox9Rx-A%LKeQogK?XxD{7=#?xd>$9{uY&`|8 z{dXW_#m-{sJSwQLbE90(eO*^$mGV`gyLYC@4t~l`GB90gx8c^h{}$tzzh~UL#&6_j z8*!|1wB7jVWqG?U9-HEcajvX~vTJ9)b4Jp;Z##*$QwXH(6!u`IOudiddEoPb+u+zf z`3r|%gn|gN4mlT(tt~1xWnaI()_PL`h*m4WMPRS%)Q5BzzD#49MkDSIiMcL);#@-< zm%I3^)5lE;RIexv-&4{W+jc!+4F_?-|6?jKauGAH+cI+3=q1i`sv@-Tl6UdXC{D<* zp7N1zAEz9zV9Dg6fyLSrOtdj?*+&y35n|^ZTW4;9HfK6BE6b z2O5PLMKysUJiLT=l#$KTlr!Y7(YUJLkrLSboc|6*fu2)ZpS#wmTIRrFe4sxBdr zd9G$R>q9SR!I>v~Ua)&ct29F!q2o&fy3rc-dxU-io`RXm!{7%P66iFwl+vMigpLD> zVQ5wF!C{9!=gy<(5)_k?k^-h3F$N95q?B}faX!I9sn3u~A=!r%tUH-j7>}&@)JsJ6 zeB&Sv)&6kD}N&{#k)JvdVkbx++Vxl%pJnkV{S)vom zMq|b1>v1v=!l?2(tWE2N1;rT@)I*hw_e;gp$faM7)MrW-%Mh;YHP{T#O9fGD z5xtKlo;QZ34T#S}I4106=C(aZ?Jp~!*Iq7Xlzn|f0=zR?d+eQPSR`Hb6K*~RxreCF zFmULLfdRnZz}5UXe3_Vc)9J=x8NnYg5$x)8NRe*nMm+{pSae(MDS*fcYupzveTmu93B z3UjFB4lmpI0o0?T`u&9V*P*Tx4P-{Ptxb$x4CA}?Qpunf$wuH^dkTq>xfEVVD#hEiT2Q zfi4Fxf@)|W^Z@}vNBTUIy(rAu8dZBf%IDm(u!d3{uFOROApiQBL@A@4QohF(1vFFn zH9@|Hym^D|$8CeL&;INp`W1a*M}+hKL)zUIf9qhhryx;q3-8|4l(Xr747FXo1-K6| zha*sTxZtyEYcfwNPn>4Y*zpnC>!QeMxS7}yb#W@QC+EzMq|L^2YBl0VVbD8fUAQ@8 z2P}1vz7<)li7pS#T+YFflm2Nx^xwKuQ5fSbD8b=`s9p(Q{2oOUH*?8EoRFI11Fae+u?k)bF!U(E zaCPtIwd-3%b)yU+^@ah8kl5mE71?w3nbV{E0LB7mxr}S;*+`~Ih3~Mz>}~+OLK*E6HD)gs9#96o&^P+eKK--DAe)w#e1Mx2((BtM{#iL zX!*28{6)=s0(tg$&md2|@R;Eyqj#DTiGs8O*h11L1zM0)QH&FR;8pcUtj_dRLLV<0f=$!NELP9JEw1?3=zu->wu zAljWx8|I?zzQ=qyrD%f4myzZ!!OSafk6t2e@qbf{cS($LTHV%gC7{p0rQt@`s!KO= zO(phg`HI5!rlmM~ax1Y=5-PUQaOKQG{}Wd-CX(&?#1DU}s+#!G<7$Un%h>UM`JN>Q zbs&+$UQPeyYz|}f9TXT5xGp)G!FL|-( zRl;rW^|dr)owt~H?LdyQ>c37TytMQM?L&#QOe4XjQ|)gO478ioo~(z8&)o>DuYYD0 zjhXwG-0fk#p?hFGLlpu4{s-u4)T?^BIpmNY$n#;Zc(>ZA#EeBVQY z1-o=yF85)A)8DL;cDugicFf)*L`KUB{yAbiymm7l$jONV)GoVpS!$_e*m(TS4crbt zXkp`z8z%h%j%WxvFOqcgznj{*JL47#=EoLzfn9`TLeas(sV zCS}%h6+I=O46yef)BQK`Nj&n{gwRMAqjLA>J(JDxr+g*b35XkgiC`3@?sO;r=b zalEu;jr^$3W|!=h)gy13>ATqMO8ll@KfAm7&v3KayrMfZIp^4T(d!^ed-`OzP77u1 zh7C1Dc+f#=5LM1g?yTyuT0e?YJ%jYC0>90`rcJ@LPuxYzTeN~xVG?aRqLQ_1E? zs4@A)gsL7oa@37;WAAUI#rOZVdy96?u+!10c@vhux!0#Y*5A4Ag^Lme3K4DkvoV1D zu=Ey>jF|dUkBPM;wmfpGyTkzk*=6MC@k2Y>NjHYyEs zCx?}mWfVYC0sroInBmu7jU?}x3T$x|4d}<^c2%d2W6MKRK%9*J6MO%iIrP2Ub+fyQ z8YwLwm6HDgOUluv4GYT53%V$HnF?gkI0IV~WYkLF%xkpa`&WYbWTip{L2hi&| zp}T76g^K^g*%`S(&_rI4K2H5Br~5SGOx+5-0mH+G;JSQQ$zl={PFot~Lu@hAQ3@hi z-Mj_zMS{pA#4n~DID8LlSJ+SXxIjRVQh!ZIBz>X}x+kF`af;rBJdZl}ntAyXpUibg zK9`>Ce#~?K8-?0KJxbFwFhF%l6nJO`A9oEr9&-o<01z5kixt9$I1DD+b*O!!)IRQ- zk!;qBx$pP#msk{TZ0&Rx5p5kot^NlIvG5HFkAGav0YY|%*%%KEm|&1|U$3=B=wuee z2a?7g@1XRd$g+d|@`j5;<|bREIWE|4_2;y6A4p8imH_Q>FO*3-h^ix?x8K}PxW}I z*TFv1l{YBRZ}nu@AZ$jun3hQfjYMWdYA<8Gdp(2D{0udiFwfZOv}aVn#w+xrMKi-j zw93PU$KQj8gD36wf4o{_Mot&IR9}+nSUEhEuOKAKo&5S_rq=GA^w#d=JWk^2m?>Fg z&xnU|XXLFqLK(4*Ysv`oT#U61txo{qeh8kxiI+n-kr#bMURpA&xlA+EtItMi!c@0- zpH*S%Xg4Kkz*=4IC=dDEcvJA<)A?yu2uzh9a61_?#-rw_p#%VjhslK$Or#nXZD&4f7 z!v@*>inx^i`vpvMC*&QA+&e)b8nrri=IUj2wbXr6$ z*H&(`XY%P-qCc6NKGft6zk4-w^-l)e96PnE;wHop)nFJboU{^?*DaOYrxP~*p^6xj znZ66MyN7~OOQ&TKKHe8?St6_@$)$=6yMoT;LOueT%y=>I!*cb zOy2_Nqimns(v&AcjyL6%)u_;B>;JXC>-aCAXEohA`EGjB0A_6BK zVZ|$ix9rj8)7sb;sqfw`pn3nGfk925@@y40%Wx3qR)>{+j+0Lc?%VXI6n!I2++d)s z{T14ZiX6zC7IBKz;^3Wh#Ca);su2(J#ACFfww({^YS%f6t>d^_vij?Hes|fl7pOJm z1@l&s9ly{M&!14)-+~bBS33scw%|_kc)`HNsyH(1o;NW%0!P(5D8(fvTnqk_tU~J% zlnGAYhE;E_OoXVLj5E6Y0^9$o43E>R)b_Rjmyi+3@__vg1+wy9V{}BYgH>}Rhub2d zF1Gv_(?@w2vtx-xCwtx$R z?e5SXA*2t40WSm`lPoTJWFk20y@30n`Xe|xBTtNcKu3oo(8A2D6)e@6q}Q)c;Tc7@ zL16zDjAT=Y$ZvIcTYS*yr=zytj6IvQ{w#YMv&jARMf#MfulIt`zk*?}V)T}-=b2s{ zPrhycGJ}D(JF1Rynzdk_Z#Z@4%ymshHmFu+YS*D_E$TK_li8>h1i6%6Ly${;fsQb+ zgEg%|9D-Dv=p&8C?|4BCUr*F6O&#aIviwrCmP~t$YKK|(SyKY!{#cf(L~SGuomOD9 z&$PTn&(>>s1r!##9b|@}&`Uf!-1GqxffJGPYE|YRy`k%kjg3XiAxtFvrW{3Wn)w{B z)DSS_$B@ogsp(AE3X?O6C;xI=Dd_T}ZiXz4I~Rs0L$APD&5 z2L}fsCPbsMGbr1`bl-4<6K6JkjM)l{8w6~R5=w(5G6jfsmuFV#_F*y+YB|_1j##wM zTG`%#aT37^wKGn5F+v;ut8vo6rGiGIz8iX#&Kmoh{yMbnv&jCh#Hl@tH~-md;nOq~ zjiuEMD;Ojk6%+rb@}G$SlyT+g78;davYcA+FAb4|rDIR#`GNp?Y=~Ad)ltW{hd$SI zxZufPcIFHr40j4**T|&W9!_pD^4%a9w$#_w=M66!Ac!qutd@nXJukcA^&CDsq*0

    {VEYcRUQ#~5+aD-Wa+JZ8nm&?*~Usey>2XT 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 literal 0 HcmV?d00001 diff --git a/source/_static/puml/exports/form_steps.puml b/source/_static/puml/exports/form_steps.puml new file mode 100644 index 000000000..40f17a346 --- /dev/null +++ b/source/_static/puml/exports/form_steps.puml @@ -0,0 +1,82 @@ +@startuml + +actor User +participant Controller +participant ExportFormType as eft +participant ExportManager as em +participant SelectedExport as se +participant AggregatorFormType as aft +collections aggregators as a +participant FilterFormType as fft +collections filters as f + + +User -> Controller: request "/exports/new/?step=export" + +activate Controller +Controller -> eft: build the form (AbstractType::buildForm) + +activate eft +eft -> em: get the export + +activate em +em -> eft: return the SelectedExport +deactivate em + +eft -> se: get the Export Type + +activate se +se -> eft: return the export Type (a string) +deactivate se + +eft -> aft: build the subform 'aggregators' +activate aft + +aft -> em: get aggregators for this export type +activate em +em -> aft: return a collection of aggregators +deactivate em + +loop for each aggregator + aft -> a: build eventual subform for the aggregator + activate a + a -> a: append enventually his form + a -> aft + deactivate a +end + +aft -> eft +deactivate aft + +eft -> fft: build the subform 'filters' +activate fft + +fft -> em: get filters for this export type +activate em +em -> fft: return a collection for filters +deactivate em + +loop for each filter + fft -> f: build eventual subform for the filter + activate f + f -> f: append eventually his form + f -> fft + deactivate f +end + +fft -> eft +deactivate fft + +eft -> se: build eventual subform for the export itsefl +activate se +se -> se: append eventually his form +se -> eft +deactivate se + +se -> Controller: return a well-build form +deactivate se + +Controller -> User: render the page with the form +deactivate Controller +@enduml + diff --git a/source/_static/puml/exports/processing_export.png b/source/_static/puml/exports/processing_export.png new file mode 100644 index 0000000000000000000000000000000000000000..573ad6efd0ef473c2bd8bb6b6874d0159c09903c GIT binary patch literal 182386 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 z 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

    You button