Compare commits
33 Commits
es_rev9823
...
jfiat_2016
| Author | SHA1 | Date | |
|---|---|---|---|
| 9060025e1c | |||
| d18c7c9ad5 | |||
| e1d6e79f09 | |||
| 53181f901d | |||
|
|
4b04e5f471 | ||
|
|
e1a657a880 | ||
|
|
40fd1df648 | ||
|
|
ddb3f9474a | ||
|
|
7896036165 | ||
|
|
7e9c29f277 | ||
|
|
e1ae9f9eec | ||
|
|
52b9ed277c | ||
|
|
5e55a6f1f1 | ||
|
|
f439a791a9 | ||
|
|
85dfc45f8b | ||
|
|
e573d9da41 | ||
|
|
53cb73ca5a | ||
| 0813abe0bb | |||
| 1094acb3ec | |||
| e7c9a54f3f | |||
| bbbdac12c8 | |||
|
|
22528315cb | ||
|
|
090a48eb85 | ||
|
|
e05c4dca3a | ||
|
|
2255fcc0f6 | ||
| e50fb6959e | |||
|
|
3b88c746a1 | ||
|
|
fa8ef44a4a | ||
|
|
068943734f | ||
|
|
089179e60e | ||
|
|
c25590c9cd | ||
|
|
7f26b9feb3 | ||
|
|
948b4b3456 |
1
ROC
Submodule
1
ROC
Submodule
Submodule ROC added at ec53a2682b
@@ -1,14 +1,14 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="demo" uuid="3643E657-BCBE-46AA-931B-71EAEA877A18" library_target="demo">
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="demo" uuid="3643E657-BCBE-46AA-931B-71EAEA877A18" library_target="demo">
|
||||
<description>Example/demo for Eiffel ROC CMS library</description>
|
||||
<target name="common" abstract="true">
|
||||
<root class="DEMO_CMS_SERVER" feature="make_and_launch"/>
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option debug="true" warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="all" syntax="transitional">
|
||||
<option debug="true" warning="true" full_class_checking="false" is_attached_by_default="true" is_obsolete_routine_type="true" void_safety="all" syntax="transitional">
|
||||
<debug name="dbglog" enabled="true"/>
|
||||
</option>
|
||||
<setting name="concurrency" value="thread"/>
|
||||
@@ -26,22 +26,21 @@
|
||||
<library name="cms_demo_module" location="modules\demo\cms_demo_module-safe.ecf" readonly="false"/>
|
||||
<library name="cms_email_service" location="..\..\library\email\email-safe.ecf" readonly="false"/>
|
||||
<library name="cms_feed_aggregator_module" location="..\..\modules\feed_aggregator\feed_aggregator-safe.ecf" readonly="false"/>
|
||||
<library name="cms_file_uploader" location="..\..\modules\file_upload\file_uploader.ecf" readonly="false"/>
|
||||
<library name="cms_google_search_module" location="..\..\modules\google_search\google_search-safe.ecf" readonly="false" use_application_options="true"/>
|
||||
<library name="cms_model" location="..\..\library\model\cms_model-safe.ecf" readonly="false"/>
|
||||
<library name="cms_node_module" location="..\..\modules\node\node-safe.ecf" readonly="false"/>
|
||||
<library name="cms_taxnomy_module" location="..\..\modules\taxonomy\taxonomy-safe.ecf" readonly="false"/>
|
||||
<library name="cms_oauth_20_module" location="..\..\modules\oauth20\oauth20-safe.ecf" readonly="false"/>
|
||||
<library name="cms_openid_module" location="..\..\modules\openid\openid-safe.ecf" readonly="false"/>
|
||||
<library name="cms_recent_changes_module" location="..\..\modules\recent_changes\recent_changes-safe.ecf" readonly="false"/>
|
||||
<library name="cms_session_auth_module" location="..\..\modules\session_auth\cms_session_auth-safe.ecf" readonly="false"/>
|
||||
<library name="cms_taxnomy_module" location="..\..\modules\taxonomy\taxonomy-safe.ecf" readonly="false"/>
|
||||
<library name="persistence_sqlite3" location="..\..\library\persistence\sqlite3\sqlite3-safe.ecf" readonly="false">
|
||||
<option>
|
||||
<assertions/>
|
||||
</option>
|
||||
</library>
|
||||
<library name="persistence_store_odbc" location="..\..\library\persistence\store_odbc\store_odbc-safe.ecf"/>
|
||||
<!--
|
||||
<library name="persistence_store_mysql" location="..\..\library\persistence\store_mysql\store_mysql-safe.ecf" />
|
||||
-->
|
||||
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
|
||||
<library name="wsf_extension" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf_extension-safe.ecf" readonly="false"/>
|
||||
</target>
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
port=9090
|
||||
#port=12345
|
||||
#verbose=true
|
||||
|
||||
BIN
examples/demo/site/database.sqlite3
Normal file
BIN
examples/demo/site/database.sqlite3
Normal file
Binary file not shown.
@@ -0,0 +1,79 @@
|
||||
<!-- Updated: 12/04/2015 12:30:04.000 AM --><div class="feed">
|
||||
<ul>
|
||||
<li>
|
||||
<div class="date"> Dec 08</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/-jtyp4iengE">Re: [eiffel-users] Precompile on Linux</a>
|
||||
<div class="description">Hi, I just check if the .ecf files are in /usr/share/eiffelstudio-MM.mm/precomp/spec/unix and they are. When I look in the "Locations of configuration files" dialog in EiffelStudio, I see this: # Search paths for precompiled libraries are specified in the configuration file: # /usr/share/ei</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 08</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/-jtyp4iengE">RE: [eiffel-users] Precompile on Linux</a>
|
||||
<div class="description">This is a bug in the Unix layout then. Do you mind submitting a problem report? Thanks, Manu *From:* eiffel...@googlegroups.com [mailto:eiffel...@googlegroups.com] *On Behalf Of *Louis *Sent:* Tuesday, December 08, 2015 23:02 *To:* eiffel...@googlegroups.com *Subject:* Re: [eiffel-users]</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 08</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/ddSxnBrvhUg">Re: [eiffel-users] Re: Version of EiffelBase2 shipped with different EiffelStudio versions</a>
|
||||
<div class="description">Not really. What I need to know is when I run tests on the performance and suitability for use of EiffelBase2, do I have to repeat them with all of ES versions 15.01, 15.08 and 15.11? On 8 December 2015 at 13:52, Jocelyn Fiat <jf...@eiffelsolution.com> wrote: > As the author of EiffelBase2 does</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 08</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/ddSxnBrvhUg">RE: [eiffel-users] Re: Version of EiffelBase2 shipped with different EiffelStudio versions</a>
|
||||
<div class="description">In this particular case, it is sufficient to take any version of EiffelStudio and the EiffelBase2 version that comes with it to evaluate EiffelBase2 as the library has not really changed much in the past few years. In the general case, you should stick to one version of EiffelStudio and</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 08</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/ddSxnBrvhUg">Re: [eiffel-users] Re: Version of EiffelBase2 shipped with different EiffelStudio versions</a>
|
||||
<div class="description">But this doesn't answer my question. Which version of EiffelBase2 ships with which version of EiffelStudio? On 8 December 2015 at 13:05, Jocelyn Fiat <jf...@eiffel.com> wrote: > Hi all, > > Usually the version uploaded to iron server comes from the associated > branch > for instance > > - 15.01</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 08</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/ddSxnBrvhUg">Re: [eiffel-users] Re: Version of EiffelBase2 shipped with different EiffelStudio versions</a>
|
||||
<div class="description">Hi all, Usually the version uploaded to iron server comes from the associated branch for instance - 15.01 has almost same content as https://svn.eiffel.com/eiffelstudio/branches/Eiffel_15.01/ - ... I said "almost" because, the ecf are modified to use iron references rather than $ISE_LIBRARY/...</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 08</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/ddSxnBrvhUg">Re: [eiffel-users] Re: Version of EiffelBase2 shipped with different EiffelStudio versions</a>
|
||||
<div class="description">As the author of EiffelBase2 does not define any specific versions, I would say - EiffelStudio 14.05 ships EiffelBase2 version 14.05, and associated source code can be found at https://svn.eiffel.com/eiffelstudio/branches/Eiffel_14.05/Src/unstable/library/base2/ (last changed revision: 94979)</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 08</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/-jtyp4iengE">RE: [eiffel-users] Precompile on Linux</a>
|
||||
<div class="description">We currently only ship precompiled libraries for EiffelBase, WEL on Windows and EiffelVision2. The configuration files for those precompiled library are in a read-only folder of the EiffelStudio installation. The first time EiffelStudio tries to access $ISE_PRECOMP (which if not set resolves to</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 08</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/ddSxnBrvhUg">Re: [eiffel-users] Re: Version of EiffelBase2 shipped with different EiffelStudio versions</a>
|
||||
<div class="description">Come on - someone must know - the person who loaded it to iron, e.g. On 7 December 2015 at 12:03, Colin Adams <colinpa...@gmail.com> wrote: > I know what EiffelBase2 is. I was asking about the version in the iron > repository for each version of EiffelStudio. > > On 7 December 2015 at 11:59,</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 08</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/Vwsw67vq6ss">Re: Explaining high-level programming</a>
|
||||
<div class="description">A very interesting article Ian. I learned more by reading it. However, the title leads me to ask: Who (precisely) is the target audience to whom the plea is being made? From my point of view, I cannot see this audience, so I am asking for your view to help me identify the audience. Thanks,</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 08</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/Vwsw67vq6ss">Explaining high-level programming</a>
|
||||
<div class="description">On 4 Dec 2015, at 00:24, Bertrand Meyer <Bertran...@inf.ethz.ch> wrote: 3. The community should help spread Eiffel. The last point is particularly important. The user community needs to grow; if it moves up to the next level then we at Eiffel Software will be able to perform many more</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 08</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/-jtyp4iengE">Precompile on Linux</a>
|
||||
<div class="description">Recently I installed EiffelStudio 15.08 on my Xubuntu laptop, with an assist from Louis M who showed me how to install it using apt-get. In the past, after extracting everything in the .tar.bz archive, I would run the supplied precompile script to produce the precompiled libraries I needed. The</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 07</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/4BS09rbY6UY">RE: [eiffel-users] Maximum capacity of a TUPLE</a>
|
||||
<div class="description">If you are looking at TUPLE with more than 20 entries, I think I would consider using LIST or ARRAY for storing data. The issue is not so much on the TUPLE capacity (which has the same limit as a SPECIAL object) but on the number of class types any type declaration. This is a limit for all</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 07</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/YgyWglEEN3A">Re: [eiffel-users] Does {MEMORY}.collection_off reset the progress of the garbage collector?</a>
|
||||
<div class="description">Thanks, that was what I was looking for.</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Dec 07</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/4BS09rbY6UY">Maximum capacity of a TUPLE</a>
|
||||
<div class="description">The header comment to {TUPLE}.plus says the result can be Void if the result exceeds the capacity of a TUPLE. What is this maximum capacity? (without knowing it is is impossible to prove the result of such a concatenation will be attached).</div>
|
||||
</li>
|
||||
<liv class="nav"><a href="/feed_aggregation/forum">See more ...</a></li>
|
||||
</ul>
|
||||
@@ -0,0 +1,29 @@
|
||||
<!-- Updated: 01/10/2016 12:50:45.000 PM --><div class="feed">
|
||||
<ul>
|
||||
<li>
|
||||
<div class="date"> Jan 11</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/mCVwJQ21QBA">SCOOP & Contracts</a>
|
||||
<div class="description">Recently, I was reading a research paper from a Canadian university talking about applying something like SCOOP to Java. Because Java does not have Design-by-Contract, they wanted to build in a new keyword (like "require") called "await". From the writers point of view, the require contract was</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Jan 11</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/7HR-z0DVklU">General Question: Object Persistence Mechanism</a>
|
||||
<div class="description">NEED: An innate, compiler-known, object persistence mechanism, whereby objects are tracked for version (at design-time), version-updating (at run-time comparative between memory object and persisted object), and attribute change auto-persist. What would be nice is something akin to the Design-by</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Jan 11</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/BouQ6JU-fQ4">RE: [eiffel-users] Solving OOSC 2/E E7.3</a>
|
||||
<div class="description">In addition to Colin's reply, "is" is no longer used, as it is unnecessary. The functions sqrt and atan are in classes SINGLE_MATH or DOUBLE_MATH which are interfaces to the C library functions defined in math.h. Peter Horan -----Original Message----- From: eiffel...@googlegroups.com</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Jan 11</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/BouQ6JU-fQ4">Re: [eiffel-users] Solving OOSC 2/E E7.3</a>
|
||||
<div class="description">The indexing clause has been replaced by the note clause (just a change of keyword). You might try changing the configuration to use transitional syntax instead of standard syntax. On 11 January 2016 at 09:46, <sagyo1...@gmail.com> wrote: > Hi, everyone. > > I'm reading the japanese version of</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date"> Jan 11</div>
|
||||
<a href="https://groups.google.com/d/topic/eiffel-users/BouQ6JU-fQ4">Solving OOSC 2/E E7.3</a>
|
||||
<div class="description">Hi, everyone. I'm reading the japanese version of Object-Oriented Software Construction 2/E. I tried to build the code of "7.5.4 class" in EiffelStudio 15.11 and it raises some build errors. My question is: 1) Is the "indexing description:" description disposed? 2) Is the "function: syntax</div>
|
||||
</li>
|
||||
<liv class="nav"><a href="/feed_aggregation/forum">See more ...</a></li>
|
||||
</ul>
|
||||
@@ -0,0 +1,138 @@
|
||||
<!-- Updated: 01/10/2016 12:50:45.000 PM --><div class="feed">
|
||||
<ul>
|
||||
<li>
|
||||
<div class="date">2015, Nov 17</div>
|
||||
<a href="https://room.eiffel.com/blog/colinadams/some_lazy_data_structures_implemented_in_eiffel_part_i_iterating_the_calkinwilf_tree">Some lazy data structures implemented in Eiffel - Part I - Iterating the Calkin-Wilf tree</a>
|
||||
<div class="description"><p>This is the first part of a series in which I intend to make some explorations of lazy, infinite data structures in Eiffel. If you want to compile the code in these articles, you will need EiffelStudio 15.11 or later.
|
||||
</p><p>In this first article, I am going to iterate an infinite data structure - the strictly-positive rational numbers, represented by an infinite tree - <a href="https://en.wikipedia.org/wiki/Calkin%E2%80%93Wilf_tree" class="external text" title="https://en.wikipedia.org/wiki/Calkin%E2%80%93Wilf_tree">The Calkin-Wilf tree</a>. The easiest way to follow the code is to view it directly on <a href="https://github.com/colin-adams/lazy_eiffel" class="external text" title="https://github.com/colin-adams/lazy_eiffel">GitHub</a>. An alternative is to checkout the repository and compile it in EiffelStudio. To do the latter (instructions are for Linux from a bash terminal, but should be similar for other O/S I think):
|
||||
</p>
|
||||
<ol><li> git clone git@github.com:colin-adams/lazy_eiffel.git
|
||||
</li><li> git checkout V1
|
||||
</li><li> cd lazy_eiffel/examples/calkin_wilf/src
|
||||
</li><li> estudio calkin_wilf.ecf &
|
||||
</li><li> Press OK
|
||||
</li></ol>
|
||||
<p>The first class worth looking at briefly is <span class="geshifilter"><code class="eiffel geshifilter-eiffel">LAZY_BINARY_TREE</code></span>. This represents a single node in an infinite binary tree, together with a link to it's parent, and two <span class="geshifilter"><code class="eiffel geshifilter-eiffel"><a href="http://www.google.com/search?q=site%3Ahttp%3A%2F%2Fdocs.eiffel.com%2Feiffelstudio%2Flibraries+function&btnI=I%27m+Feeling+Lucky"><span style="color: #800000">FUNCTION</span></a></code></span>s to find the left and right children. Incidentally, you may be surprised at the syntax used for declaring these <span class="geshifilter"><code class="eiffel geshifilter-eiffel"><a href="http://www.google.com/search?q=site%3Ahttp%3A%2F%2Fdocs.eiffel.com%2Feiffelstudio%2Flibraries+function&btnI=I%27m+Feeling+Lucky"><span style="color: #800000">FUNCTION</span></a></code></span>s unless you have already read <a href="https://groups.google.com/forum/#!topic/eiffel-users/poTM7aUIa4I" class="external text" title="https://groups.google.com/forum/#!topic/eiffel-users/poTM7aUIa4I">this thread</a>. This is why 15.11 or later is needed to compile the code. I think it's worth showing one of those agents here:
|
||||
</p><p><div class="geshifilter"><div class="eiffel geshifilter-eiffel" style="font-family:monospace;"> left_child_function<span style="color: #600000;">:</span> <a href="http://www.google.com/search?q=site%3Ahttp%3A%2F%2Fdocs.eiffel.com%2Feiffelstudio%2Flibraries+function&btnI=I%27m+Feeling+Lucky"><span style="color: #800000">FUNCTION</span></a> <span style="color: #FF0000;">[</span>LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>G<span style="color: #FF0000;">]</span>, LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>G<span style="color: #FF0000;">]</span><span style="color: #FF0000;">]</span></div></div>
|
||||
</p><p>This syntax is starting to look lightweight. Looking quite comparable to Haskell, for example (<span class="geshifilter"><code class="text geshifilter-text">leftChildFunction :: LazyBinaryTree a -> LazyBinaryTree a</code></span>), and none of that horrible camelCase.
|
||||
</p><p>Then let's look at the <span class="geshifilter"><code class="eiffel geshifilter-eiffel">CALKIN_WILF</code></span> tree itself. The core of the class is a root node, two functions to navigate from any node in the tree to the left and right children (or to lazily build the tree structure, depending on how you want to look at it), and a creation procedure to initialize root to 1/1.
|
||||
</p><p><div class="geshifilter"><div class="eiffel geshifilter-eiffel" style="font-family:monospace;"><span style="color: #0600FF; font-weight: bold;">feature</span> <span style="color: #FF0000;">{</span><a href="http://www.google.com/search?q=site%3Ahttp%3A%2F%2Fdocs.eiffel.com%2Feiffelstudio%2Flibraries+none&btnI=I%27m+Feeling+Lucky"><span style="color: #800000">NONE</span></a><span style="color: #FF0000;">}</span> <span style="color: #008000; font-style: italic;">-- Initialization</span><br />
|
||||
<br />
|
||||
make<br />
|
||||
<span style="color: #008000; font-style: italic;">-- Create `root'.</span><br />
|
||||
<span style="color: #0600FF; font-weight: bold;">do</span><br />
|
||||
left_child_agent<6E><span style="color: #600000;">:=</span> <span style="color: #0600FF; font-weight: bold;">agent</span> left_child<br />
|
||||
right_child_agent<6E><span style="color: #600000;">:=</span> <span style="color: #0600FF; font-weight: bold;">agent</span> right_child<br />
|
||||
<span style="color: #0600FF; font-weight: bold;">create</span> <span style="color: #603000;">root</span>.<span style="color: #000060;">make</span> <span style="color: #FF0000;">(</span><span style="color: #FF0000;">(</span><span style="color: #0600FF; font-weight: bold;">create</span> <span style="color: #FF0000;">{</span>RATIONAL_NUMBER<span style="color: #FF0000;">}</span>.<span style="color: #000060;">make</span> <span style="color: #FF0000;">(</span><span style="color: #FF0000;">1</span>, <span style="color: #FF0000;">1</span><span style="color: #FF0000;">)</span><span style="color: #FF0000;">)</span>, <span style="color: #800080;">Void</span>, left_child_agent, right_child_agent<span style="color: #FF0000;">)</span><br />
|
||||
start<br />
|
||||
<span style="color: #0600FF; font-weight: bold;">end</span><br />
|
||||
<br />
|
||||
<span style="color: #0600FF; font-weight: bold;">feature</span> <span style="color: #008000; font-style: italic;">-- Access</span><br />
|
||||
<br />
|
||||
<span style="color: #603000;">root</span><span style="color: #600000;">:</span> LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>RATIONAL_NUMBER<span style="color: #FF0000;">]</span><br />
|
||||
<span style="color: #008000; font-style: italic;">-- 1/1</span><br />
|
||||
<br />
|
||||
left_child_agent<span style="color: #600000;">:</span> <a href="http://www.google.com/search?q=site%3Ahttp%3A%2F%2Fdocs.eiffel.com%2Feiffelstudio%2Flibraries+function&btnI=I%27m+Feeling+Lucky"><span style="color: #800000">FUNCTION</span></a> <span style="color: #FF0000;">[</span>LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>RATIONAL_NUMBER<span style="color: #FF0000;">]</span>, LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>RATIONAL_NUMBER<span style="color: #FF0000;">]</span><span style="color: #FF0000;">]</span><br />
|
||||
<span style="color: #008000; font-style: italic;">-- Function from a node to its left child</span><br />
|
||||
<br />
|
||||
right_child_agent<span style="color: #600000;">:</span> <a href="http://www.google.com/search?q=site%3Ahttp%3A%2F%2Fdocs.eiffel.com%2Feiffelstudio%2Flibraries+function&btnI=I%27m+Feeling+Lucky"><span style="color: #800000">FUNCTION</span></a> <span style="color: #FF0000;">[</span>LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>RATIONAL_NUMBER<span style="color: #FF0000;">]</span>, LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>RATIONAL_NUMBER<span style="color: #FF0000;">]</span><span style="color: #FF0000;">]</span><br />
|
||||
<span style="color: #008000; font-style: italic;">-- Function from a node to its left child</span><br />
|
||||
<br />
|
||||
left_child <span style="color: #FF0000;">(</span>a_node<span style="color: #600000;">:</span> LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>RATIONAL_NUMBER<span style="color: #FF0000;">]</span><span style="color: #FF0000;">)</span><span style="color: #600000;">:</span> LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>RATIONAL_NUMBER<span style="color: #FF0000;">]</span><br />
|
||||
<span style="color: #008000; font-style: italic;">-- Left child of `a_node'</span><br />
|
||||
<span style="color: #0600FF; font-weight: bold;">do</span><br />
|
||||
<span style="color: #0600FF; font-weight: bold;">create</span> <span style="color: #800080;">Result</span>.<span style="color: #000060;">make</span> <span style="color: #FF0000;">(</span><span style="color: #0600FF; font-weight: bold;">create</span> <span style="color: #FF0000;">{</span>RATIONAL_NUMBER<span style="color: #FF0000;">}</span>.<span style="color: #000060;">make</span> <span style="color: #FF0000;">(</span><br />
|
||||
a_node.<span style="color: #000060;">item</span>.<span style="color: #000060;">numerator</span>, a_node.<span style="color: #000060;">item</span>.<span style="color: #000060;">numerator</span> <span style="color: #600000;">+</span> a_node.<span style="color: #000060;">item</span>.<span style="color: #000060;">denominator</span><span style="color: #FF0000;">)</span>,<br />
|
||||
a_node, left_child_agent, right_child_agent<span style="color: #FF0000;">)</span><br />
|
||||
<span style="color: #0600FF; font-weight: bold;">end</span><br />
|
||||
<br />
|
||||
right_child <span style="color: #FF0000;">(</span>a_node<span style="color: #600000;">:</span> LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>RATIONAL_NUMBER<span style="color: #FF0000;">]</span><span style="color: #FF0000;">)</span><span style="color: #600000;">:</span> LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>RATIONAL_NUMBER<span style="color: #FF0000;">]</span><br />
|
||||
<span style="color: #008000; font-style: italic;">-- Right child of `a_node'</span><br />
|
||||
<span style="color: #0600FF; font-weight: bold;">do</span><br />
|
||||
<span style="color: #0600FF; font-weight: bold;">create</span> <span style="color: #800080;">Result</span>.<span style="color: #000060;">make</span> <span style="color: #FF0000;">(</span><span style="color: #0600FF; font-weight: bold;">create</span> <span style="color: #FF0000;">{</span>RATIONAL_NUMBER<span style="color: #FF0000;">}</span>.<span style="color: #000060;">make</span> <span style="color: #FF0000;">(</span><br />
|
||||
a_node.<span style="color: #000060;">item</span>.<span style="color: #000060;">numerator</span> <span style="color: #600000;">+</span> a_node.<span style="color: #000060;">item</span>.<span style="color: #000060;">denominator</span>, a_node.<span style="color: #000060;">item</span>.<span style="color: #000060;">denominator</span><span style="color: #FF0000;">)</span>,<br />
|
||||
a_node, left_child_agent, right_child_agent<span style="color: #FF0000;">)</span><br />
|
||||
<span style="color: #0600FF; font-weight: bold;">end</span></div></div>
|
||||
</p><p>However, I muddled this nice little picture by inheriting from <span class="geshifilter"><code class="eiffel geshifilter-eiffel"><a href="http://www.google.com/search?q=site%3Ahttp%3A%2F%2Fdocs.eiffel.com%2Feiffelstudio%2Flibraries+linear&btnI=I%27m+Feeling+Lucky"><span style="color: #800000">LINEAR</span></a> <span style="color: #FF0000;">[</span>LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>RATIONAL_NUMBER<span style="color: #FF0000;">]</span><span style="color: #FF0000;">]</span></code></span>. So the class <span class="geshifilter"><code class="eiffel geshifilter-eiffel">CALKIN_WILF</code></span> <em>has</em> a lazy tree of rationals, and <em>is</em> a linear iteration of them. In the root class <span class="geshifilter"><code class="eiffel geshifilter-eiffel">CALKIN_WILF_DEMO_ROOT</code></span> we simply print the first 100 rational numbers (I could have made the program take an argument) using a <span class="geshifilter"><code class="eiffel geshifilter-eiffel"><a href="http://www.google.com/search?q=site%3Ahttp%3A%2F%2Fdocs.eiffel.com%2Feiffelstudio%2Flibraries+linear_iterator&btnI=I%27m+Feeling+Lucky"><span style="color: #800000">LINEAR_ITERATOR</span></a> <span style="color: #FF0000;">[</span>LAZY_BINARY_TREE <span style="color: #FF0000;">[</span>RATIONAL_NUMBER<span style="color: #FF0000;">]</span><span style="color: #FF0000;">]</span></code></span>. However, the iteration is <em>not</em> in numerical order. In a future post we'll see other ways of iterating the rationals.
|
||||
</p><p>The really interesting thing (to me) about the Calkin-Wilf tree is the way I did a breadth-first traversal of this infinite tree. It turns out that the index in the linear structure, when translated into binary, can be considered as a set of instructions to move through the tree. You ignore all leading zeros. At the first one, move to the root. Then every time you see a zero, you take the left child, and every time you see a one, you take the right child. Lovely!
|
||||
</p></div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date">2015, Sep 15</div>
|
||||
<a href="http://feedproxy.google.com/~r/BertrandMeyer/~3/vAyEwWESHTY/">Design by Contract: ACM Webinar this Thursday</a>
|
||||
<div class="description"><p>A third ACM webinar this year (after two on agile methods): I will be providing a general introduction to Design by Contract. The date is this coming Thursday, September 17, and the time is noon New York (18 Paris/Zurich, 17 London, 9 Los Angeles, see here for hours elsewhere). Please tune in! The event is […]</p>
|
||||
<p>The post <a rel="nofollow" href="https://bertrandmeyer.com/2015/09/15/design-by-contract-acm-webinar-this-thursday/">Design by Contract: ACM Webinar this Thursday</a> appeared first on <a rel="nofollow" href="https://bertrandmeyer.com">Bertrand Meyer's technology+ blog</a>.</p></div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date">2015, Jan 21</div>
|
||||
<a href="http://feedproxy.google.com/~r/BertrandMeyer/~3/zNoU82qSoBU/">Framing the frame problem (new paper)</a>
|
||||
<div class="description"><p>Among the open problems of verification, particularly the verification of object-oriented programs, one of the most vexing is framing: how to specify and verify what programs element do not change. Continuing previous work, this article presents a “double frame inference” method, automatic on both sides the specification and verification sides. There is no need to […]</p>
|
||||
<p>The post <a rel="nofollow" href="https://bertrandmeyer.com/2015/01/21/framing-the-frame-problem-new-paper/">Framing the frame problem (new paper)</a> appeared first on <a rel="nofollow" href="https://bertrandmeyer.com">Bertrand Meyer's technology+ blog</a>.</p></div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date">2015, Jan 21</div>
|
||||
<a href="http://feedproxy.google.com/~r/BertrandMeyer/~3/gYfn3TjKVzA/">Detecting deadlock automatically? (New paper)</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="date">2015, Jan 11</div>
|
||||
<a href="https://room.eiffel.com/blog/conaclos/a_colored_year_on_the_web_for_eiffel">A colored year on the web for Eiffel</a>
|
||||
<div class="description"><p>Happy new year!
|
||||
</p><p>ACE, Prism, and Rouge now support the syntax highlighting of the Eiffel language.
|
||||
</p>
|
||||
<table id="toc" class="toc" summary="Contents"><tr><td><div id="toctitle"><strong>Contents</strong></div>
|
||||
<div id="tocbody">
|
||||
<ul>
|
||||
<li class="toclevel-1"><a href="#Prism"><span class="tocnumber">1</span> <span class="toctext">Prism</span></a></li>
|
||||
<li class="toclevel-1"><a href="#ACE_editor"><span class="tocnumber">2</span> <span class="toctext">ACE editor</span></a></li>
|
||||
<li class="toclevel-1"><a href="#Rouge"><span class="tocnumber">3</span> <span class="toctext">Rouge</span></a></li>
|
||||
<li class="toclevel-1"><a href="#Other_syntax_highlighters"><span class="tocnumber">4</span> <span class="toctext">Other syntax highlighters</span></a></li>
|
||||
</ul></div>
|
||||
</td></tr></table><a name="Prism"></a><h2> <span class="mw-headline"> Prism </span></h2>
|
||||
<p><a href="http://prismjs.com/" class="external text" title="http://prismjs.com/">Prism</a> is by-design a lightweight syntax highlighter for the web.
|
||||
It is very simple to use, and the <a href="http://prismjs.com/download.html" class="external text" title="http://prismjs.com/download.html">download page</a> enables to get only what you need. It proposes interesting add-ons (Line numbers, File Highlight, ...).
|
||||
</p><p>The library is usable with a bench of tools, including <a href="https://wordpress.org/" class="external text" title="https://wordpress.org/">Wordpress</a> and <a href="http://jekyllrb.com/" class="external text" title="http://jekyllrb.com/">Jekyll</a>.
|
||||
</p><p>You can test the highlighting <a href="http://prismjs.com/test.html" class="external text" title="http://prismjs.com/test.html">here</a>. The Eiffel support should be full, including the verbatim options.
|
||||
</p><p>If you note any issues, you can report it on my <a href="https://github.com/Conaclos/prism" class="external text" title="https://github.com/Conaclos/prism">github fork</a>.
|
||||
</p><p>I have commited a <a href="https://github.com/PrismJS/prism/pull/471" class="external text" title="https://github.com/PrismJS/prism/pull/471">Pull Request</a> to enable class name highlighting.
|
||||
Please feel free to express your support in the discussion thread of the <a href="https://github.com/PrismJS/prism/pull/471" class="external text" title="https://github.com/PrismJS/prism/pull/471">Pull Request</a>.
|
||||
The PR is waiitng for author agreement since January...
|
||||
</p>
|
||||
<a name="ACE_editor"></a><h2> <span class="mw-headline"> ACE editor </span></h2>
|
||||
<p><a href="http://ace.c9.io/" class="external text" title="http://ace.c9.io/">ACE Editor</a> is certainly the most use web-based code editor.
|
||||
A bench of web applications use ACE including:
|
||||
</p>
|
||||
<ul><li> the <a href="https://help.github.com/articles/editing-files-in-your-repository/" class="external text" title="https://help.github.com/articles/editing-files-in-your-repository/">Github web editor</a>
|
||||
</li><li> the <a href="https://c9.io/" class="external text" title="https://c9.io/">Cloud9 web IDE</a>
|
||||
</li><li> <a href="http://codecombat.com/" class="external text" title="http://codecombat.com/">Code Combat</a>, a game to learn programming basis
|
||||
</li><li> and <a href="https://ace.c9.io/#nav=production" class="external text" title="https://ace.c9.io/#nav=production">more</a>
|
||||
</li></ul>
|
||||
<p>The support of Eiffel is not full. In particular, the verbatim options and multiple-line strings are not supported.
|
||||
</p><p>If you note any issues, you can report it on my <a href="https://github.com/Conaclos/ace" class="external text" title="https://github.com/Conaclos/ace">github fork</a>.
|
||||
</p>
|
||||
<a name="Rouge"></a><h2> <span class="mw-headline"> Rouge </span></h2>
|
||||
<p>Rouge is a recent syntax highlighter increasing in popularity.
|
||||
It is compatible with the stylesheets of Pygments.
|
||||
</p><p>It is used by:
|
||||
</p>
|
||||
<ul><li> <a href="https://about.gitlab.com/" class="external text" title="https://about.gitlab.com/">Gitlab</a>
|
||||
</li><li> <a href="http://kramdown.gettalong.org/" class="external text" title="http://kramdown.gettalong.org/">krandown</a>, a markdown parser (anoption must be enabled)
|
||||
</li><li> <a href="https://github.com/vmg/redcarpet" class="external text" title="https://github.com/vmg/redcarpet">RedCarpet</a>, another markdown parser
|
||||
</li><li> the static site builder <a href="https://middlemanapp.com/" class="external text" title="https://middlemanapp.com/">Middleman</a>
|
||||
</li></ul>
|
||||
<p>The support of Eiffel is not full. In particular, the verbatim options are not supported.
|
||||
</p><p>If you note any issues, you can report it on my <a href="https://github.com/Conaclos/rouge" class="external text" title="https://github.com/Conaclos/rouge">github fork</a>.
|
||||
</p>
|
||||
<a name="Other_syntax_highlighters"></a><h2> <span class="mw-headline"> Other syntax highlighters </span></h2>
|
||||
<p>Some tools need updates in order to fully support the highlighting of Eiffel.
|
||||
For instance:
|
||||
</p>
|
||||
<ul><li> <a href="https://codemirror.net/" class="external text" title="https://codemirror.net/">Code Mirror</a>, a web-based code editor
|
||||
</li><li> <a href="http://pygments.org/" class="external text" title="http://pygments.org/">Pygments</a>
|
||||
</li><li> <a href="https://github.com/textmate/eiffel.tmbundle" class="external text" title="https://github.com/textmate/eiffel.tmbundle">TexMate</a>, (used also by <a href="https://github.com/github/linguist" class="external text" title="https://github.com/github/linguist">Github Linguist</a>
|
||||
</li><li> <a href="http://qbnz.com/highlighter/" class="external text" title="http://qbnz.com/highlighter/">GeSHi</a>
|
||||
</li></ul>
|
||||
<p>Enjoy ;)
|
||||
</p></div>
|
||||
</li>
|
||||
<liv class="nav"><a href="/feed_aggregation/news">See more ...</a></li>
|
||||
</ul>
|
||||
0
examples/demo/site/files/uploaded_files/Readme.md
Normal file
0
examples/demo/site/files/uploaded_files/Readme.md
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
CREATE TABLE session_auth (
|
||||
`uid` INTEGER PRIMARY KEY NOT NULL CHECK(`uid`>=0),
|
||||
`access_token` TEXT NOT NULL,
|
||||
`created` DATETIME NOT NULL,
|
||||
CONSTRAINT `uid`
|
||||
UNIQUE(`uid`),
|
||||
CONSTRAINT `access_token`
|
||||
UNIQUE(`access_token`)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<div class="primary-tabs">
|
||||
{unless isset="$user"}
|
||||
<h3>Login or <a href="{$site_url/}account/roc-register">Register</a></h3>
|
||||
<div>
|
||||
<div>
|
||||
<form name="cms_session_auth" action="{$site_url/}account/login-with-session" method="POST">
|
||||
<div>
|
||||
<input type="text" name="username" id="username" required value="{$username/}">
|
||||
<label>Username</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="password" name="password" id="password" required >
|
||||
<label>Password</label>
|
||||
</div>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
<a href="{$site_url/}account/new-password">Forgot password?</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/unless}
|
||||
{if isset=$error}
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
<strong>{$error/}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -89,6 +89,13 @@ feature -- CMS modules
|
||||
|
||||
create {GOOGLE_CUSTOM_SEARCH_MODULE} m.make
|
||||
a_setup.register_module (m)
|
||||
|
||||
create {CMS_SESSION_AUTH_MODULE} m.make
|
||||
a_setup.register_module (m)
|
||||
|
||||
-- uploader
|
||||
create {CMS_FILE_UPLOADER_MODULE} m.make
|
||||
a_setup.register_module (m)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -9,10 +9,10 @@ class
|
||||
inherit
|
||||
CMS_MODULE
|
||||
redefine
|
||||
setup_hooks
|
||||
setup_hooks,
|
||||
permissions
|
||||
end
|
||||
|
||||
|
||||
CMS_HOOK_AUTO_REGISTER
|
||||
|
||||
CMS_HOOK_VALUE_TABLE_ALTER
|
||||
@@ -52,6 +52,13 @@ feature -- Access
|
||||
|
||||
name: STRING = "auth"
|
||||
|
||||
permissions: LIST [READABLE_STRING_8]
|
||||
-- List of permission ids, used by this module, and declared.
|
||||
do
|
||||
Result := Precursor
|
||||
Result.force ("account register")
|
||||
end
|
||||
|
||||
feature -- Access: docs
|
||||
|
||||
root_dir: PATH
|
||||
@@ -228,6 +235,7 @@ feature -- Handler
|
||||
end
|
||||
else
|
||||
create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
||||
r.set_main_content ("You can also contact the webmaster to ask for an account.")
|
||||
end
|
||||
|
||||
r.execute
|
||||
|
||||
69
modules/file_upload/cms_file.e
Normal file
69
modules/file_upload/cms_file.e
Normal file
@@ -0,0 +1,69 @@
|
||||
note
|
||||
description: "Interface representing any files under `{CMS_API}.files_location' ."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_FILE
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initializaion
|
||||
|
||||
make (a_relative_path: PATH; a_api: CMS_API)
|
||||
do
|
||||
cms_api := a_api
|
||||
location := a_relative_path
|
||||
end
|
||||
|
||||
cms_api: CMS_API
|
||||
|
||||
feature -- Access
|
||||
|
||||
filename: STRING_32
|
||||
-- File name of Current file.
|
||||
local
|
||||
p: PATH
|
||||
do
|
||||
p := location
|
||||
if attached p.entry as e then
|
||||
Result := e.name
|
||||
else
|
||||
Result := p.name
|
||||
end
|
||||
end
|
||||
|
||||
location: PATH
|
||||
-- Path relative the `CMS_API.files_location'.
|
||||
|
||||
owner: detachable CMS_USER
|
||||
-- Optional owner.
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_directory: BOOLEAN
|
||||
local
|
||||
d: DIRECTORY
|
||||
do
|
||||
create d.make_with_path (cms_api.files_location.extended_path (location))
|
||||
Result := d.exists
|
||||
end
|
||||
|
||||
is_file: BOOLEAN
|
||||
local
|
||||
f: RAW_FILE
|
||||
do
|
||||
create f.make_with_path (cms_api.files_location.extended_path (location))
|
||||
Result := f.exists
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_owner (u: detachable CMS_USER)
|
||||
-- Set `owner' to `u'.
|
||||
do
|
||||
owner := u
|
||||
end
|
||||
|
||||
end
|
||||
70
modules/file_upload/cms_file_upload_file.e
Normal file
70
modules/file_upload/cms_file_upload_file.e
Normal file
@@ -0,0 +1,70 @@
|
||||
note
|
||||
description: "Summary description for {CMS_FILE_UPLOAD_FILE}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_FILE_UPLOAD_FILE
|
||||
|
||||
inherit
|
||||
WSF_UPLOADED_FILE
|
||||
rename
|
||||
make as make_uploaded_file,
|
||||
name as uploaded_file_name,
|
||||
change_name as change_uploaded_file_name,
|
||||
is_empty as is_empty_uploaded_file,
|
||||
exists as uploaded_file_exists
|
||||
end
|
||||
|
||||
RAW_FILE
|
||||
rename
|
||||
make as make_file
|
||||
end
|
||||
-- undefine
|
||||
-- make, change_name, is_empty, exists
|
||||
-- end
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Initialization
|
||||
|
||||
make (a_name: READABLE_STRING_GENERAL; a_filename: READABLE_STRING_GENERAL; a_content_type: like content_type; a_size: like size; a_user: CMS_USER)
|
||||
local
|
||||
time: DATE_TIME
|
||||
do
|
||||
make_uploaded_file (a_name, a_filename, a_content_type, a_size)
|
||||
make_with_name (a_filename)
|
||||
|
||||
uploaded_file_name := a_name.as_string_32
|
||||
url_encoded_name := url_encoded_string (a_name)
|
||||
filename := a_filename.as_string_32
|
||||
content_type := a_content_type
|
||||
size := a_size
|
||||
|
||||
create time.make_now_utc
|
||||
set_uploaded_time (time)
|
||||
set_uploaded_by (a_user)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
uploaded_by: CMS_USER
|
||||
-- user who has uploaded the file
|
||||
|
||||
uploaded_time: DATE_TIME
|
||||
-- time and date when file was uploaded
|
||||
|
||||
feature -- Setter functions
|
||||
|
||||
set_uploaded_by (a_user: CMS_USER)
|
||||
do
|
||||
uploaded_by := a_user
|
||||
end
|
||||
|
||||
set_uploaded_time (a_time: DATE_TIME)
|
||||
do
|
||||
uploaded_time := a_time
|
||||
end
|
||||
end
|
||||
145
modules/file_upload/cms_file_upload_file_system_handler.e
Normal file
145
modules/file_upload/cms_file_upload_file_system_handler.e
Normal file
@@ -0,0 +1,145 @@
|
||||
note
|
||||
description: "Summary description for {CMS_FILE_UPLOAD_FILE_SYSTEM_HANDLER}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_FILE_UPLOAD_FILE_SYSTEM_HANDLER
|
||||
|
||||
inherit
|
||||
WSF_FILE_SYSTEM_HANDLER
|
||||
redefine
|
||||
process_index
|
||||
end
|
||||
|
||||
create
|
||||
make_with_p
|
||||
|
||||
feature -- Initialization
|
||||
|
||||
make_with_p (d: like document_root)
|
||||
do
|
||||
if d.is_empty then
|
||||
document_root := execution_environment.current_working_path
|
||||
else
|
||||
document_root := d
|
||||
end
|
||||
ensure
|
||||
not document_root.is_empty
|
||||
end
|
||||
|
||||
feature -- process function
|
||||
|
||||
process_index (a_uri: READABLE_STRING_8; dn: PATH; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
h: HTTP_HEADER
|
||||
uri, s: STRING_8
|
||||
d: DIRECTORY
|
||||
l_files: LIST [PATH]
|
||||
p: PATH
|
||||
n: READABLE_STRING_32
|
||||
httpdate: HTTP_DATE
|
||||
pf: RAW_FILE
|
||||
l_is_dir: BOOLEAN
|
||||
do
|
||||
create d.make_with_path (dn)
|
||||
d.open_read
|
||||
if attached directory_index_file (d) as f then
|
||||
process_file (f, req, res)
|
||||
else
|
||||
uri := a_uri
|
||||
if not uri.is_empty and then uri [uri.count] /= '/' then
|
||||
uri.append_character ('/')
|
||||
end
|
||||
s := "[
|
||||
<html>
|
||||
<head>
|
||||
<title>Index of $URI</title>
|
||||
<style>
|
||||
td { padding-left: 10px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Index of $URI</h1>
|
||||
<table>
|
||||
<tr><th/><th>Name</th><th>Last modified</th><th>Size</th></tr>
|
||||
<tr><th colspan="4"><hr></th></tr>
|
||||
]"
|
||||
s.replace_substring_all ("$URI", uri)
|
||||
|
||||
from
|
||||
l_files := d.entries
|
||||
l_files.start
|
||||
until
|
||||
l_files.after
|
||||
loop
|
||||
p := l_files.item
|
||||
if ignoring_index_entry (p) then
|
||||
|
||||
else
|
||||
n := p.name
|
||||
create pf.make_with_path (dn.extended_path (p))
|
||||
if pf.exists and then pf.is_directory then
|
||||
l_is_dir := True
|
||||
else
|
||||
l_is_dir := False
|
||||
end
|
||||
|
||||
s.append ("<tr><td>")
|
||||
if l_is_dir then
|
||||
s.append ("[dir]")
|
||||
else
|
||||
s.append (" ")
|
||||
end
|
||||
s.append ("</td>")
|
||||
s.append ("<td><a href=%"" + uri)
|
||||
url_encoder.append_percent_encoded_string_to (n, s)
|
||||
s.append ("%">")
|
||||
if p.is_parent_symbol then
|
||||
s.append ("[Parent Directory] ..")
|
||||
else
|
||||
s.append (html_encoder.encoded_string (n))
|
||||
end
|
||||
if l_is_dir then
|
||||
s.append ("/")
|
||||
end
|
||||
|
||||
s.append ("</td>")
|
||||
s.append ("<td>")
|
||||
if pf.exists then
|
||||
create httpdate.make_from_date_time (file_date (pf))
|
||||
httpdate.append_to_rfc1123_string (s)
|
||||
end
|
||||
s.append ("</td>")
|
||||
s.append ("<td>")
|
||||
if not l_is_dir and pf.exists then
|
||||
s.append_integer (file_size (pf))
|
||||
end
|
||||
s.append ("</td>")
|
||||
s.append ("</tr>")
|
||||
end
|
||||
l_files.forth
|
||||
end
|
||||
s.append ("[
|
||||
<tr><th colspan="4"><hr></th></tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
]"
|
||||
)
|
||||
|
||||
create h.make
|
||||
h.put_content_type_text_html
|
||||
res.set_status_code ({HTTP_STATUS_CODE}.ok)
|
||||
h.put_content_length (s.count)
|
||||
res.put_header_lines (h)
|
||||
if not req.request_method.same_string ({HTTP_REQUEST_METHODS}.method_head) then
|
||||
res.put_string (s)
|
||||
end
|
||||
res.flush
|
||||
end
|
||||
d.close
|
||||
end
|
||||
|
||||
end
|
||||
79
modules/file_upload/cms_file_uploader_api.e
Normal file
79
modules/file_upload/cms_file_uploader_api.e
Normal file
@@ -0,0 +1,79 @@
|
||||
note
|
||||
description: "API to manage files."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_FILE_UPLOADER_API
|
||||
|
||||
inherit
|
||||
CMS_MODULE_API
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Access
|
||||
|
||||
uploads_directory_name: STRING = "uploaded_files"
|
||||
|
||||
uploads_location: PATH
|
||||
do
|
||||
Result := cms_api.files_location.extended (uploads_directory_name)
|
||||
end
|
||||
|
||||
file_link (f: CMS_FILE): CMS_LOCAL_LINK
|
||||
local
|
||||
s: STRING
|
||||
do
|
||||
s := "files"
|
||||
across
|
||||
f.location.components as ic
|
||||
loop
|
||||
s.append_character ('/')
|
||||
s.append (percent_encoded (ic.item.name))
|
||||
end
|
||||
create Result.make (f.filename, s)
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_file (p: PATH): CMS_FILE
|
||||
do
|
||||
create Result.make (p, cms_api)
|
||||
end
|
||||
|
||||
new_uploads_file (p: PATH): CMS_FILE
|
||||
-- New uploaded path from `p' related to `uploads_location'.
|
||||
do
|
||||
create Result.make ((create {PATH}.make_from_string (uploads_directory_name)).extended_path (p), cms_api)
|
||||
end
|
||||
|
||||
feature -- Storage
|
||||
|
||||
save_uploaded_file (f: CMS_UPLOADED_FILE)
|
||||
local
|
||||
p: PATH
|
||||
ut: FILE_UTILITIES
|
||||
stored: BOOLEAN
|
||||
do
|
||||
reset_error
|
||||
p := f.location
|
||||
if p.is_absolute then
|
||||
else
|
||||
p := uploads_location.extended_path (p)
|
||||
end
|
||||
if ut.file_path_exists (p) then
|
||||
-- FIXME: find an alternative name for it, by appending "-" + i.out , with i: INTEGER;
|
||||
error_handler.add_custom_error (-1, "uploaded file storage failed", "A file with same name already exists!")
|
||||
else
|
||||
-- move file to path
|
||||
stored := f.move_to (p)
|
||||
if not stored then
|
||||
error_handler.add_custom_error (-1, "uploaded file storage failed", "Issue occurred when saving uploaded file!")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
345
modules/file_upload/cms_file_uploader_module.e
Normal file
345
modules/file_upload/cms_file_uploader_module.e
Normal file
@@ -0,0 +1,345 @@
|
||||
note
|
||||
description: "file_upload application root class"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_FILE_UPLOADER_MODULE
|
||||
|
||||
inherit
|
||||
CMS_MODULE
|
||||
rename
|
||||
module_api as file_upload_api
|
||||
redefine
|
||||
install,
|
||||
initialize,
|
||||
setup_hooks,
|
||||
permissions,
|
||||
file_upload_api
|
||||
end
|
||||
|
||||
CMS_HOOK_MENU_SYSTEM_ALTER
|
||||
|
||||
SHARED_EXECUTION_ENVIRONMENT
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
do
|
||||
name := "file_uploader"
|
||||
version := "1.0"
|
||||
description := "Service to upload files, and manage them."
|
||||
package := "file"
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: STRING
|
||||
|
||||
permissions: LIST [READABLE_STRING_8]
|
||||
-- List of permission ids, used by this module, and declared.
|
||||
do
|
||||
Result := Precursor
|
||||
Result.force ("admin uploaded files")
|
||||
Result.force ("upload files")
|
||||
end
|
||||
|
||||
feature {CMS_API} -- Module Initialization
|
||||
|
||||
initialize (api: CMS_API)
|
||||
-- <Precursor>
|
||||
do
|
||||
Precursor (api)
|
||||
if file_upload_api = Void then
|
||||
create file_upload_api.make (api)
|
||||
end
|
||||
end
|
||||
|
||||
feature {CMS_API}-- Module management
|
||||
|
||||
install (api: CMS_API)
|
||||
-- install the module
|
||||
local
|
||||
sql: STRING
|
||||
l_file_upload_api: like file_upload_api
|
||||
d: DIRECTORY
|
||||
do
|
||||
-- create a database table
|
||||
if attached {CMS_STORAGE_SQL_I} api.storage as l_sql_storage then
|
||||
|
||||
-- FIXME: This is not used, is it planned in the future?
|
||||
|
||||
if not l_sql_storage.sql_table_exists ("file_upload_table") then
|
||||
sql := "[
|
||||
CREATE TABLE file_upload_table(
|
||||
`id` INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL CHECK("id">=0),
|
||||
`name` VARCHAR(100) NOT NULL,
|
||||
`uploaded_date` DATE,
|
||||
`size` INTEGER
|
||||
);
|
||||
]"
|
||||
l_sql_storage.sql_execute_script (sql, Void)
|
||||
if l_sql_storage.has_error then
|
||||
api.logger.put_error ("Could not initialize database for file uploader module", generating_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
create l_file_upload_api.make (api)
|
||||
create d.make_with_path (l_file_upload_api.uploads_location)
|
||||
if not d.exists then
|
||||
d.recursive_create_dir
|
||||
end
|
||||
file_upload_api := l_file_upload_api
|
||||
Precursor (api)
|
||||
end
|
||||
|
||||
feature {CMS_API} -- Access: API
|
||||
|
||||
file_upload_api: detachable CMS_FILE_UPLOADER_API
|
||||
-- <Precursor>
|
||||
|
||||
feature -- Access: router
|
||||
|
||||
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
|
||||
-- <Precursor>
|
||||
local
|
||||
-- www: WSF_FILE_SYSTEM_HANDLER
|
||||
do
|
||||
map_uri_template_agent (a_router, "/upload/", agent execute_upload (?, ?, a_api), Void) -- Accepts any method GET, HEAD, POST, PUT, DELETE, ...
|
||||
map_uri_template_agent (a_router, "/upload/{filename}", agent display_uploaded_file_info (?, ?, a_api), a_router.methods_get)
|
||||
|
||||
-- create www.make_with_path (document_root)
|
||||
-- www.set_directory_index (<<"index.html">>)
|
||||
-- www.set_not_found_handler (agent execute_not_found_handler)
|
||||
-- a_router.handle("", www, a_router.methods_get)
|
||||
end
|
||||
|
||||
feature -- Hooks
|
||||
|
||||
setup_hooks (a_hooks: CMS_HOOK_CORE_MANAGER)
|
||||
do
|
||||
a_hooks.subscribe_to_menu_system_alter_hook (Current)
|
||||
end
|
||||
|
||||
menu_system_alter (a_menu_system: CMS_MENU_SYSTEM; a_response: CMS_RESPONSE)
|
||||
local
|
||||
link: CMS_LOCAL_LINK
|
||||
do
|
||||
-- login in demo did somehow not work
|
||||
-- if a_response.has_permission ("upload files") then
|
||||
create link.make ("Upload", "upload/")
|
||||
a_menu_system.primary_menu.extend (link)
|
||||
-- end
|
||||
end
|
||||
|
||||
--feature -- Configuration
|
||||
|
||||
-- document_root: PATH
|
||||
-- -- Document root to look for files or directories
|
||||
-- once
|
||||
-- Result := execution_environment.current_working_path.extended ("site")
|
||||
-- end
|
||||
|
||||
-- files_root: PATH
|
||||
-- -- Uploaded files will be stored in `files_root' folder
|
||||
-- local
|
||||
-- tmp: PATH
|
||||
-- once
|
||||
-- tmp := document_root.extended ("files")
|
||||
-- Result := tmp.extended ("uploaded_files")
|
||||
-- end
|
||||
|
||||
feature -- Handler
|
||||
|
||||
execute_not_found_handler (uri: READABLE_STRING_8; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- `uri' is not found, redirect to default page
|
||||
do
|
||||
res.redirect_now_with_content (req.script_url ("/"), uri + ": not found. %N Redirectioin to" + req.script_url ("/"), "text/html")
|
||||
end
|
||||
|
||||
display_uploaded_file_info (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API)
|
||||
-- Display information related to a cms uploaded file.
|
||||
local
|
||||
body: STRING_8
|
||||
r: CMS_RESPONSE
|
||||
f: CMS_FILE
|
||||
fn: READABLE_STRING_32
|
||||
do
|
||||
check req.is_get_request_method end
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
||||
|
||||
create body.make_empty
|
||||
if attached {WSF_STRING} req.path_parameter ("filename") as p_filename then
|
||||
fn := p_filename.value
|
||||
body.append ("<h1>File %"" + api.html_encoded (fn) + "%"</h1>%N")
|
||||
body.append ("<div class=%"uploaded-file%">%N") -- To ease css customization.
|
||||
if attached file_upload_api as l_file_upload_api then
|
||||
f := l_file_upload_api.new_uploads_file (create {PATH}.make_from_string (fn))
|
||||
|
||||
-- FIXME: get CMS information related to this file ... owner, ...
|
||||
|
||||
body.append ("<p>Open the media <a href=%"" + req.script_url ("/" + l_file_upload_api.file_link (f).location) + "%">")
|
||||
body.append (api.html_encoded (f.filename))
|
||||
body.append ("</a>.</p>%N")
|
||||
|
||||
if attached f.location.extension as ext then
|
||||
if
|
||||
ext.is_case_insensitive_equal_general ("png")
|
||||
or ext.is_case_insensitive_equal_general ("jpg")
|
||||
then
|
||||
body.append ("<div><img src=%"" + req.script_url ("/" + l_file_upload_api.file_link (f).location) + "%" /></div>")
|
||||
end
|
||||
end
|
||||
end
|
||||
body.append ("%N</div>%N")
|
||||
end
|
||||
r.add_to_primary_tabs (create {CMS_LOCAL_LINK}.make ("Uploaded files", "upload/"))
|
||||
r.set_main_content (body)
|
||||
r.execute
|
||||
end
|
||||
|
||||
execute_upload (req: WSF_REQUEST; res: WSF_RESPONSE; api: CMS_API)
|
||||
local
|
||||
body: STRING_8
|
||||
r: CMS_RESPONSE
|
||||
do
|
||||
if req.is_get_head_request_method or req.is_post_request_method then
|
||||
create body.make_empty
|
||||
body.append ("<h1> Upload files </h1>%N")
|
||||
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
||||
if r.has_permission ("upload files") then
|
||||
-- create body
|
||||
body.append ("<p>Please choose some file(s) to upload.</p>")
|
||||
|
||||
-- create form to choose files and upload them
|
||||
body.append ("<form action=%"" + req.script_url ("/upload/") + "%" enctype=%"multipart/form-data%" method=%"POST%"> %N")
|
||||
body.append ("<input name=%"file-name[]%" type=%"file%" multiple> %N")
|
||||
body.append ("<button type=submit>Upload</button>%N")
|
||||
body.append ("</form>%N")
|
||||
|
||||
if req.is_post_request_method then
|
||||
process_uploaded_files (req, api, body)
|
||||
end
|
||||
else
|
||||
create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
||||
end
|
||||
|
||||
-- Build the response.
|
||||
|
||||
append_uploaded_file_album_to (req, api, body)
|
||||
r.set_main_content (body)
|
||||
else
|
||||
create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
||||
end
|
||||
r.execute
|
||||
end
|
||||
|
||||
process_uploaded_files (req: WSF_REQUEST; api: CMS_API; a_output: STRING)
|
||||
-- show all uploaded files
|
||||
local
|
||||
-- stored: BOOLEAN
|
||||
-- file_system_handler: WSF_FILE_SYSTEM_HANDLER
|
||||
-- file_system_upload_handler: CMS_FILE_UPLOAD_FILE_SYSTEM_HANDLER
|
||||
l_uploaded_file: CMS_UPLOADED_FILE
|
||||
uf: WSF_UPLOADED_FILE
|
||||
-- ut: FILE_UTILITIES
|
||||
-- files_root: PATH
|
||||
do
|
||||
if attached file_upload_api as l_file_upload_api then
|
||||
-- if has uploaded files, then store them
|
||||
if req.has_uploaded_file then
|
||||
a_output.append ("<ul class=%"uploaded-files%"><strong>Uploaded file(s):</strong>%N")
|
||||
across
|
||||
req.uploaded_files as ic
|
||||
loop
|
||||
uf := ic.item
|
||||
create l_uploaded_file.make_with_uploaded_file (l_file_upload_api.uploads_location, uf)
|
||||
a_output.append ("<li>")
|
||||
a_output.append (api.html_encoded (l_uploaded_file.filename))
|
||||
|
||||
-- Record current user, ..
|
||||
-- for now, only user, but it should also take care of uploaded time, ...
|
||||
l_uploaded_file.set_owner (api.current_user (req))
|
||||
|
||||
l_file_upload_api.save_uploaded_file (l_uploaded_file)
|
||||
|
||||
-- FIXME: display for information, about the new disk filename.
|
||||
if l_file_upload_api.error_handler.has_error then
|
||||
a_output.append (" <span class=%"error%">failed!</span>")
|
||||
end
|
||||
a_output.append ("</li>")
|
||||
end
|
||||
a_output.append ("</ul>%N")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
append_uploaded_file_album_to (req: WSF_REQUEST; api: CMS_API; a_output: STRING)
|
||||
local
|
||||
d: DIRECTORY
|
||||
f: CMS_FILE
|
||||
p: PATH
|
||||
rel: PATH
|
||||
do
|
||||
if attached file_upload_api as l_file_upload_api then
|
||||
create rel.make_from_string (l_file_upload_api.uploads_directory_name)
|
||||
p := api.files_location.extended_path (rel)
|
||||
|
||||
a_output.append ("<ul class=%"directory-index%"><strong>Index of uploads:</strong>%N")
|
||||
|
||||
create d.make_with_path (p)
|
||||
if d.exists then
|
||||
across
|
||||
d.entries as ic
|
||||
loop
|
||||
if ic.item.is_current_symbol then
|
||||
-- Ignore
|
||||
elseif ic.item.is_parent_symbol then
|
||||
-- Ignore for now.
|
||||
else
|
||||
f := l_file_upload_api.new_file (rel.extended_path (ic.item))
|
||||
|
||||
if f.is_directory then
|
||||
a_output.append ("<li class=%"directory%">")
|
||||
else
|
||||
a_output.append ("<li class=%"file%">")
|
||||
end
|
||||
a_output.append ("<a href=%"" + api.percent_encoded (f.filename) + "%">")
|
||||
a_output.append (api.html_encoded (f.filename))
|
||||
a_output.append ("</a>")
|
||||
|
||||
a_output.append ("( <a href=%"" + req.script_url ("/" + l_file_upload_api.file_link (f).location) + "%">")
|
||||
a_output.append ("media</a>)")
|
||||
a_output.append ("</li>%N")
|
||||
end
|
||||
end
|
||||
end
|
||||
a_output.append ("</ul>%N")
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Mapping helper: uri template agent (analogue to the demo-module)
|
||||
|
||||
map_uri_template (a_router: WSF_ROUTER; a_tpl: STRING; h: WSF_URI_TEMPLATE_HANDLER; rqst_methods: detachable WSF_REQUEST_METHODS)
|
||||
-- Map `h' as handler for `a_tpl', according to `rqst_methods'.
|
||||
require
|
||||
a_tpl_attached: a_tpl /= Void
|
||||
h_attached: h /= Void
|
||||
do
|
||||
a_router.map (create {WSF_URI_TEMPLATE_MAPPING}.make (a_tpl, h), rqst_methods)
|
||||
end
|
||||
|
||||
map_uri_template_agent (a_router: WSF_ROUTER; a_tpl: READABLE_STRING_8; proc: PROCEDURE [TUPLE [req: WSF_REQUEST; res: WSF_RESPONSE]]; rqst_methods: detachable WSF_REQUEST_METHODS)
|
||||
-- Map `proc' as handler for `a_tpl', according to `rqst_methods'.
|
||||
require
|
||||
a_tpl_attached: a_tpl /= Void
|
||||
proc_attached: proc /= Void
|
||||
do
|
||||
map_uri_template (a_router, a_tpl, create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (proc), rqst_methods)
|
||||
end
|
||||
end
|
||||
62
modules/file_upload/cms_uploaded_file.e
Normal file
62
modules/file_upload/cms_uploaded_file.e
Normal file
@@ -0,0 +1,62 @@
|
||||
note
|
||||
description: "Summary description for {CMS_UPLOADED_FILE}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_UPLOADED_FILE
|
||||
|
||||
create
|
||||
make_with_uploaded_file
|
||||
|
||||
feature {NONE} -- Initializaion
|
||||
|
||||
make_with_uploaded_file (a_uploads_location: PATH; uf: WSF_UPLOADED_FILE)
|
||||
do
|
||||
uploads_location := a_uploads_location
|
||||
uploaded_file := uf
|
||||
location := a_uploads_location.extended (uf.safe_filename)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
uploaded_file: WSF_UPLOADED_FILE
|
||||
|
||||
uploads_location: PATH
|
||||
|
||||
filename: STRING_32
|
||||
-- File name of Current file.
|
||||
local
|
||||
p: PATH
|
||||
do
|
||||
p := location
|
||||
if attached p.entry as e then
|
||||
Result := e.name
|
||||
else
|
||||
Result := p.name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
location: PATH
|
||||
-- Absolute path, or relative path to the `CMS_API.files_location'.
|
||||
|
||||
owner: detachable CMS_USER
|
||||
-- Optional owner.
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_owner (u: detachable CMS_USER)
|
||||
-- Set `owner' to `u'.
|
||||
do
|
||||
owner := u
|
||||
end
|
||||
|
||||
feature -- Basic operation
|
||||
|
||||
move_to (p: PATH): BOOLEAN
|
||||
do
|
||||
Result := uploaded_file.move_to (p.name)
|
||||
end
|
||||
|
||||
end
|
||||
21
modules/file_upload/file_uploader.ecf
Normal file
21
modules/file_upload/file_uploader.ecf
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="file_uploader" uuid="795C88E5-9218-4F35-A985-5501340E2D9D" library_target="file_uploader">
|
||||
<target name="file_uploader">
|
||||
<root all_classes="true" />
|
||||
<file_rule>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="standard"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="cms" location="..\..\cms-safe.ecf"/>
|
||||
<library name="cms_model" location="..\..\library\model\cms_model-safe.ecf"/>
|
||||
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
|
||||
<library name="wsf_encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder-safe.ecf"/>
|
||||
<cluster name="file_uploader" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
27
modules/file_upload/file_uploader.ecf~
Normal file
27
modules/file_upload/file_uploader.ecf~
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="file_uploader" uuid="795C88E5-9218-4F35-A985-5501340E2D9D" library_target="file_uploader">
|
||||
<target name="file_uploader">
|
||||
<!-- <root class="CMS_FILE_UPLOAD" feature="make"/> -->
|
||||
<root all_classes="true" />
|
||||
<option warning="true">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<!-- <precompile name="base_pre" location="$ISE_PRECOMP\base-safe.ecf"/> -->
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="cms" location="\home\fmurer\Documents\EWF_ROC\ROC\cms-safe.ecf"/>
|
||||
<library name="cms_model" location="\home\fmurer\Documents\EWF_ROC\ROC\library\model\cms_model-safe.ecf"/>
|
||||
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
|
||||
<library name="wsf_encoder" location="\home\fmurer\Documents\EWF_ROC\eiffelstudio-src\contrib\library\web\framework\ewf\text\encoder\encoder-safe.ecf"/>
|
||||
<cluster name="file_uploader" location=".\" recursive="true">
|
||||
<file_rule>
|
||||
<exclude>/.svn$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
</file_rule>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
@@ -11,8 +11,10 @@ deferred class
|
||||
|
||||
inherit
|
||||
CMS_CONTENT
|
||||
rename
|
||||
has_identifier as has_id
|
||||
redefine
|
||||
debug_output
|
||||
debug_output, has_id
|
||||
end
|
||||
|
||||
REFACTORING_HELPER
|
||||
@@ -63,6 +65,12 @@ feature -- Conversion
|
||||
|
||||
feature -- Access
|
||||
|
||||
identifier: detachable IMMUTABLE_STRING_32
|
||||
-- Optional identifier.
|
||||
do
|
||||
create Result.make_from_string_general (id.out)
|
||||
end
|
||||
|
||||
id: INTEGER_64 assign set_id
|
||||
-- Unique id.
|
||||
--| Should we use NATURAL_64 instead?
|
||||
|
||||
@@ -97,241 +97,10 @@ feature -- Forms ...
|
||||
populate_form_with_path_alias (response, f, a_node)
|
||||
end
|
||||
|
||||
populate_form_with_taxonomy (response: NODE_RESPONSE; f: CMS_FORM; a_node: detachable CMS_NODE)
|
||||
local
|
||||
ti: detachable WSF_FORM_TEXT_INPUT
|
||||
th: WSF_FORM_HIDDEN_INPUT
|
||||
w_set: WSF_FORM_FIELD_SET
|
||||
w_select: WSF_FORM_SELECT
|
||||
w_opt: WSF_FORM_SELECT_OPTION
|
||||
w_cb: WSF_FORM_CHECKBOX_INPUT
|
||||
w_voc_set: WSF_FORM_FIELD_SET
|
||||
s: STRING_32
|
||||
voc: CMS_VOCABULARY
|
||||
t: detachable CMS_TERM
|
||||
l_terms: detachable CMS_TERM_COLLECTION
|
||||
l_has_edit_permission: BOOLEAN
|
||||
populate_form_with_taxonomy (response: CMS_RESPONSE; f: CMS_FORM; a_content: detachable CMS_CONTENT)
|
||||
do
|
||||
if
|
||||
attached {CMS_TAXONOMY_API} response.api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api and then
|
||||
attached l_taxonomy_api.vocabularies_for_type (content_type.name) as l_vocs and then not l_vocs.is_empty
|
||||
then
|
||||
|
||||
l_has_edit_permission := response.has_permissions (<<"update any taxonomy", "update " + content_type.name + " taxonomy">>)
|
||||
|
||||
-- Handle Taxonomy fields, if any associated with `content_type'.
|
||||
create w_set.make
|
||||
w_set.add_css_class ("taxonomy")
|
||||
l_vocs.sort
|
||||
across
|
||||
l_vocs as vocs_ic
|
||||
loop
|
||||
voc := vocs_ic.item
|
||||
create th.make_with_text ({STRING_32} "taxonomy_vocabularies[" + voc.id.out + "]", voc.name)
|
||||
w_set.extend (th)
|
||||
|
||||
l_terms := Void
|
||||
if a_node /= Void and then a_node.has_id then
|
||||
l_terms := l_taxonomy_api.terms_of_entity (a_node.content_type, a_node.id.out, voc)
|
||||
if l_terms /= Void then
|
||||
l_terms.sort
|
||||
end
|
||||
end
|
||||
create w_voc_set.make
|
||||
w_set.extend (w_voc_set)
|
||||
|
||||
if voc.is_tags then
|
||||
w_voc_set.set_legend (response.translation (voc.name, Void))
|
||||
|
||||
create ti.make ({STRING_32} "taxonomy_" + voc.id.out)
|
||||
w_voc_set.extend (ti)
|
||||
if voc.is_term_required then
|
||||
ti.enable_required
|
||||
end
|
||||
if attached voc.description as l_desc then
|
||||
ti.set_description (response.html_encoded (response.translation (l_desc, Void)))
|
||||
else
|
||||
ti.set_description (response.html_encoded (response.translation (voc.name, Void)))
|
||||
end
|
||||
ti.set_size (70)
|
||||
if l_terms /= Void then
|
||||
create s.make_empty
|
||||
across
|
||||
l_terms as ic
|
||||
loop
|
||||
t := ic.item
|
||||
if not s.is_empty then
|
||||
s.append_character (',')
|
||||
s.append_character (' ')
|
||||
end
|
||||
if ic.item.text.has (' ') then
|
||||
s.append_character ('"')
|
||||
s.append (t.text)
|
||||
s.append_character ('"')
|
||||
else
|
||||
s.append (t.text)
|
||||
end
|
||||
end
|
||||
ti.set_text_value (s)
|
||||
end
|
||||
if not l_has_edit_permission then
|
||||
ti.set_is_readonly (True)
|
||||
end
|
||||
else
|
||||
l_taxonomy_api.fill_vocabularies_with_terms (voc)
|
||||
if not voc.terms.is_empty then
|
||||
if voc.multiple_terms_allowed then
|
||||
if attached voc.description as l_desc then
|
||||
w_voc_set.set_legend (response.html_encoded (l_desc))
|
||||
else
|
||||
w_voc_set.set_legend (response.html_encoded (voc.name))
|
||||
end
|
||||
across
|
||||
voc as voc_terms_ic
|
||||
loop
|
||||
t := voc_terms_ic.item
|
||||
create w_cb.make_with_value ({STRING_32} "taxonomy_" + voc.id.out + "[]", t.text)
|
||||
w_cb.set_title (t.text)
|
||||
w_voc_set.extend (w_cb)
|
||||
if l_terms /= Void and then across l_terms as ic some ic.item.text.same_string (t.text) end then
|
||||
w_cb.set_checked (True)
|
||||
end
|
||||
if not l_has_edit_permission then
|
||||
w_cb.set_is_readonly (True)
|
||||
end
|
||||
end
|
||||
else
|
||||
create w_select.make ({STRING_32} "taxonomy_" + voc.id.out)
|
||||
w_voc_set.extend (w_select)
|
||||
|
||||
if attached voc.description as l_desc then
|
||||
w_select.set_description (response.html_encoded (l_desc))
|
||||
else
|
||||
w_select.set_description (response.html_encoded (voc.name))
|
||||
end
|
||||
w_voc_set.set_legend (response.html_encoded (voc.name))
|
||||
|
||||
across
|
||||
voc as voc_terms_ic
|
||||
loop
|
||||
t := voc_terms_ic.item
|
||||
create w_opt.make (response.html_encoded (t.text), response.html_encoded (t.text))
|
||||
w_select.add_option (w_opt)
|
||||
|
||||
if l_terms /= Void and then across l_terms as ic some ic.item.text.same_string (t.text) end then
|
||||
w_opt.set_is_selected (True)
|
||||
end
|
||||
end
|
||||
if not l_has_edit_permission then
|
||||
w_select.set_is_readonly (True)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
f.submit_actions.extend (agent taxonomy_submit_action (response, l_taxonomy_api, l_vocs, a_node, ?))
|
||||
|
||||
if
|
||||
attached f.fields_by_name ("title") as l_title_fields and then
|
||||
attached l_title_fields.first as l_title_field
|
||||
then
|
||||
f.insert_after (w_set, l_title_field)
|
||||
else
|
||||
f.extend (w_set)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
taxonomy_submit_action (a_response: CMS_RESPONSE; a_taxonomy_api: CMS_TAXONOMY_API; a_vocs: CMS_VOCABULARY_COLLECTION; a_node: detachable CMS_NODE fd: WSF_FORM_DATA)
|
||||
require
|
||||
vocs_not_empty: not a_vocs.is_empty
|
||||
local
|
||||
l_voc_name: READABLE_STRING_32
|
||||
l_terms_to_remove: ARRAYED_LIST [CMS_TERM]
|
||||
l_new_terms: LIST [READABLE_STRING_32]
|
||||
l_text: READABLE_STRING_GENERAL
|
||||
l_found: BOOLEAN
|
||||
t: detachable CMS_TERM
|
||||
vid: INTEGER_64
|
||||
do
|
||||
if
|
||||
a_node /= Void and then a_node.has_id and then
|
||||
attached fd.table_item ("taxonomy_vocabularies") as fd_vocs
|
||||
then
|
||||
if a_response.has_permissions (<<{STRING_32} "update any taxonomy", {STRING_32} "update " + content_type.name + " taxonomy">>) then
|
||||
across
|
||||
fd_vocs.values as ic
|
||||
loop
|
||||
vid := ic.key.to_integer_64
|
||||
l_voc_name := ic.item.string_representation
|
||||
|
||||
if attached a_vocs.item_by_id (vid) as voc then
|
||||
if attached fd.string_item ("taxonomy_" + vid.out) as l_string then
|
||||
l_new_terms := a_taxonomy_api.splitted_string (l_string, ',')
|
||||
elseif attached fd.table_item ("taxonomy_" + vid.out) as fd_terms then
|
||||
create {ARRAYED_LIST [READABLE_STRING_32]} l_new_terms.make (fd_terms.count)
|
||||
across
|
||||
fd_terms as t_ic
|
||||
loop
|
||||
l_new_terms.force (t_ic.item.string_representation)
|
||||
end
|
||||
else
|
||||
create {ARRAYED_LIST [READABLE_STRING_32]} l_new_terms.make (0)
|
||||
end
|
||||
|
||||
create l_terms_to_remove.make (0)
|
||||
if attached a_taxonomy_api.terms_of_entity (content_type.name, a_node.id.out, voc) as l_existing_terms then
|
||||
across
|
||||
l_existing_terms as t_ic
|
||||
loop
|
||||
l_text := t_ic.item.text
|
||||
from
|
||||
l_found := False
|
||||
l_new_terms.start
|
||||
until
|
||||
l_new_terms.after
|
||||
loop
|
||||
if l_new_terms.item.same_string_general (l_text) then
|
||||
-- Already associated with term `t_ic.text'.
|
||||
l_found := True
|
||||
l_new_terms.remove
|
||||
else
|
||||
l_new_terms.forth
|
||||
end
|
||||
end
|
||||
if not l_found then
|
||||
-- Remove term
|
||||
l_terms_to_remove.force (t_ic.item)
|
||||
end
|
||||
end
|
||||
across
|
||||
l_terms_to_remove as t_ic
|
||||
loop
|
||||
a_taxonomy_api.unassociate_term_from_entity (t_ic.item, content_type.name, a_node.id.out)
|
||||
end
|
||||
end
|
||||
across
|
||||
l_new_terms as t_ic
|
||||
loop
|
||||
t := a_taxonomy_api.term_by_text (t_ic.item, voc)
|
||||
if
|
||||
t = Void and voc.is_tags
|
||||
then
|
||||
-- Create new term!
|
||||
create t.make (t_ic.item)
|
||||
a_taxonomy_api.save_term (t, voc)
|
||||
if a_taxonomy_api.has_error then
|
||||
t := Void
|
||||
end
|
||||
end
|
||||
if t /= Void then
|
||||
a_taxonomy_api.associate_term_with_entity (t, content_type.name, a_node.id.out)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if attached {CMS_TAXONOMY_API} response.api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api then
|
||||
l_taxonomy_api.populate_edit_form (response, f, content_type.name, a_content)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -565,30 +334,9 @@ feature -- Output
|
||||
|
||||
if
|
||||
a_response /= Void and then
|
||||
attached {CMS_TAXONOMY_API} cms_api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api and then
|
||||
attached l_taxonomy_api.vocabularies_for_type (content_type.name) as vocs and then not vocs.is_empty
|
||||
attached {CMS_TAXONOMY_API} cms_api.module_api ({CMS_TAXONOMY_MODULE}) as l_taxonomy_api
|
||||
then
|
||||
vocs.sort
|
||||
across
|
||||
vocs as ic
|
||||
loop
|
||||
if
|
||||
attached l_taxonomy_api.terms_of_entity (content_type.name, a_node.id.out, ic.item) as l_terms and then
|
||||
not l_terms.is_empty
|
||||
then
|
||||
a_output.append ("<ul class=%"taxonomy term-" + ic.item.id.out + "%">")
|
||||
a_output.append (l_node_api.html_encoded (ic.item.name))
|
||||
a_output.append (": ")
|
||||
across
|
||||
l_terms as t_ic
|
||||
loop
|
||||
a_output.append ("<li>")
|
||||
a_response.append_link_to_html (t_ic.item.text, "taxonomy/term/" + t_ic.item.id.out, Void, a_output)
|
||||
a_output.append ("</li>")
|
||||
end
|
||||
a_output.append ("</ul>%N")
|
||||
end
|
||||
end
|
||||
l_taxonomy_api.append_taxonomy_to_xhtml (a_node, a_response, a_output)
|
||||
end
|
||||
|
||||
-- We don't show the summary on the detail page, since its just a short view of the full content. Otherwise we would write the same thing twice.
|
||||
|
||||
@@ -99,7 +99,7 @@ feature {CMS_API} -- Module management
|
||||
l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("oauth2_consumers.sql")), Void)
|
||||
|
||||
if l_sql_storage.has_error then
|
||||
api.logger.put_error ("Could not initialize database for blog module", generating_type)
|
||||
api.logger.put_error ("Could not initialize database for oauth_20 module", generating_type)
|
||||
end
|
||||
-- TODO workaround.
|
||||
l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("oauth2_consumers_initialize.sql")), Void)
|
||||
@@ -108,7 +108,7 @@ feature {CMS_API} -- Module management
|
||||
-- TODO workaround, until we have an admin module
|
||||
l_sql_storage.sql_query ("SELECT name FROM oauth2_consumers;", Void)
|
||||
if l_sql_storage.has_error then
|
||||
api.logger.put_error ("Could not initialize database for differnent consumerns", generating_type)
|
||||
api.logger.put_error ("Could not initialize database for differnent consumers", generating_type)
|
||||
else
|
||||
from
|
||||
l_sql_storage.sql_start
|
||||
@@ -227,7 +227,10 @@ feature -- Hooks
|
||||
until
|
||||
lnk2 /= Void
|
||||
loop
|
||||
if ic.item.location.same_string ("account/roc-logout") then
|
||||
if
|
||||
ic.item.location.same_string ("account/roc-logout") or else
|
||||
ic.item.location.same_string ("basic_auth_logoff")
|
||||
then
|
||||
lnk2 := ic.item
|
||||
end
|
||||
end
|
||||
|
||||
@@ -203,7 +203,10 @@ feature -- Hooks
|
||||
until
|
||||
lnk2 /= Void
|
||||
loop
|
||||
if ic.item.location.same_string ("account/roc-logout") then
|
||||
if
|
||||
ic.item.location.same_string ("account/roc-logout") or else
|
||||
ic.item.location.same_string ("basic_auth_logoff")
|
||||
then
|
||||
lnk2 := ic.item
|
||||
end
|
||||
end
|
||||
|
||||
@@ -197,7 +197,7 @@ feature {NONE} -- User OpenID
|
||||
|
||||
Sql_insert_openid: STRING = "INSERT INTO openid_items (uid, identity, created) VALUES (:uid, :identity, :utc_date);"
|
||||
|
||||
Sql_openid_consumers: STRING = "SELECT name FROM openid_consumers";
|
||||
Sql_openid_consumers: STRING = "SELECT name FROM openid_consumers;"
|
||||
|
||||
|
||||
feature {NONE} -- Consumer
|
||||
|
||||
63
modules/session_auth/cms_session_api.e
Normal file
63
modules/session_auth/cms_session_api.e
Normal file
@@ -0,0 +1,63 @@
|
||||
note
|
||||
description: "API to manage CMS User session authentication"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_SESSION_API
|
||||
|
||||
|
||||
inherit
|
||||
CMS_MODULE_API
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
create {CMS_SESSION_AUTH_MODULE}
|
||||
make_with_storage
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make_with_storage (a_api: CMS_API; a_session_auth_storage: CMS_SESSION_AUTH_STORAGE_I)
|
||||
-- Create an object with api `a_api' and storage `a_session_auth_storage'.
|
||||
do
|
||||
session_auth_storage := a_session_auth_storage
|
||||
make (a_api)
|
||||
ensure
|
||||
session_auth_storage_set: session_auth_storage = a_session_auth_storage
|
||||
end
|
||||
|
||||
feature {CMS_MODULE} -- Access: User session storage.
|
||||
|
||||
session_auth_storage: CMS_SESSION_AUTH_STORAGE_I
|
||||
-- storage interface.
|
||||
|
||||
feature -- Access
|
||||
|
||||
user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER
|
||||
-- Retrieve user by token `a_token', if any.
|
||||
do
|
||||
Result := session_auth_storage.user_by_session_token (a_token)
|
||||
end
|
||||
|
||||
has_user_token (a_user: CMS_USER): BOOLEAN
|
||||
-- Has the user `a_user' and associated session token?
|
||||
do
|
||||
Result := session_auth_storage.has_user_token (a_user)
|
||||
end
|
||||
|
||||
feature -- Change User session
|
||||
|
||||
new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;)
|
||||
-- New user session for user `a_user' with token `a_token'.
|
||||
do
|
||||
session_auth_storage.new_user_session_auth (a_token, a_user)
|
||||
end
|
||||
|
||||
|
||||
update_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER )
|
||||
-- Update user session for user `a_user' with token `a_token'.
|
||||
do
|
||||
session_auth_storage.update_user_session_auth (a_token, a_user)
|
||||
end
|
||||
|
||||
end
|
||||
28
modules/session_auth/cms_session_auth-safe.ecf
Normal file
28
modules/session_auth/cms_session_auth-safe.ecf
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="session_auth" uuid="8A43B6DD-6B39-472C-9A96-978414CBF1E3" library_target="session_auth">
|
||||
<target name="session_auth">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="false" is_attached_by_default="true" is_obsolete_routine_type="true" void_safety="all" syntax="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="cms" location="..\..\cms-safe.ecf"/>
|
||||
<library name="cms_app_env" location="..\..\library\app_env\app_env-safe.ecf" readonly="false"/>
|
||||
<library name="cms_auth_module" location="..\..\modules\auth\auth-safe.ecf" readonly="false"/>
|
||||
<library name="cms_model" location="..\..\library\model\cms_model-safe.ecf" readonly="false"/>
|
||||
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto-safe.ecf"/>
|
||||
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder-safe.ecf"/>
|
||||
<library name="error" location="$ISE_LIBRARY\contrib\library\utility\general\error\error-safe.ecf"/>
|
||||
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="http_authorization" location="$ISE_LIBRARY\contrib\library\web\authentication\http_authorization\http_authorization-safe.ecf" readonly="false"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
|
||||
<library name="wsf_extension" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf_extension-safe.ecf" readonly="false"/>
|
||||
<cluster name="src" location=".\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
349
modules/session_auth/cms_session_auth_module.e
Normal file
349
modules/session_auth/cms_session_auth_module.e
Normal file
@@ -0,0 +1,349 @@
|
||||
note
|
||||
description: "[
|
||||
This module allows the use Session Based Authentication using Cookies to restrict access
|
||||
by looking up users in the given providers.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_SESSION_AUTH_MODULE
|
||||
|
||||
inherit
|
||||
CMS_MODULE
|
||||
rename
|
||||
module_api as user_session_api
|
||||
redefine
|
||||
filters,
|
||||
setup_hooks,
|
||||
initialize,
|
||||
install,
|
||||
user_session_api
|
||||
end
|
||||
|
||||
|
||||
CMS_HOOK_AUTO_REGISTER
|
||||
|
||||
CMS_HOOK_BLOCK
|
||||
|
||||
CMS_HOOK_MENU_SYSTEM_ALTER
|
||||
|
||||
CMS_HOOK_VALUE_TABLE_ALTER
|
||||
|
||||
SHARED_LOGGER
|
||||
|
||||
CMS_REQUEST_UTIL
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
do
|
||||
version := "1.0"
|
||||
description := "Service to manage cookie based authentication"
|
||||
package := "authentication"
|
||||
add_dependency ({CMS_AUTHENTICATION_MODULE})
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: STRING = "session_auth"
|
||||
|
||||
|
||||
|
||||
feature {CMS_API} -- Module Initialization
|
||||
|
||||
initialize (a_api: CMS_API)
|
||||
-- <Precursor>
|
||||
local
|
||||
l_session_auth_api: like user_session_api
|
||||
l_user_auth_storage: CMS_SESSION_AUTH_STORAGE_I
|
||||
do
|
||||
Precursor (a_api)
|
||||
|
||||
-- Storage initialization
|
||||
if attached a_api.storage.as_sql_storage as l_storage_sql then
|
||||
create {CMS_SESSION_AUTH_STORAGE_SQL} l_user_auth_storage.make (l_storage_sql)
|
||||
else
|
||||
-- FIXME: in case of NULL storage, should Current be disabled?
|
||||
create {CMS_SESSION_AUTH_STORAGE_NULL} l_user_auth_storage
|
||||
end
|
||||
|
||||
-- API initialization
|
||||
create l_session_auth_api.make_with_storage (a_api, l_user_auth_storage)
|
||||
user_session_api := l_session_auth_api
|
||||
ensure then
|
||||
session_auth_api_set: user_session_api /= Void
|
||||
end
|
||||
|
||||
feature {CMS_API} -- Module management
|
||||
|
||||
install (api: CMS_API)
|
||||
do
|
||||
-- Schema
|
||||
if attached api.storage.as_sql_storage as l_sql_storage then
|
||||
if not l_sql_storage.sql_table_exists ("session_auth") then
|
||||
--| Schema
|
||||
l_sql_storage.sql_execute_file_script (api.module_resource_location (Current, (create {PATH}.make_from_string ("scripts")).extended ("session_auth_table.sql")), Void)
|
||||
|
||||
if l_sql_storage.has_error then
|
||||
api.logger.put_error ("Could not initialize database for session auth module", generating_type)
|
||||
end
|
||||
end
|
||||
l_sql_storage.sql_finalize
|
||||
Precursor {CMS_MODULE}(api)
|
||||
end
|
||||
end
|
||||
|
||||
feature {CMS_API} -- Access: API
|
||||
|
||||
user_session_api: detachable CMS_SESSION_API
|
||||
-- <Precursor>
|
||||
|
||||
feature -- Access: router
|
||||
|
||||
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
|
||||
-- <Precursor>
|
||||
do
|
||||
a_router.handle ("/account/roc-session-login", create {WSF_URI_AGENT_HANDLER}.make (agent handle_login(a_api, ?, ?)), a_router.methods_head_get)
|
||||
a_router.handle ("/account/roc-session-logout", create {WSF_URI_AGENT_HANDLER}.make (agent handle_logout (a_api, ?, ?)), a_router.methods_get_post)
|
||||
a_router.handle ("/account/login-with-session", create {WSF_URI_TEMPLATE_AGENT_HANDLER}.make (agent handle_login_with_session (a_api,user_session_api, ?, ?)), a_router.methods_get_post)
|
||||
end
|
||||
|
||||
feature -- Access: filter
|
||||
|
||||
filters (a_api: CMS_API): detachable LIST [WSF_FILTER]
|
||||
-- Possibly list of Filter's module.
|
||||
do
|
||||
create {ARRAYED_LIST [WSF_FILTER]} Result.make (1)
|
||||
if attached user_session_api as l_session_api then
|
||||
Result.extend (create {CMS_SESSION_AUTH_FILTER}.make (a_api, l_session_api))
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation: routes
|
||||
|
||||
handle_login (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
r: CMS_RESPONSE
|
||||
do
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
||||
r.execute
|
||||
end
|
||||
|
||||
handle_logout (api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
r: CMS_RESPONSE
|
||||
l_cookie: WSF_COOKIE
|
||||
do
|
||||
if
|
||||
attached {WSF_STRING} req.cookie ({CMS_SESSION_CONSTANTS}.session_auth_token) as l_cookie_token and then
|
||||
attached {CMS_USER} current_user (req) as l_user
|
||||
then
|
||||
-- Logout Session
|
||||
create l_cookie.make ({CMS_SESSION_CONSTANTS}.session_auth_token, l_cookie_token.value)
|
||||
l_cookie.set_path ("/")
|
||||
l_cookie.set_max_age (-1)
|
||||
res.add_cookie (l_cookie)
|
||||
unset_current_user (req)
|
||||
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
||||
r.set_status_code ({HTTP_CONSTANTS}.found)
|
||||
r.set_redirection (req.absolute_script_url (""))
|
||||
r.execute
|
||||
else
|
||||
fixme (generator + ": missing else implementation in handle_logout!")
|
||||
end
|
||||
end
|
||||
|
||||
handle_login_with_session (api: CMS_API; a_session_api: detachable CMS_SESSION_API; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
r: CMS_RESPONSE
|
||||
l_token: STRING
|
||||
l_cookie: WSF_COOKIE
|
||||
do
|
||||
if
|
||||
attached a_session_api as l_session_api and then
|
||||
attached {WSF_STRING} req.form_parameter ("username") as l_username and then
|
||||
attached {WSF_STRING} req.form_parameter ("password") as l_password and then
|
||||
api.user_api.is_valid_credential (l_username.value, l_password.value) and then
|
||||
attached api.user_api.user_by_name (l_username.value) as l_user
|
||||
then
|
||||
l_token := generate_token
|
||||
if
|
||||
a_session_api.has_user_token (l_user)
|
||||
then
|
||||
l_session_api.update_user_session_auth (l_token, l_user)
|
||||
else
|
||||
l_session_api.new_user_session_auth (l_token, l_user)
|
||||
end
|
||||
create l_cookie.make ({CMS_SESSION_CONSTANTS}.session_auth_token, l_token)
|
||||
l_cookie.set_max_age ({CMS_SESSION_CONSTANTS}.session_max_age)
|
||||
l_cookie.set_path ("/")
|
||||
res.add_cookie (l_cookie)
|
||||
set_current_user (req, l_user)
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
||||
r.set_redirection (req.absolute_script_url (""))
|
||||
r.execute
|
||||
else
|
||||
create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
||||
if attached template_block ("login", r) as l_tpl_block then
|
||||
if attached {WSF_STRING} req.form_parameter ("username") as l_username then
|
||||
l_tpl_block.set_value (l_username.value, "username")
|
||||
end
|
||||
l_tpl_block.set_value ("Wrong: Username or password ", "error")
|
||||
r.add_block (l_tpl_block, "content")
|
||||
end
|
||||
r.execute
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Hooks configuration
|
||||
|
||||
setup_hooks (a_hooks: CMS_HOOK_CORE_MANAGER)
|
||||
-- Module hooks configuration.
|
||||
do
|
||||
auto_subscribe_to_hooks (a_hooks)
|
||||
a_hooks.subscribe_to_block_hook (Current)
|
||||
a_hooks.subscribe_to_value_table_alter_hook (Current)
|
||||
end
|
||||
|
||||
feature -- Hooks
|
||||
|
||||
value_table_alter (a_value: CMS_VALUE_TABLE; a_response: CMS_RESPONSE)
|
||||
-- <Precursor>
|
||||
do
|
||||
if
|
||||
attached a_response.user as u and then
|
||||
attached {WSF_STRING} a_response.request.cookie ({CMS_SESSION_CONSTANTS}.session_auth_token)
|
||||
then
|
||||
a_value.force ("account/roc-session-logout", "auth_login_strategy")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
menu_system_alter (a_menu_system: CMS_MENU_SYSTEM; a_response: CMS_RESPONSE)
|
||||
-- Hook execution on collection of menu contained by `a_menu_system'
|
||||
-- for related response `a_response'.
|
||||
local
|
||||
lnk: CMS_LOCAL_LINK
|
||||
lnk2: detachable CMS_LINK
|
||||
do
|
||||
if
|
||||
attached a_response.user as u and then
|
||||
attached {WSF_STRING} a_response.request.cookie ({CMS_SESSION_CONSTANTS}.session_auth_token)
|
||||
then
|
||||
across
|
||||
a_menu_system.primary_menu.items as ic
|
||||
until
|
||||
lnk2 /= Void
|
||||
loop
|
||||
if ic.item.location.same_string ("account/roc-logout") or else ic.item.location.same_string ("basic_auth_logoff") then
|
||||
lnk2 := ic.item
|
||||
end
|
||||
end
|
||||
if lnk2 /= Void then
|
||||
a_menu_system.primary_menu.remove (lnk2)
|
||||
end
|
||||
create lnk.make ("Logout", "account/roc-session-logout" )
|
||||
a_menu_system.primary_menu.extend (lnk)
|
||||
else
|
||||
if a_response.location.starts_with ("account/") then
|
||||
create lnk.make ("Session", "account/roc-session-login")
|
||||
a_response.add_to_primary_tabs (lnk)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
block_list: ITERABLE [like {CMS_BLOCK}.name]
|
||||
local
|
||||
l_string: STRING
|
||||
do
|
||||
Result := <<"login">>
|
||||
debug ("roc")
|
||||
create l_string.make_empty
|
||||
across
|
||||
Result as ic
|
||||
loop
|
||||
l_string.append (ic.item)
|
||||
l_string.append_character (' ')
|
||||
end
|
||||
write_debug_log (generator + ".block_list:" + l_string )
|
||||
end
|
||||
end
|
||||
|
||||
get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
|
||||
do
|
||||
if
|
||||
a_block_id.is_case_insensitive_equal_general ("login") and then
|
||||
a_response.location.starts_with ("account/roc-session-login")
|
||||
then
|
||||
get_block_view_login (a_block_id, a_response)
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Helpers
|
||||
|
||||
template_block (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE): detachable CMS_SMARTY_TEMPLATE_BLOCK
|
||||
-- Smarty content block for `a_block_id'
|
||||
local
|
||||
p: detachable PATH
|
||||
do
|
||||
create p.make_from_string ("templates")
|
||||
p := p.extended ("block_").appended (a_block_id).appended_with_extension ("tpl")
|
||||
|
||||
p := a_response.api.module_theme_resource_location (Current, p)
|
||||
if p /= Void then
|
||||
if attached p.entry as e then
|
||||
create Result.make (a_block_id, Void, p.parent, e)
|
||||
else
|
||||
create Result.make (a_block_id, Void, p.parent, p)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Block views
|
||||
|
||||
get_block_view_login (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
|
||||
local
|
||||
vals: CMS_VALUE_TABLE
|
||||
do
|
||||
if attached template_block (a_block_id, a_response) as l_tpl_block then
|
||||
create vals.make (1)
|
||||
-- add the variable to the block
|
||||
value_table_alter (vals, a_response)
|
||||
across
|
||||
vals as ic
|
||||
loop
|
||||
l_tpl_block.set_value (ic.item, ic.key)
|
||||
end
|
||||
a_response.add_block (l_tpl_block, "content")
|
||||
else
|
||||
debug ("cms")
|
||||
a_response.add_warning_message ("Error with block [" + a_block_id + "]")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
generate_token: STRING
|
||||
-- Generate token to use in a Session.
|
||||
local
|
||||
l_token: STRING
|
||||
l_security: CMS_TOKEN_GENERATOR
|
||||
l_encode: URL_ENCODER
|
||||
do
|
||||
create l_security
|
||||
l_token := l_security.token
|
||||
create l_encode
|
||||
from until l_token.same_string (l_encode.encoded_string (l_token)) loop
|
||||
-- Loop ensure that we have a security token that does not contain characters that need encoding.
|
||||
-- We cannot simply to an encode-decode because the email sent to the user will contain an encoded token
|
||||
-- but the user will need to use an unencoded token if activation has to be done manually.
|
||||
l_token := l_security.token
|
||||
end
|
||||
Result := l_token
|
||||
end
|
||||
end
|
||||
19
modules/session_auth/cms_session_constants.e
Normal file
19
modules/session_auth/cms_session_constants.e
Normal file
@@ -0,0 +1,19 @@
|
||||
note
|
||||
description: "Summary description for {CMS_SESSION_CONSTANTS}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_SESSION_CONSTANTS
|
||||
|
||||
|
||||
feature
|
||||
session_auth_token: STRING = "EWF_ROC_SESSION_AUTH_TOKEN_"
|
||||
-- Name of Cookie used to keep the session info.
|
||||
-- TODO add a config file to be able to customize this value via coniguration file.
|
||||
|
||||
session_max_age: INTEGER = 86400
|
||||
-- Value of the Max-Age, before the cookie expires.
|
||||
-- TODO add a config file to be able to customize this value via coniguration file.
|
||||
|
||||
end
|
||||
55
modules/session_auth/filter/cms_session_auth_filter.e
Normal file
55
modules/session_auth/filter/cms_session_auth_filter.e
Normal file
@@ -0,0 +1,55 @@
|
||||
note
|
||||
description: "[
|
||||
Processes a HTTP request's checking Session cookies, putting the result into the execution variable user.
|
||||
]"
|
||||
date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $"
|
||||
revision: "$Revision: 96616 $"
|
||||
|
||||
class
|
||||
CMS_SESSION_AUTH_FILTER
|
||||
|
||||
inherit
|
||||
WSF_URI_TEMPLATE_HANDLER
|
||||
|
||||
CMS_HANDLER
|
||||
rename
|
||||
make as make_handler
|
||||
end
|
||||
|
||||
WSF_FILTER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_api: CMS_API; a_session_oauth_api: CMS_SESSION_API)
|
||||
do
|
||||
make_handler (a_api)
|
||||
session_oauth_api := a_session_oauth_api
|
||||
end
|
||||
|
||||
session_oauth_api: CMS_SESSION_API
|
||||
|
||||
feature -- Basic operations
|
||||
|
||||
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Execute the filter.
|
||||
do
|
||||
api.logger.put_debug (generator + ".execute ", Void)
|
||||
-- A valid user
|
||||
if
|
||||
attached {WSF_STRING} req.cookie ({CMS_SESSION_CONSTANTS}.session_auth_token) as l_roc_auth_session_token
|
||||
then
|
||||
if attached session_oauth_api.user_by_session_token (l_roc_auth_session_token.value) as l_user then
|
||||
set_current_user (req, l_user)
|
||||
else
|
||||
api.logger.put_error (generator + ".execute login_valid failed for: " + l_roc_auth_session_token.value , Void)
|
||||
end
|
||||
else
|
||||
api.logger.put_debug (generator + ".execute without authentication", Void)
|
||||
end
|
||||
execute_next (req, res)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,46 @@
|
||||
note
|
||||
description: "[
|
||||
API to handle OAUTH storage
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
CMS_SESSION_AUTH_STORAGE_I
|
||||
|
||||
inherit
|
||||
SHARED_LOGGER
|
||||
|
||||
feature -- Error Handling
|
||||
|
||||
error_handler: ERROR_HANDLER
|
||||
-- Error handler.
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Access: Users
|
||||
|
||||
user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER
|
||||
-- Retrieve user by token `a_token', if any.
|
||||
deferred
|
||||
end
|
||||
|
||||
has_user_token (a_user: CMS_USER): BOOLEAN
|
||||
-- Has the user `a_user' and associated session token?
|
||||
deferred
|
||||
end
|
||||
|
||||
|
||||
feature -- Change User session
|
||||
|
||||
new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;)
|
||||
-- New user session for user `a_user' with token `a_token'.
|
||||
deferred
|
||||
end
|
||||
|
||||
|
||||
update_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER )
|
||||
-- Update user session for user `a_user' with token `a_token'.
|
||||
deferred
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,47 @@
|
||||
note
|
||||
description: "Summary description for {CMS_SESSION_AUTH_STORAGE_NULL}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_SESSION_AUTH_STORAGE_NULL
|
||||
|
||||
inherit
|
||||
|
||||
CMS_SESSION_AUTH_STORAGE_I
|
||||
|
||||
|
||||
feature -- Error handler
|
||||
|
||||
error_handler: ERROR_HANDLER
|
||||
-- Error handler.
|
||||
do
|
||||
create Result.make
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER
|
||||
-- Retrieve user by token `a_token', if any.
|
||||
do
|
||||
end
|
||||
|
||||
has_user_token (a_user: CMS_USER): BOOLEAN
|
||||
-- Has the user `a_user' and associated session token?
|
||||
do
|
||||
end
|
||||
|
||||
feature -- Change User session
|
||||
|
||||
new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;)
|
||||
-- New user session for user `a_user' with token `a_token'.
|
||||
do
|
||||
end
|
||||
|
||||
|
||||
update_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER )
|
||||
-- Update user session for user `a_user' with token `a_token'.
|
||||
do
|
||||
end
|
||||
|
||||
end
|
||||
155
modules/session_auth/persistence/cms_session_auth_storage_sql.e
Normal file
155
modules/session_auth/persistence/cms_session_auth_storage_sql.e
Normal file
@@ -0,0 +1,155 @@
|
||||
note
|
||||
description: "Summary description for {CMS_SESSION_AUTH_STORAGE_SQL}."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_SESSION_AUTH_STORAGE_SQL
|
||||
|
||||
inherit
|
||||
|
||||
CMS_SESSION_AUTH_STORAGE_I
|
||||
|
||||
CMS_PROXY_STORAGE_SQL
|
||||
|
||||
CMS_SESSION_AUTH_STORAGE_I
|
||||
|
||||
CMS_STORAGE_SQL_I
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Access User
|
||||
|
||||
user_by_session_token (a_token: READABLE_STRING_32): detachable CMS_USER
|
||||
-- Retrieve user by token `a_token', if any.
|
||||
local
|
||||
l_parameters: STRING_TABLE [detachable ANY]
|
||||
do
|
||||
error_handler.reset
|
||||
write_information_log (generator + ".user_by_session_token")
|
||||
create l_parameters.make (1)
|
||||
l_parameters.put (a_token, "token")
|
||||
sql_query (Select_user_by_token, l_parameters)
|
||||
if not has_error and not sql_after then
|
||||
Result := fetch_user
|
||||
sql_forth
|
||||
if not sql_after then
|
||||
check
|
||||
no_more_than_one: False
|
||||
end
|
||||
Result := Void
|
||||
end
|
||||
end
|
||||
sql_finalize
|
||||
end
|
||||
|
||||
has_user_token (a_user: CMS_USER): BOOLEAN
|
||||
-- Has the user `a_user' and associated session token?
|
||||
local
|
||||
l_parameters: STRING_TABLE [detachable ANY]
|
||||
do
|
||||
error_handler.reset
|
||||
write_information_log (generator + ".has_user_token")
|
||||
create l_parameters.make (1)
|
||||
l_parameters.put (a_user.id, "uid")
|
||||
sql_query (Select_user_token, l_parameters)
|
||||
if not has_error and not sql_after then
|
||||
if sql_read_integer_64 (1) = 1 then
|
||||
Result := True
|
||||
else
|
||||
Result := False
|
||||
end
|
||||
end
|
||||
sql_finalize
|
||||
end
|
||||
|
||||
feature -- Change User token
|
||||
|
||||
new_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER;)
|
||||
-- <Precursor>.
|
||||
local
|
||||
l_parameters: STRING_TABLE [detachable ANY]
|
||||
do
|
||||
error_handler.reset
|
||||
write_information_log (generator + ".new_user_session")
|
||||
create l_parameters.make (3)
|
||||
l_parameters.put (a_user.id, "uid")
|
||||
l_parameters.put (a_token, "token")
|
||||
l_parameters.put (create {DATE_TIME}.make_now_utc, "utc_date")
|
||||
sql_begin_transaction
|
||||
sql_insert (sql_insert_session_auth, l_parameters)
|
||||
sql_commit_transaction
|
||||
sql_finalize
|
||||
end
|
||||
|
||||
update_user_session_auth (a_token: READABLE_STRING_GENERAL; a_user: CMS_USER)
|
||||
-- <Precursor>
|
||||
local
|
||||
l_parameters: STRING_TABLE [detachable ANY]
|
||||
do
|
||||
error_handler.reset
|
||||
write_information_log (generator + ".update_user_session_auth")
|
||||
create l_parameters.make (3)
|
||||
l_parameters.put (a_user.id, "uid")
|
||||
l_parameters.put (a_token, "token")
|
||||
l_parameters.put (create {DATE_TIME}.make_now_utc, "utc_date")
|
||||
sql_begin_transaction
|
||||
sql_modify (sql_update_session_auth, l_parameters)
|
||||
sql_commit_transaction
|
||||
sql_finalize
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
fetch_user: detachable CMS_USER
|
||||
local
|
||||
l_id: INTEGER_64
|
||||
l_name: detachable READABLE_STRING_32
|
||||
do
|
||||
if attached sql_read_integer_64 (1) as i then
|
||||
l_id := i
|
||||
end
|
||||
if attached sql_read_string_32 (2) as s and then not s.is_whitespace then
|
||||
l_name := s
|
||||
end
|
||||
if l_name /= Void then
|
||||
create Result.make (l_name)
|
||||
if l_id > 0 then
|
||||
Result.set_id (l_id)
|
||||
end
|
||||
elseif l_id > 0 then
|
||||
create Result.make_with_id (l_id)
|
||||
end
|
||||
if Result /= Void then
|
||||
if attached sql_read_string (3) as l_password then
|
||||
-- FIXME: should we return the password here ???
|
||||
Result.set_hashed_password (l_password)
|
||||
end
|
||||
if attached sql_read_string (5) as l_email then
|
||||
Result.set_email (l_email)
|
||||
end
|
||||
if attached sql_read_integer_32 (6) as l_status then
|
||||
Result.set_status (l_status)
|
||||
end
|
||||
else
|
||||
check
|
||||
expected_valid_user: False
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- SQL statements
|
||||
|
||||
Select_user_by_token: STRING = "SELECT u.* FROM users as u JOIN session_auth as og ON og.uid = u.uid and og.access_token = :token;"
|
||||
--| FIXME: replace the u.* by a list of field names, to avoid breaking `featch_user' if two fieds are swiped.
|
||||
|
||||
Sql_insert_session_auth: STRING = "INSERT INTO session_auth (uid, access_token, created) VALUES (:uid, :token, :utc_date);"
|
||||
|
||||
Sql_update_session_auth: STRING = "UPDATE session_auth SET access_token = :token, created = :utc_date WHERE uid =:uid;"
|
||||
|
||||
Select_user_token: STRING = "SELECT COUNT(*) FROM session_auth where uid = :uid;"
|
||||
|
||||
end
|
||||
153
modules/session_auth/site/cms_token_generator.e
Normal file
153
modules/session_auth/site/cms_token_generator.e
Normal file
@@ -0,0 +1,153 @@
|
||||
note
|
||||
description: "Provides security routine helpers"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_TOKEN_GENERATOR
|
||||
|
||||
inherit
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
feature -- Access
|
||||
|
||||
token: STRING
|
||||
-- Cryptographic random base 64 string.
|
||||
do
|
||||
Result := salt_with_size (16)
|
||||
-- Remove trailing equal sign
|
||||
Result.keep_head (Result.count - 2)
|
||||
end
|
||||
|
||||
salt: STRING
|
||||
-- Cryptographic random number of 16 bytes.
|
||||
do
|
||||
Result := salt_with_size (16)
|
||||
end
|
||||
|
||||
password: STRING
|
||||
-- Cryptographic random password of 10 bytes.
|
||||
do
|
||||
Result := salt_with_size (10)
|
||||
-- Remove trailing equal signs
|
||||
Result.keep_head (Result.count - 2)
|
||||
end
|
||||
|
||||
password_hash (a_password, a_salt: STRING): STRING
|
||||
-- Password hash based on password `a_password' and salt value `a_salt'.
|
||||
do
|
||||
Result := sha1_string (a_password + a_salt )
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
salt_with_size (a_val: INTEGER): STRING
|
||||
-- Return a salt with size `a_val'.
|
||||
local
|
||||
l_salt: SALT_XOR_SHIFT_64_GENERATOR
|
||||
l_array: ARRAY [INTEGER_8]
|
||||
i: INTEGER
|
||||
do
|
||||
create l_salt.make (a_val)
|
||||
create l_array.make_empty
|
||||
i := 1
|
||||
across
|
||||
l_salt.new_sequence as c
|
||||
loop
|
||||
l_array.force (c.item.as_integer_8, i)
|
||||
i := i + 1
|
||||
end
|
||||
Result := base_64 (l_array)
|
||||
end
|
||||
|
||||
sha1_string (a_str: STRING): STRING
|
||||
-- SHA1 diggest of `a_str'.
|
||||
do
|
||||
sha1.update_from_string (a_str)
|
||||
Result := sha1.digest_as_string
|
||||
sha1.reset
|
||||
end
|
||||
|
||||
sha1: SHA1
|
||||
-- Create a SHA1 object.
|
||||
do
|
||||
create Result.make
|
||||
end
|
||||
|
||||
feature -- Encoding
|
||||
|
||||
|
||||
base_64 (bytes: SPECIAL [INTEGER_8]): STRING_8
|
||||
-- Encodes a byte array into a STRING doing base64 encoding.
|
||||
local
|
||||
l_output: SPECIAL [INTEGER_8]
|
||||
l_remaining: INTEGER
|
||||
i, ptr: INTEGER
|
||||
char: CHARACTER
|
||||
do
|
||||
to_implement ("Check existing code to do that!!!.")
|
||||
create l_output.make_filled (0, ((bytes.count + 2) // 3) * 4)
|
||||
l_remaining := bytes.count
|
||||
from
|
||||
i := 0
|
||||
ptr := 0
|
||||
until
|
||||
l_remaining <= 3
|
||||
loop
|
||||
l_output [ptr] := encode_value (bytes [i] |>> 2)
|
||||
ptr := ptr + 1
|
||||
l_output [ptr] := encode_value (((bytes [i] & 0x3) |<< 4) | ((bytes [i + 1] |>> 4) & 0xF))
|
||||
ptr := ptr + 1
|
||||
l_output [ptr] := encode_value (((bytes [i + 1] & 0xF) |<< 2) | ((bytes [i + 2] |>> 6) & 0x3))
|
||||
ptr := ptr + 1
|
||||
l_output [ptr] := encode_value (bytes [i + 2] & 0x3F)
|
||||
ptr := ptr + 1
|
||||
l_remaining := l_remaining - 3
|
||||
i := i + 3
|
||||
end
|
||||
-- encode when exactly 1 element (left) to encode
|
||||
char := '='
|
||||
if l_remaining = 1 then
|
||||
l_output [ptr] := encode_value (bytes [i] |>> 2)
|
||||
ptr := ptr + 1
|
||||
l_output [ptr] := encode_value (((bytes [i]) & 0x3) |<< 4)
|
||||
ptr := ptr + 1
|
||||
l_output [ptr] := char.code.as_integer_8
|
||||
ptr := ptr + 1
|
||||
l_output [ptr] := char.code.as_integer_8
|
||||
ptr := ptr + 1
|
||||
end
|
||||
|
||||
-- encode when exactly 2 elements (left) to encode
|
||||
if l_remaining = 2 then
|
||||
l_output [ptr] := encode_value (bytes [i] |>> 2)
|
||||
ptr := ptr + 1
|
||||
l_output [ptr] := encode_value (((bytes [i] & 0x3) |<< 4) | ((bytes [i + 1] |>> 4) & 0xF));
|
||||
ptr := ptr + 1
|
||||
l_output [ptr] := encode_value ((bytes [i + 1] & 0xF) |<< 2);
|
||||
ptr := ptr + 1
|
||||
l_output [ptr] := char.code.as_integer_8
|
||||
ptr := ptr + 1
|
||||
end
|
||||
create Result.make_empty
|
||||
across
|
||||
l_output as elem
|
||||
loop
|
||||
Result.append_character (elem.item.to_character_8)
|
||||
end
|
||||
end
|
||||
|
||||
base64_map: SPECIAL [CHARACTER_8]
|
||||
-- Table for Base64 encoding.
|
||||
once
|
||||
Result := ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/").area
|
||||
end
|
||||
|
||||
encode_value (i: INTEGER_8): INTEGER_8
|
||||
-- Encode `i'.
|
||||
do
|
||||
Result := base64_map [i & 0x3F].code.as_integer_8
|
||||
end
|
||||
|
||||
end
|
||||
11
modules/session_auth/site/scripts/session_auth_table.sql.tpl
Normal file
11
modules/session_auth/site/scripts/session_auth_table.sql.tpl
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
CREATE TABLE session_auth (
|
||||
`uid` INTEGER PRIMARY KEY NOT NULL CHECK(`uid`>=0),
|
||||
`access_token` TEXT NOT NULL,
|
||||
`created` DATETIME NOT NULL,
|
||||
CONSTRAINT `uid`
|
||||
UNIQUE(`uid`),
|
||||
CONSTRAINT `access_token`
|
||||
UNIQUE(`access_token`)
|
||||
);
|
||||
|
||||
37
modules/session_auth/site/templates/block_login.tpl
Normal file
37
modules/session_auth/site/templates/block_login.tpl
Normal file
@@ -0,0 +1,37 @@
|
||||
<div class="primary-tabs">
|
||||
{unless isset="$user"}
|
||||
<h3>Login or <a href="{$site_url/}account/roc-register">Register</a></h3>
|
||||
<div>
|
||||
<div>
|
||||
<form name="cms_session_auth" action="{$site_url/}account/login-with-session" method="POST">
|
||||
<div>
|
||||
<input type="text" name="username" id="username" required value="{$username/}">
|
||||
<label>Username</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="password" name="password" id="password" required >
|
||||
<label>Password</label>
|
||||
</div>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
<a href="{$site_url/}account/new-password">Forgot password?</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/unless}
|
||||
{if isset=$error}
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
<strong>{$error/}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -109,6 +109,17 @@ feature -- Access node
|
||||
Result := taxonomy_storage.term_count_from_vocabulary (a_vocab)
|
||||
end
|
||||
|
||||
terms_of_content (a_content: CMS_CONTENT; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM_COLLECTION
|
||||
-- Terms related to `a_content', and if `a_vocabulary' is set
|
||||
-- constrain to be part of `a_vocabulary'.
|
||||
require
|
||||
content_with_identifier: a_content.has_identifier
|
||||
do
|
||||
if attached a_content.identifier as l_id then
|
||||
Result := terms_of_entity (a_content.content_type, l_id, a_vocabulary)
|
||||
end
|
||||
end
|
||||
|
||||
terms_of_entity (a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL; a_vocabulary: detachable CMS_VOCABULARY): detachable CMS_TERM_COLLECTION
|
||||
-- Terms related to `(a_type_name,a_entity)', and if `a_vocabulary' is set
|
||||
-- constrain to be part of `a_vocabulary'.
|
||||
@@ -179,6 +190,30 @@ feature -- Write
|
||||
error_handler.append (taxonomy_storage.error_handler)
|
||||
end
|
||||
|
||||
associate_term_with_content (a_term: CMS_TERM; a_content: CMS_CONTENT)
|
||||
-- Associate term `a_term' with `a_content'.
|
||||
require
|
||||
content_with_identifier: a_content.has_identifier
|
||||
do
|
||||
reset_error
|
||||
if attached a_content.identifier as l_id then
|
||||
taxonomy_storage.associate_term_with_entity (a_term, a_content.content_type, l_id)
|
||||
error_handler.append (taxonomy_storage.error_handler)
|
||||
end
|
||||
end
|
||||
|
||||
unassociate_term_from_content (a_term: CMS_TERM; a_content: CMS_CONTENT)
|
||||
-- Unassociate term `a_term' from `a_content'.
|
||||
require
|
||||
content_with_identifier: a_content.has_identifier
|
||||
do
|
||||
reset_error
|
||||
if attached a_content.identifier as l_id then
|
||||
taxonomy_storage.unassociate_term_from_entity (a_term, a_content.content_type, l_id)
|
||||
error_handler.append (taxonomy_storage.error_handler)
|
||||
end
|
||||
end
|
||||
|
||||
associate_term_with_entity (a_term: CMS_TERM; a_type_name: READABLE_STRING_GENERAL; a_entity: READABLE_STRING_GENERAL)
|
||||
-- Associate term `a_term' with `(a_type_name, a_entity)'.
|
||||
do
|
||||
@@ -215,6 +250,275 @@ feature -- Write
|
||||
error_handler.append (taxonomy_storage.error_handler)
|
||||
end
|
||||
|
||||
feature -- Web forms
|
||||
|
||||
populate_edit_form (a_response: CMS_RESPONSE; a_form: CMS_FORM; a_content_type_name: READABLE_STRING_8; a_content: detachable CMS_CONTENT)
|
||||
local
|
||||
ti: detachable WSF_FORM_TEXT_INPUT
|
||||
th: WSF_FORM_HIDDEN_INPUT
|
||||
w_set: WSF_FORM_FIELD_SET
|
||||
w_select: WSF_FORM_SELECT
|
||||
w_opt: WSF_FORM_SELECT_OPTION
|
||||
w_cb: WSF_FORM_CHECKBOX_INPUT
|
||||
w_voc_set: WSF_FORM_FIELD_SET
|
||||
s: STRING_32
|
||||
voc: CMS_VOCABULARY
|
||||
t: detachable CMS_TERM
|
||||
l_terms: detachable CMS_TERM_COLLECTION
|
||||
l_has_edit_permission: BOOLEAN
|
||||
do
|
||||
if
|
||||
attached vocabularies_for_type (a_content_type_name) as l_vocs and then not l_vocs.is_empty
|
||||
then
|
||||
l_has_edit_permission := a_response.has_permissions (<<"update any taxonomy", "update " + a_content_type_name + " taxonomy">>)
|
||||
|
||||
-- Handle Taxonomy fields, if any associated with `content_type'.
|
||||
create w_set.make
|
||||
w_set.add_css_class ("taxonomy")
|
||||
l_vocs.sort
|
||||
across
|
||||
l_vocs as vocs_ic
|
||||
loop
|
||||
voc := vocs_ic.item
|
||||
create th.make_with_text ({STRING_32} "taxonomy_vocabularies[" + voc.id.out + "]", voc.name)
|
||||
w_set.extend (th)
|
||||
|
||||
l_terms := Void
|
||||
if a_content /= Void then
|
||||
l_terms := terms_of_content (a_content, voc)
|
||||
if l_terms /= Void then
|
||||
l_terms.sort
|
||||
end
|
||||
end
|
||||
create w_voc_set.make
|
||||
w_set.extend (w_voc_set)
|
||||
|
||||
if voc.is_tags then
|
||||
w_voc_set.set_legend (cms_api.translation (voc.name, Void))
|
||||
|
||||
create ti.make ({STRING_32} "taxonomy_" + voc.id.out)
|
||||
w_voc_set.extend (ti)
|
||||
if voc.is_term_required then
|
||||
ti.enable_required
|
||||
end
|
||||
if attached voc.description as l_desc then
|
||||
ti.set_description (cms_api.html_encoded (cms_api.translation (l_desc, Void)))
|
||||
else
|
||||
ti.set_description (a_response.html_encoded (cms_api.translation (voc.name, Void)))
|
||||
end
|
||||
ti.set_size (70)
|
||||
if l_terms /= Void then
|
||||
create s.make_empty
|
||||
across
|
||||
l_terms as ic
|
||||
loop
|
||||
t := ic.item
|
||||
if not s.is_empty then
|
||||
s.append_character (',')
|
||||
s.append_character (' ')
|
||||
end
|
||||
if ic.item.text.has (' ') then
|
||||
s.append_character ('"')
|
||||
s.append (t.text)
|
||||
s.append_character ('"')
|
||||
else
|
||||
s.append (t.text)
|
||||
end
|
||||
end
|
||||
ti.set_text_value (s)
|
||||
end
|
||||
if not l_has_edit_permission then
|
||||
ti.set_is_readonly (True)
|
||||
end
|
||||
else
|
||||
fill_vocabularies_with_terms (voc)
|
||||
if not voc.terms.is_empty then
|
||||
if voc.multiple_terms_allowed then
|
||||
if attached voc.description as l_desc then
|
||||
w_voc_set.set_legend (cms_api.html_encoded (l_desc))
|
||||
else
|
||||
w_voc_set.set_legend (cms_api.html_encoded (voc.name))
|
||||
end
|
||||
across
|
||||
voc as voc_terms_ic
|
||||
loop
|
||||
t := voc_terms_ic.item
|
||||
create w_cb.make_with_value ({STRING_32} "taxonomy_" + voc.id.out + "[]", t.text)
|
||||
w_cb.set_title (t.text)
|
||||
w_voc_set.extend (w_cb)
|
||||
if l_terms /= Void and then across l_terms as ic some ic.item.text.same_string (t.text) end then
|
||||
w_cb.set_checked (True)
|
||||
end
|
||||
if not l_has_edit_permission then
|
||||
w_cb.set_is_readonly (True)
|
||||
end
|
||||
end
|
||||
else
|
||||
create w_select.make ({STRING_32} "taxonomy_" + voc.id.out)
|
||||
w_voc_set.extend (w_select)
|
||||
|
||||
if attached voc.description as l_desc then
|
||||
w_select.set_description (cms_api.html_encoded (l_desc))
|
||||
else
|
||||
w_select.set_description (cms_api.html_encoded (voc.name))
|
||||
end
|
||||
w_voc_set.set_legend (cms_api.html_encoded (voc.name))
|
||||
|
||||
across
|
||||
voc as voc_terms_ic
|
||||
loop
|
||||
t := voc_terms_ic.item
|
||||
create w_opt.make (cms_api.html_encoded (t.text), cms_api.html_encoded (t.text))
|
||||
w_select.add_option (w_opt)
|
||||
|
||||
if l_terms /= Void and then across l_terms as ic some ic.item.text.same_string (t.text) end then
|
||||
w_opt.set_is_selected (True)
|
||||
end
|
||||
end
|
||||
if not l_has_edit_permission then
|
||||
w_select.set_is_readonly (True)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
a_form.submit_actions.extend (agent taxonomy_submit_action (a_response, Current, l_vocs, a_content, ?))
|
||||
|
||||
if
|
||||
attached a_form.fields_by_name ("title") as l_title_fields and then
|
||||
attached l_title_fields.first as l_title_field
|
||||
then
|
||||
a_form.insert_after (w_set, l_title_field)
|
||||
else
|
||||
a_form.extend (w_set)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
taxonomy_submit_action (a_response: CMS_RESPONSE; a_taxonomy_api: CMS_TAXONOMY_API; a_vocs: CMS_VOCABULARY_COLLECTION; a_content: detachable CMS_CONTENT fd: WSF_FORM_DATA)
|
||||
require
|
||||
vocs_not_empty: not a_vocs.is_empty
|
||||
local
|
||||
l_voc_name: READABLE_STRING_32
|
||||
l_terms_to_remove: ARRAYED_LIST [CMS_TERM]
|
||||
l_new_terms: LIST [READABLE_STRING_32]
|
||||
l_text: READABLE_STRING_GENERAL
|
||||
l_found: BOOLEAN
|
||||
t: detachable CMS_TERM
|
||||
vid: INTEGER_64
|
||||
do
|
||||
if
|
||||
a_content /= Void and then a_content.has_identifier and then
|
||||
attached fd.table_item ("taxonomy_vocabularies") as fd_vocs
|
||||
then
|
||||
if a_response.has_permissions (<<{STRING_32} "update any taxonomy", {STRING_32} "update " + a_content.content_type + " taxonomy">>) then
|
||||
across
|
||||
fd_vocs.values as ic
|
||||
loop
|
||||
vid := ic.key.to_integer_64
|
||||
l_voc_name := ic.item.string_representation
|
||||
|
||||
if attached a_vocs.item_by_id (vid) as voc then
|
||||
if attached fd.string_item ("taxonomy_" + vid.out) as l_string then
|
||||
l_new_terms := a_taxonomy_api.splitted_string (l_string, ',')
|
||||
elseif attached fd.table_item ("taxonomy_" + vid.out) as fd_terms then
|
||||
create {ARRAYED_LIST [READABLE_STRING_32]} l_new_terms.make (fd_terms.count)
|
||||
across
|
||||
fd_terms as t_ic
|
||||
loop
|
||||
l_new_terms.force (t_ic.item.string_representation)
|
||||
end
|
||||
else
|
||||
create {ARRAYED_LIST [READABLE_STRING_32]} l_new_terms.make (0)
|
||||
end
|
||||
|
||||
create l_terms_to_remove.make (0)
|
||||
if attached a_taxonomy_api.terms_of_content (a_content, voc) as l_existing_terms then
|
||||
across
|
||||
l_existing_terms as t_ic
|
||||
loop
|
||||
l_text := t_ic.item.text
|
||||
from
|
||||
l_found := False
|
||||
l_new_terms.start
|
||||
until
|
||||
l_new_terms.after
|
||||
loop
|
||||
if l_new_terms.item.same_string_general (l_text) then
|
||||
-- Already associated with term `t_ic.text'.
|
||||
l_found := True
|
||||
l_new_terms.remove
|
||||
else
|
||||
l_new_terms.forth
|
||||
end
|
||||
end
|
||||
if not l_found then
|
||||
-- Remove term
|
||||
l_terms_to_remove.force (t_ic.item)
|
||||
end
|
||||
end
|
||||
across
|
||||
l_terms_to_remove as t_ic
|
||||
loop
|
||||
a_taxonomy_api.unassociate_term_from_content (t_ic.item, a_content)
|
||||
end
|
||||
end
|
||||
across
|
||||
l_new_terms as t_ic
|
||||
loop
|
||||
t := a_taxonomy_api.term_by_text (t_ic.item, voc)
|
||||
if
|
||||
t = Void and voc.is_tags
|
||||
then
|
||||
-- Create new term!
|
||||
create t.make (t_ic.item)
|
||||
a_taxonomy_api.save_term (t, voc)
|
||||
if a_taxonomy_api.has_error then
|
||||
t := Void
|
||||
end
|
||||
end
|
||||
if t /= Void then
|
||||
a_taxonomy_api.associate_term_with_content (t, a_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
append_taxonomy_to_xhtml (a_content: CMS_CONTENT; a_response: CMS_RESPONSE; a_output: STRING)
|
||||
-- Append taxonomy related to `a_content' to xhtml string `a_output',
|
||||
-- using `a_response' helper routines.
|
||||
do
|
||||
if
|
||||
attached vocabularies_for_type (a_content.content_type) as vocs and then not vocs.is_empty
|
||||
then
|
||||
vocs.sort
|
||||
across
|
||||
vocs as ic
|
||||
loop
|
||||
if
|
||||
attached terms_of_content (a_content, ic.item) as l_terms and then
|
||||
not l_terms.is_empty
|
||||
then
|
||||
a_output.append ("<ul class=%"taxonomy term-" + ic.item.id.out + "%">")
|
||||
a_output.append (cms_api.html_encoded (ic.item.name))
|
||||
a_output.append (": ")
|
||||
across
|
||||
l_terms as t_ic
|
||||
loop
|
||||
a_output.append ("<li>")
|
||||
a_response.append_link_to_html (t_ic.item.text, "taxonomy/term/" + t_ic.item.id.out, Void, a_output)
|
||||
a_output.append ("</li>")
|
||||
end
|
||||
a_output.append ("</ul>%N")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Helpers
|
||||
|
||||
splitted_string (s: READABLE_STRING_32; sep: CHARACTER): LIST [READABLE_STRING_32]
|
||||
|
||||
28
src/file/cms_file_uploader.e
Normal file
28
src/file/cms_file_uploader.e
Normal file
@@ -0,0 +1,28 @@
|
||||
note
|
||||
description: "Summary description for {CMS_FILE_UPLOADER}."
|
||||
author: "Fabian Murer"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_FILE_UPLOADER
|
||||
|
||||
inherit
|
||||
ANY
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- initialize
|
||||
|
||||
make (a_setup: CMS_SETUP)
|
||||
-- creates the CMS_FILE_UPLOADER with a setup `a_setup'
|
||||
do
|
||||
|
||||
end
|
||||
|
||||
|
||||
note
|
||||
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
end
|
||||
@@ -292,6 +292,26 @@ feature -- Logging
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Internationalization (i18n)
|
||||
|
||||
translation (a_text: READABLE_STRING_GENERAL; opts: detachable CMS_API_OPTIONS): STRING_32
|
||||
-- Translated text `a_text' according to expected context (lang, ...)
|
||||
-- and adapt according to options eventually set by `opts'.
|
||||
do
|
||||
to_implement ("Implement i18n support [2015-may]")
|
||||
Result := a_text.as_string_32
|
||||
end
|
||||
|
||||
formatted_string (a_text: READABLE_STRING_GENERAL; args: TUPLE): STRING_32
|
||||
-- Format `a_text' using arguments `args'.
|
||||
--| ex: formatted_string ("hello $1, see page $title.", ["bob", "contact"] -> "hello bob, see page contact"
|
||||
local
|
||||
l_formatter: CMS_STRING_FORMATTER
|
||||
do
|
||||
create l_formatter
|
||||
Result := l_formatter.formatted_string (a_text, args)
|
||||
end
|
||||
|
||||
feature -- Emails
|
||||
|
||||
new_email (a_to_address: READABLE_STRING_8; a_subject: READABLE_STRING_8; a_content: READABLE_STRING_8): CMS_EMAIL
|
||||
|
||||
@@ -13,6 +13,11 @@ inherit
|
||||
|
||||
feature -- Access
|
||||
|
||||
identifier: detachable READABLE_STRING_32
|
||||
-- Optional identifier.
|
||||
deferred
|
||||
end
|
||||
|
||||
title: detachable READABLE_STRING_32
|
||||
-- Title associated with Current content.
|
||||
deferred
|
||||
@@ -37,6 +42,14 @@ feature -- Access
|
||||
|
||||
feature -- Status report
|
||||
|
||||
has_identifier: BOOLEAN
|
||||
-- Current content has identifier?
|
||||
do
|
||||
Result := identifier /= Void
|
||||
ensure
|
||||
Result implies identifier /= Void
|
||||
end
|
||||
|
||||
is_typed_as (a_content_type: READABLE_STRING_GENERAL): BOOLEAN
|
||||
-- Is current node of type `a_content_type' ?
|
||||
do
|
||||
|
||||
@@ -26,6 +26,9 @@ feature {NONE} -- Initialization
|
||||
|
||||
feature -- Access
|
||||
|
||||
identifier: detachable READABLE_STRING_32
|
||||
-- <Precursor>
|
||||
|
||||
title: detachable READABLE_STRING_32
|
||||
-- Title associated with Current content.
|
||||
|
||||
@@ -42,6 +45,15 @@ feature -- Access
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_identifier (a_identifier: detachable READABLE_STRING_GENERAL)
|
||||
do
|
||||
if a_identifier = Void then
|
||||
identifier := Void
|
||||
else
|
||||
create {IMMUTABLE_STRING_32} identifier.make_from_string_general (a_identifier)
|
||||
end
|
||||
end
|
||||
|
||||
set_title (a_title: detachable READABLE_STRING_GENERAL)
|
||||
do
|
||||
if a_title = Void then
|
||||
|
||||
@@ -113,26 +113,6 @@ feature -- Access
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Internationalization (i18n)
|
||||
|
||||
translation (a_text: READABLE_STRING_GENERAL; opts: detachable CMS_API_OPTIONS): STRING_32
|
||||
-- Translated text `a_text' according to expected context (lang, ...)
|
||||
-- and adapt according to options eventually set by `opts'.
|
||||
do
|
||||
to_implement ("Implement i18n support [2015-may]")
|
||||
Result := a_text.as_string_32
|
||||
end
|
||||
|
||||
formatted_string (a_text: READABLE_STRING_GENERAL; args: TUPLE): STRING_32
|
||||
-- Format `a_text' using arguments `args'.
|
||||
--| ex: formatted_string ("hello $1, see page $title.", ["bob", "contact"] -> "hello bob, see page contact"
|
||||
local
|
||||
l_formatter: CMS_STRING_FORMATTER
|
||||
do
|
||||
create l_formatter
|
||||
Result := l_formatter.formatted_string (a_text, args)
|
||||
end
|
||||
|
||||
feature -- API
|
||||
|
||||
api: CMS_API
|
||||
@@ -896,6 +876,22 @@ feature -- Menu: change
|
||||
m.extend (lnk)
|
||||
end
|
||||
|
||||
feature -- Internationalization (i18n)
|
||||
|
||||
translation (a_text: READABLE_STRING_GENERAL; opts: detachable CMS_API_OPTIONS): STRING_32
|
||||
-- Translated text `a_text' according to expected context (lang, ...)
|
||||
-- and adapt according to options eventually set by `opts'.
|
||||
do
|
||||
Result := api.translation (a_text, opts)
|
||||
end
|
||||
|
||||
formatted_string (a_text: READABLE_STRING_GENERAL; args: TUPLE): STRING_32
|
||||
-- Format `a_text' using arguments `args'.
|
||||
--| ex: formatted_string ("hello $1, see page $title.", ["bob", "contact"] -> "hello bob, see page contact"
|
||||
do
|
||||
Result := api.formatted_string (a_text, args)
|
||||
end
|
||||
|
||||
feature -- Message
|
||||
|
||||
add_message (a_msg: READABLE_STRING_8; a_category: detachable READABLE_STRING_8)
|
||||
|
||||
Reference in New Issue
Block a user