Compare commits
90 Commits
es_15_08
...
taxonomy_c
| Author | SHA1 | Date | |
|---|---|---|---|
| ecbcb6a5cb | |||
| a5c117e46e | |||
| 20dfce1396 | |||
|
|
1bfc4a6741 | ||
|
|
3fdbcb2eef | ||
|
|
a11a93c285 | ||
|
|
fade19bbee | ||
|
|
d10612f94b | ||
|
|
9da8b8a025 | ||
| f1f3c126dd | |||
| 1d4ce37ebf | |||
|
|
10102e80fa | ||
| 3791ffacdc | |||
| b8920ee8b3 | |||
| 2cf2b1da8c | |||
| 17ae27df40 | |||
|
|
a976b1e21a | ||
| 04df6b85f0 | |||
| 79d30ee3a7 | |||
| a5973c9c8a | |||
| 420051cd14 | |||
| 6b3ff6f980 | |||
| 360855a558 | |||
| 6aaec0be9f | |||
| cb6d13b5f7 | |||
| 951c977892 | |||
| 2a4ebfa12e | |||
| 955852747a | |||
| 23fe22cad1 | |||
| da4b36869a | |||
| 5624892ebc | |||
|
|
e40a7969fa | ||
|
|
67fdd357df | ||
| 5b0ab76434 | |||
| a84f86d7a2 | |||
| 3f4e70b98c | |||
| 782e9397a3 | |||
| f51ddc9796 | |||
| a260bbc2c5 | |||
| eb5ae32e46 | |||
| 7fcacad5eb | |||
| 7c99f2dc83 | |||
| 57430193e3 | |||
| 62bf58ce6d | |||
| 23c395513b | |||
| d2d86ecdf2 | |||
| e90f82387f | |||
| 05472abdd7 | |||
|
|
193760b34f | ||
|
|
9263f31521 | ||
|
|
454d92f85b | ||
| 788cf3738d | |||
|
|
0e63c14613 | ||
| f6185612b2 | |||
| d37f45d958 | |||
| 872f2a177d | |||
| 3ed2f410d9 | |||
| 1b451ef142 | |||
| 50146985de | |||
| 8cdf9ba973 | |||
| 8044f7d52b | |||
| ed24eb7c94 | |||
| 43d6b4a197 | |||
| dffd06e331 | |||
| 463105f29f | |||
| abebd00a4f | |||
| ec53a2682b | |||
|
|
7b2e6ab7b4 | ||
|
|
87f4de1264 | ||
|
|
ed614a662c | ||
|
|
d54ad59e5f | ||
|
|
9173ef2ded | ||
|
|
ad9e908dc2 | ||
|
|
4584917877 | ||
|
|
f7d68d09e4 | ||
| f9ecd4956f | |||
| 18e159ad3c | |||
| 438259033a | |||
| 62e74ea6cd | |||
| 32a409b7e9 | |||
|
|
eb70af6f19 | ||
| 5f4eb2cf10 | |||
| 88bc52fffb | |||
|
|
544e6540ed | ||
| 2431d7af6c | |||
|
|
0d55bd67a2 | ||
|
|
e1727cc445 | ||
|
|
634a078282 | ||
|
|
2f65a084ac | ||
|
|
13df6fd593 |
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="cms" uuid="8CC0D052-57D1-4CAA-AFF1-448FA290734B" library_target="cms">
|
||||
<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="cms" uuid="8CC0D052-57D1-4CAA-AFF1-448FA290734B" library_target="cms">
|
||||
<target name="cms">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
@@ -14,14 +14,16 @@
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension-safe.ecf"/>
|
||||
<library name="cms_app_env" location=".\library\app_env\app_env-safe.ecf"/>
|
||||
<library name="cms_model" location=".\library\model\cms_model-safe.ecf" readonly="false"/>
|
||||
<library name="cms_config" location=".\library\configuration\config-safe.ecf"/>
|
||||
<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" readonly="false"/>
|
||||
<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="i18n" location="$ISE_LIBRARY\library\i18n\i18n-safe.ecf"/>
|
||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf" readonly="false"/>
|
||||
<library name="kmp_matcher" location="$ISE_LIBRARY\library\text\regexp\kmp_matcher\kmp_matcher-safe.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||
<library name="notification_mailer" location="$ISE_LIBRARY\contrib\library\runtime\process\notification_email\notification_email-safe.ecf"/>
|
||||
<library name="smarty" location="$ISE_LIBRARY\contrib\library\text\template\smarty\smarty-safe.ecf" readonly="false"/>
|
||||
<library name="text_filter" location="$ISE_LIBRARY\unstable\library\text\text_filter\text_filter-safe.ecf"/>
|
||||
@@ -30,7 +32,6 @@
|
||||
<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"/>
|
||||
<library name="wsf_html" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf_html\wsf_html-safe.ecf" readonly="false"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
|
||||
5
cms.ecf
5
cms.ecf
@@ -15,14 +15,16 @@
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension.ecf"/>
|
||||
<library name="cms_app_env" location=".\library\app_env\app_env.ecf"/>
|
||||
<library name="cms_model" location=".\library\model\cms_model.ecf" readonly="false"/>
|
||||
<library name="cms_config" location=".\library\configuration\config.ecf"/>
|
||||
<library name="cms_model" location=".\library\model\cms_model.ecf" readonly="false"/>
|
||||
<library name="crypto" location="$ISE_LIBRARY\unstable\library\text\encryption\crypto\crypto.ecf"/>
|
||||
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder.ecf" readonly="false"/>
|
||||
<library name="error" location="$ISE_LIBRARY\contrib\library\utility\general\error\error.ecf"/>
|
||||
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http.ecf"/>
|
||||
<library name="i18n" location="$ISE_LIBRARY\library\i18n\i18n.ecf"/>
|
||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json.ecf" readonly="false"/>
|
||||
<library name="kmp_matcher" location="$ISE_LIBRARY\library\text\regexp\kmp_matcher\kmp_matcher.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||
<library name="notification_mailer" location="$ISE_LIBRARY\contrib\library\runtime\process\notification_email\notification_email.ecf"/>
|
||||
<library name="smarty" location="$ISE_LIBRARY\contrib\library\text\template\smarty\smarty.ecf" readonly="false"/>
|
||||
<library name="text_filter" location="$ISE_LIBRARY\unstable\library\text\text_filter\text_filter.ecf"/>
|
||||
@@ -31,7 +33,6 @@
|
||||
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf.ecf"/>
|
||||
<library name="wsf_extension" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf_extension.ecf" readonly="false"/>
|
||||
<library name="wsf_html" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf_html\wsf_html.ecf" readonly="false"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
CMS Concepts
|
||||
============
|
||||
>Current implemented concepts
|
||||
|
||||
##### Table of Contents
|
||||
|
||||
1. [**Theme**](#theme)
|
||||
2. [**Regions**](#regions)
|
||||
- [**Default Page Layout**](#page_layout)
|
||||
- [**Regions Holds blocks**](#regions_blocks)
|
||||
3. [**Blocks**](#blocks)
|
||||
4. [**Modules**](#modules)
|
||||
5. [**Hooks**](#hooks)
|
||||
|
||||
|
||||
<a name="theme"/>
|
||||
Theme
|
||||
-----
|
||||
In a CMS , a theme is a collection of templates files (HTML, CSS, Images, etc ) that determine how a CMS web site looks. The goal of a theme is to let you change the look and feel of the site.
|
||||
Eiffel CMS is inspired by Drupal, and use the same default region names as default drupal theme.
|
||||
|
||||
#### Important Classes
|
||||
|
||||
* [CMS_THEME] (/library/src/theme/cms_theme.e): Abstraction defining the interface of a CMS theme.
|
||||
* [SMARTY_CMS_THEME] (/library/src/theme/smarty_theme/smarty_cms_theme.e): Theme implemented using the [Eiffel Smarty library] (https://github.com/eiffelhub/template-smarty).
|
||||
* [CMS_TEMPLATE] (/library/src/theme/cms_template.e): Template Abstraction that contains theme, variables needed by template when rendering page as html. At the moment there is only one implementation SMARTY_CMS_PAGE_TEMPLATE. At the moment there is only one implementation [SMARTY_CMS_PAGE_TEMPLATE] (/library/src/theme/smarty_theme/smarty_cms_page_template.e).
|
||||
|
||||
<a name="regions"/>
|
||||
Regions
|
||||
-------
|
||||
The layout of a CMS web page has predefined area called **regions**. The Eiffel CMS uses the same default regions as Drupal, so let's see them in the following image.
|
||||
|
||||
<a name="page_layout"/>
|
||||

|
||||
|
||||
```
|
||||
regions[page_top] = Top
|
||||
regions[header] = Header
|
||||
regions[content] = Content
|
||||
regions[highlighted] = Highlighted
|
||||
regions[help] = Help
|
||||
regions[footer] = Footer
|
||||
regions[first_sidebar] = first sidebar
|
||||
regions[second_sidebar] = second sidebar
|
||||
regions[page_bottom] = Bottom
|
||||
```
|
||||
<a name="regions_blocks"/>
|
||||
**A Region holds blocks**
|
||||
|
||||
**What goes inside regions?**
|
||||
Generally, regions hold smaller piece of content called blocks. Blocks hold chunks of content, like the user login form, navigation menu or the information for the footer.
|
||||
|
||||
Regions are defined in a configuration file theme.info.
|
||||
|
||||
|
||||
<a name="blocks"/>
|
||||
CMS_BLOCK
|
||||
---------
|
||||
**What is a cms block?**
|
||||
Blocks are chunk of content that can be created to display whatever you want, and then can be placed in various resgions in your template (theme) layout.
|
||||
|
||||
#### Important Classes
|
||||
|
||||
* [CMS_BLOCK] (/library/src/kernel/content/cms_block.e): The deferred class CMS_BLOCK provides an abstraction to describe content to be placed inside Regions.
|
||||
* [CMS_CONTENT_BLOCK] (/library/src/kernel/content/cms_content_block.e): The class CMS_CONTENT_BLOCK describe how to provide generic content.
|
||||
* [CMS_MENU_BLOCK](/library/src/kernel/content/cms_menu_block.e): The class CMS_MENU_BLOCK describe how to provides a menu of navigational links.
|
||||
* [CMS_SMARTY_TEMPLATE_BLOCK] (/library/src/kernel/content/cms_smarty_templateblock.e) The class CMS_SMARTY_TEMPLATE_BLOCK describe how to use a CMS block with smarty template file content.
|
||||
|
||||
|
||||
<a name="modules"/>
|
||||
CMS_MODULES
|
||||
-----------
|
||||
**What is a cms module?**
|
||||
Modules are piece of code that adds one or more features to your web site.
|
||||
Modules can be plugged and combined to provide a web site customized to your needs. There are modules for many purposes, for example Administratiton, Basic Authentication, etc.
|
||||
|
||||
#### Important Classes
|
||||
* [CMS_MODULE] (/library/src/modules/cms_module.e): The deferred class CMS_MODULE provides an abstraction to describe a generic module that add features to your web site.
|
||||
* [CMS_RESPONSE](/library/src/service/response/cms_response.e). The deferred class CMS_RESPONSE provide an abstraction to builds the content to get process to render the output.
|
||||
|
||||
|
||||
<a name="hooks">
|
||||
CMS_HOOK
|
||||
--------
|
||||
Hooks is a mechanism which provide a way for modules to interact with each other and extending blocks of the current CMS.
|
||||
|
||||
* [CMS_HOOK] (/library/src/hooks/cms_hook.e): The deferred class CMS_HOOK is a marker interface for CMS Hook
|
||||
* [CMS_HOOK_AUTO_REGISTER] (/library/src/hooks/cms_hook_auto_register.e): The deferred class provides an abstraction that when inheriting from this class, the declared hooks are automatically registered, otherwise, each descendant has to add it to the cms service itself.
|
||||
* [CMS_HOOK_BLOCK](/library/src/hooks/cms_hook_block.e): The class CMS_HOOK_BLOCK describe a hook providing a way to alter a generic block.
|
||||
* [CMS_HOOK_FORM_ALTER](/library/src/hooks/cms_hook_form_alter.e): The class CMS_HOOK_FORM_ATLER describe a hook providing a way to alter a form.
|
||||
* [CMS_HOOK_MENU_ALTER](/library/src/hooks/cms_hook_menu_alter.e): The class CMS_HOOK_MENU_ATLER describe a hook providing a way to alter a menu.
|
||||
* [CMS_HOOK_MENU_SYSTEM_ALTER](/library/src/hooks/cms_hook_menu_system_alter.e): The class CMS_HOOK_MENU_SYSTEM_ALTER describe a hook providing a way to alter the CMS menu system.
|
||||
* [CMS_HOOK_VALUE_TABLE_ALTER](/library/src/hooks/cms_hook_value_table_alter.e):: The class CMS_HOOK_VALUE_TABLE_ALTER describe a hook providing a way to alter the value table for a response.
|
||||
BIN
doc/img_diagram.png
Normal file
BIN
doc/img_diagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
396
doc/readme.md
Normal file
396
doc/readme.md
Normal file
@@ -0,0 +1,396 @@
|
||||
ROC CMS Documentation
|
||||
=====================
|
||||
|
||||
[TOC]
|
||||
|
||||
## Overview
|
||||
|
||||
**ROC CMS** stands for "REST On CMS", however, until now, no particular focus was done on the REST API approach, and so far a more pragmatic approach dominated.
|
||||
|
||||
Part of the design is inspired by Drupal (blocks, hooks, Role-based access control, ...), and other parts related to Eiffel. Priorities, modules and related have been driven by concrete need, in order to fulfill the https://eiffel.org/ websites. Also a contribution (as student projects, or others) helped build various modules or functionality.
|
||||
|
||||
Currently, **ROC CMS** is a library or **framework** that provides components, tools and resources to build a CMS (Content Management System). It is not currently a CMS product, one can install and customize without any code.
|
||||
|
||||
Thus, it will be interesting for people willing to build a website using **Eiffel**. This will enable to reuse other Eiffel components, better integration with other Eiffel projects, and of course benefit from all the goodies of the Eiffel technologies (Eiffel language, DbC, re-usability, portability, IDE, debugger,...).
|
||||
|
||||
It depends on the **Eiffel Web Framework** (known as "Eiffel Web" or "EWF"), and thus can be executed as standalone, or CGI, libFCGI mode on Apache2 for instance, and on any Windows or Linux platform).
|
||||
|
||||
The main notions are:
|
||||
- CMS Execution
|
||||
- CMS APIs
|
||||
- CMS Response
|
||||
- CMS Modules
|
||||
- CMS Hooks
|
||||
- CMS Theme, blocks, links, ...
|
||||
|
||||
Those points will be described later in appropriated sections.
|
||||
|
||||
## Setup
|
||||
|
||||
|
||||
The ROC CMS source is available either with the latest EiffelStudio release under the locations:
|
||||
- $ISE_LIBRARY\unstable\library\web\cms
|
||||
- or from github project https://github.com/EiffelWebFramework/ROC branch v0 for now.
|
||||
```
|
||||
git clone https://github.com/EiffelWebFramework/ROC -b v0
|
||||
```
|
||||
|
||||
Note that if you use the source code from the github repository, you will need to use the latest release of EiffelStudio as it relieѕ on recent version of various libraries such as EWF, sqlite3, ....
|
||||
And using the "master" branch, even the trunk version of EiffelStudio libraries. So for now, we encourage you to use the ROC CMS shipped with your EiffelStudio.
|
||||
|
||||
Once you have the source code, you should compile project <code>cms/example/demo/demo-safe.ecf</code> target "demo_standalone".
|
||||
```
|
||||
# from Command line
|
||||
cd example
|
||||
cd demo
|
||||
ec -config demo-safe.ecf -c_compile -finalize
|
||||
cp ./EIFGENs/demo_standalone/F_Code/demo.exe demo.exe
|
||||
demo.exe
|
||||
# or launch EiffelStudio, and open that project, compile and execute it inside the debugger for instance.
|
||||
````
|
||||
|
||||
This demo includes all the official ROC CMS modules, files, and use libsqlite3 as default storage engine. So you should be able to execute it easily. The **standalone** target is configured to listen on port 9090 by default. (Mostly to avoid conflict on other app that my listen on port 80 or 8080).
|
||||
|
||||
In the directory <code>site</code> you will find all the expected files that should be in the root directory.
|
||||
* config/ : it contains the various configuration files, especially the **cms.ini**.
|
||||
* modules/ : files associated with each installed ROC CMS module.
|
||||
* scripts/ : common scripts used mainly to initialize SQL databases.
|
||||
* themes/ : folder containing the available ROC CMS themes.
|
||||
* files/ : folder containing files available from the ROC CMS app.
|
||||
* And also demo.ini that contains the settings for the web launcher, (in our case, the standalone Eiffel server), such as port_number.
|
||||
|
||||
Now that you know how to compile, execute, and see the related configuration files, let's describes the main notions of the ROC CMS, first from
|
||||
* an admin point of view (dev using ROC CMS to build its site),
|
||||
* and then from a developer point of view (in case you want to contribute to ROC CMS).
|
||||
|
||||
## Usage
|
||||
|
||||
### Main entries
|
||||
As a CMS administrator, you will need to setup your CMS application (here the demo example). For this purpose, the main entry points are the CMS_EXECUTION interface, and then the <code>site/</code> files (configuration, themes, templates, ...).
|
||||
|
||||
### CMS initialization/Execution
|
||||
The `CMS_EXECUTION` interface is deferred, and your CMS application needs to inherit from it and define `setup_storage`, `initial_cms_setup` and `setup_modules`. See for instance `DEMO_CMS_EXECUTION`.
|
||||
|
||||
So, the descendant of `CMS_EXECUTION` (`DEMO_CMS_EXECUTION` in the example), is creating the `CMS_SETUP`, declares the available **storage** builders (for persistency), and declares the available **modules**.
|
||||
|
||||
#### Persistence/Storage
|
||||
Depending on the **configuration**, the CMS engine will instantiate and use a specific **CMS_STORAGE** (the default is based on `Eiffel sqlite3`, otherwise `EiffelStore+MySQL` and `EiffelStore+ODBC` are available). The storage solution is used to implement the persistence layer, and thus store and load CMS data to disk, or database.
|
||||
|
||||
The CMS provides, for now, storage based on
|
||||
* EiffelStore + MySQL
|
||||
* EiffelStore + ODBC (could be used for MySQL, sqlite, SQLserver, ...)
|
||||
* Eiffel sqlite3 : that one is the default storage, since it is convenient for testing, but it is recommended to use EiffelStore+MySQL in production CMS site.
|
||||
A typical implementation of <code>setup_storage</code> is:
|
||||
```eiffel
|
||||
setup_storage (a_setup: CMS_SETUP)
|
||||
do
|
||||
a_setup.storage_drivers.force (create {CMS_STORAGE_SQLITE3_BUILDER}.make, "sqlite3")
|
||||
-- a_setup.storage_drivers.force (create {CMS_STORAGE_STORE_ODBC_BUILDER}.make, "odbc")
|
||||
end
|
||||
```
|
||||
And the CMS decides which storage should be used. It depends on the application configuration. See the **configuration** section.
|
||||
|
||||
Those data could be user information (login, email, password, ...), custom values, logs, emails, path aliases, ... and any data modules may need to store (for instance node content, for the `node` module.)
|
||||
|
||||
#### Modules
|
||||
|
||||
The `setup_module` is used to declare available **modules** (instances of `CMS_MODULE` effective types).
|
||||
The modular design provides a simple way to extend or alter the CMS functionalities/behaviors.
|
||||
Most of the CMS features are implemented by modules, and each module relies on the core of the CMS core.
|
||||
This **core** contains the `CMS_API`, `CMS_USER_API`, and various internal mechanisms such as mailer, logger, ...
|
||||
|
||||
Use `setup_module (a_setup: CMS_SETUP)` to customize the `CMS_SETUP` object created by `initial_cms_setup`.
|
||||
For your convenience, ROC CMS provides a `CMS_DEFAULT_SETUP` that import configuration from `site/config/cms.ini`
|
||||
|
||||
So far, what you need to remember is `CMS_EXECUTION` class and descendants are used to set up the ROC CMS application, for storage, modules, and also how to load configuration.
|
||||
|
||||
Note that a module can have 3 states:
|
||||
- not installed,
|
||||
- installed and enabled,
|
||||
- installed and disabled.
|
||||
|
||||
At first, to install the modules, open your browser at location `https://hostname:port/admin/install` and click the associated button.
|
||||
(Note: for new module addition, you also need to install them, using the same link, in the future, there will be a proper module management interface, in the admin front-end.)
|
||||
|
||||
To enable or disable a module, you will need to use the `cms.ini` configuration file, please see the **configuration** section.
|
||||
|
||||
Existing modules:
|
||||
- **admin**: basic administration pages, to manage modules, roles, permissions, users, caches, ... (note: it is still very basic, and need effort to improve it.)
|
||||
- authentication modules based on **auth**:
|
||||
- **basic_auth**: account signing using basic HTTP Authorization solution
|
||||
- **oauth20**: sign using a thirdparty OAuth2.0 account (such as Google, Facebook, github, ...)
|
||||
- **openid**: sign using an OpenID account.
|
||||
- **node**: the base of node management, include **Page** content type.
|
||||
- **blog**: extends the **node** module with a **blog** content type.
|
||||
- **recent_changes**: compute recent changes of CMS (integration with **node** management, and any modules that implement the `CMS_RECENT_CHANGES_HOOK`).
|
||||
- **feed_aggregator**: aggregate one or many feeds (rss, atom, ...), and provide associated pages or blocks.
|
||||
- **google_search**: provides search facilities using the Google Custom Search API.
|
||||
|
||||
### Configuration
|
||||
When `CMS_DEFAULT_SETUP` is used, the CMS configuration is loaded from `site/config/cms.ini`.
|
||||
That file contains a few sections:
|
||||
- **site**: to set the `name`, `email` and the name of the `theme`. (See "Themes" section pour information.)
|
||||
- **layout**: the application layout (or environment) can precise the `root-dir`, `themes-dir`, `modules-dir`. If not defined, the values are computed from Current working directory.
|
||||
- **mailer**: the CMS can send email notification for various reasons, such as new users, or reset password functionalities, ... In this section, you can use
|
||||
- `smtp` settings to precise an SMTP server (+ port),
|
||||
- or `sendmail` to use an external script using the sendmail usage,
|
||||
- or just an `output` file such as @stderr, or a path to a file on disk.
|
||||
- **modules**: used to enable or disable modules.
|
||||
- `*=on` -> modules are enabled by default
|
||||
- `*=off` -> modules are disabled by default
|
||||
- Note the default value is `on`
|
||||
- For each module, this can be overwritten with `module_name=on|off`
|
||||
- **blocks**: settings for blocks (See Themes, Blocks sections for more information on the block). A few parameters are available to customize blocks. The general form is `block-name.param=value` (note that "foo.bar" is a value block name.)
|
||||
- `block-name.region`: assign the block `block-name` to a specific region. A block can be assigned to **only one region**.
|
||||
- `block-name.title`: used to overwrite the block title (with <none> , the title is hidden).
|
||||
- `block-name.weight`: used to order blocks in the same region (blocks with lower weight goes first).
|
||||
- `block-name.expiration`: used to provide a basic cache system based on expiration. The value is a number of seconds before the cache expires (-1: never expires, 0: never cache, n: cache expires after `n` seconds).
|
||||
- `block-name.condition`, or `block-name.conditions[]`: include `block-name` only under specific condition(s). The condition can be
|
||||
- `is_front`: which is True only for the front page, usually at url "/"
|
||||
- `path:foo/bar`, `path:foo/*/bar`: True only for CMS location matching the patterns after "path:"
|
||||
- `<none>`: related block is disabled.
|
||||
- *note: There can be multiple conditions processed as any of the conditions (i.e: "or").*
|
||||
- `block-name.options[varname]: pass a table of options `varname => value` to the related block. This can be used to pass parameters for block builder (for instance, recent_changes modules accept parameters "size" to know how many changes should be included.)
|
||||
- To be able to include a block content into multiple region, it is possible to use aliases feature. For instance `&aliases[new_block]=block-name`, in this case, a `new_block` is declared, and it has same content as `block-name`, on this alias, the parameters `region, condition(s), title, weight` are supported, but not `options[]`.
|
||||
- **admin**: various admin related settings such as
|
||||
- `installation_access` which accepts 3 values: "all", "none" or "permission", to precise who has access to the modules installation page; either "all" for anyone, "none" to disable installation of new modules, or "permission" to use the CMS permissions solution to determine if the current user can install a new module.
|
||||
|
||||
Then, the configuration `cms.ini` can also define other parameters, and sections, that may be used by specific modules.
|
||||
Note it is also possible to include another ini file with instruction `@include=path-to-file.ini`.
|
||||
|
||||
Check the `example/demo/site/cms.ini` for example.
|
||||
|
||||
### User management
|
||||
The CMS core includes the notion of user, via interface `CMS_USER`, which has an id, a name, a password, ... and profile. Without any module, the CMS does not include any mean to authenticate, but still the CMS has the support for user management, and permissions system for current user. To be able to sign into the CMS, the site should include the module `auth`, and one or many of:
|
||||
- `basic_auth`: authentication using the HTTP Authorization header.
|
||||
- `oauth20`: being able to sign with an OAuth2.0 account (such as Google, Facebook, ...)
|
||||
- `OpenID`: being able to sign with an OpenID account.
|
||||
|
||||
Whatever authentication solution is used, when a user is signed-in, there is an instance of `CMS_USER` representing the associated CMS user account.
|
||||
|
||||
There is a predefined user `admin` who is the administrator of the CMS, and by definition, this **admin** has all the permissions. It is initialized by default with username `admin` and password `istrator#`.
|
||||
|
||||
The access control is role-based permissions system. This means, a user can have one or many *roles*, and each *role* includes a list of *permissions*.
|
||||
There are two built-in roles:
|
||||
- **anonymous**: when no user is signed in (typically anonymous visitors).
|
||||
- **authenticated**: when a user is signed in the CMS.
|
||||
With those 2 built-ins roles, and any custom role the admin will create, it is possible to give specific permissions, to a group of users.
|
||||
The CMS core defines a few permissions, and each module can also define their own permissions, for instance: "view any page", "create page", "edit page", "delete page", "clear cache", "install modules", ... (when the administrator is signed-in, go to url `/admin/role/1/edit` to see all the available permissions).
|
||||
|
||||
### Modules
|
||||
A module is the way to extend the CMS engine.
|
||||
First via the inherited `CMS_MODULE` interface that enables a module to:
|
||||
- have a custom `install` and `uninstall` procedure by redefining the related routines.
|
||||
- add its **routes** via `setup_router`. (i.e associated url or template of url with a specific request handler).
|
||||
- register itself to hooks via `register_hooks`.
|
||||
- declare new permissions by redefining `permissions`.
|
||||
- provide a specific module api by redefining `module_api`.
|
||||
- add its **filters** by redefining `filters`.
|
||||
|
||||
Using the `hooks` system, a module can be deeply integrated with the CMS engine, and even alter behaviors (for instance, add link, add css, javascript, ...). See related developer documentation on hooks.
|
||||
|
||||
It is simple to create your own modules (check the developer documentation).
|
||||
The ROC CMS library provides a few modules for now, for instance: basic_auth, oauth20, openid, node, blog, feed_aggregator, recent_changes, google_search, ... and others (the list keeps growing...).
|
||||
|
||||
**Reminder**: to include a module to your CMS site, you need to
|
||||
- include the associated .ecf file in your CMS site .ecf file.
|
||||
- and also declare them in your descendant of CMS_EXECUTION.
|
||||
- copy the eventual resources, configuration, ... files in the corresponding `site`.
|
||||
Note: a tool **roc** is under development to ease such operations, for now it only copies needed files from module to site location. In the future, it should also update .ecf files, associated CMS_EXECUTION effective class.
|
||||
|
||||
### Themes
|
||||
When talking about CMS, a major topic is how a request is rendered in a web browser. Here comes the notion of **theme** which is a collection of templates, accepting various values as input (including the content of the blocks), and renders as an html5 page. It also includes various assets such as css, javascript, icons, images, ...
|
||||
The ROC CMS theming is inspired by Drupal, with the notion of **region** and **block**.
|
||||
|
||||
Note: for now, there is no simple "theme" module or similar, and the common way to start your CMS site is to copy an existing project such as the one available with the demo example (i.e: copying the source code, but also the `site` folder).
|
||||
|
||||
Currently the default theme of the demo example `SMARTY_CMS_THEME` is based on Eiffel **smarty** template library (Check [smarty doc](https://svn.eiffel.com/eiffelstudio/trunk/Src/contrib/library/text/template/smarty/README.md) for syntax and functionalities).
|
||||
|
||||
The layout of a CMS web page has predefined area called **regions**. The Eiffel CMS uses the same default regions as Drupal, so let's see them in the following image.
|
||||
|
||||
```
|
||||
+----------------------------------------------------------+
|
||||
| Page_top |
|
||||
+----------------------------------------------------------+
|
||||
| Header |
|
||||
+---------------+-------------------------+----------------+
|
||||
| | Highlighted | |
|
||||
| Sidebar_first +-------------------------+ Sidebar_second |
|
||||
| | Help | |
|
||||
| +-------------------------+ |
|
||||
| | | |
|
||||
| | Content | |
|
||||
| | | |
|
||||
+---------------+-------------------------+----------------+
|
||||
| Footer |
|
||||
+----------------------------------------------------------+
|
||||
| Page_bottom |
|
||||
+----------------------------------------------------------+
|
||||
```
|
||||
|
||||
The regions available for a theme, are defined in a configuration file `theme.info` located in the theme directory. For example:
|
||||
```
|
||||
name=default_theme
|
||||
engine=smarty
|
||||
version=0.1
|
||||
regions[page_top] = Top
|
||||
regions[header] = Header
|
||||
regions[content] = Content
|
||||
regions[highlighted] = Highlighted
|
||||
regions[help] = Help
|
||||
regions[footer] = Footer
|
||||
regions[sidebar_first] = first sidebar
|
||||
regions[sidebar_second] = second sidebar
|
||||
regions[page_bottom] = Bottom
|
||||
```
|
||||
Note: the value for each region is the human readable region name.
|
||||
|
||||
Note the regions may be disposed with other layout (two sidebars on the left, or right, ... and so on), responsive design or not, and so on. But on the CMS side, a *block* can be inserted into a *region*, and depending if the region is included in the theme, the related block content will be displayed or not.
|
||||
To sort *block* inside a region, the CMS is using the `weight` property (that can be set via code, and/or overridden via configuration, i.e: `cms.ini`).
|
||||
This is how a site can support many themes, using the region as content holders, and theme for the layout and style.
|
||||
|
||||
Internally the block contents are stored in the values associated with each region.
|
||||
The theme also has access to specific `values` such as
|
||||
- `site_url`: the absolute url of the CMS website.
|
||||
- `host`: the host name.
|
||||
- `is_https`: True if the connection is using https://
|
||||
- `user`: contains the username of the signed user, if any.
|
||||
- `site_title`: site title.
|
||||
- `page_title`: per page title.
|
||||
- and also `page: CMS_HTML_PAGE` which represents the CMS page to render with the theme.
|
||||
- `page` provides values via expression, such as `$page.type`, `$page.is_front`, `$page.is_https`, `$page.title`, ...
|
||||
- and also a smart expression for region via `$page.region_xyz` for region `xyz` if any, ... (note the region are also available with expression like `$region_xyz` or `$page.region_xyz` ...)
|
||||
|
||||
==Note for developers: internally, the deferred class `CMS_RESPONSE` provides an abstraction to render the response for the request using the **theme**, in fact, the theme is controlled by the CMS_RESPONSE implementation (to set value, build expected theme, and finally render as html).==
|
||||
|
||||
### Blocks
|
||||
As previously said, a region holds smaller piece of content called blocks.
|
||||
Blocks hold chunks of content, like the user login form, navigation menu, information for the footer, or anything provided by each module.
|
||||
For instance the `feed_aggregator` module provides a block to display the latest elements of a aggregated feed.
|
||||
|
||||
Currently there are different kind of `CMS_BLOCK`:
|
||||
- `CMS_CONTENT_BLOCK`: it holds a simple text to render as it is on the page.
|
||||
- `CMS_MENU_BLOCK`: it holds a `CMS_MENU` as a collection of `CMS_LINK` generally used to hold a menu, or set of links such as navigation or management menus.
|
||||
- `CMS_SMARTY_TEMPLATE_BLOCK`: it holds a simple text to render as it is in the page.
|
||||
|
||||
Internally, there are two other kinds of block:
|
||||
- `CMS_ALIAS_BLOCK`: being the alias of another block, but with specific properties.
|
||||
- `CMS_CACHE_BLOCK`: there is a simple cache solution for blocks, based on expiration. See the configuration section to know how to define the expiration for a block.
|
||||
|
||||
For now, creating a block is only possible via block, an evolution of ROC CMS should allow the administrator to add new block without coding.
|
||||
|
||||
### Persistence
|
||||
The persistence or storage layer is used by the CMS to store custom values, path aliases, logs, emails, user information, but it is also used by module (unless a module wants to use its own persistence solution, disk, cloud, ...).
|
||||
|
||||
Currently, there are only SQL based implementations of that `CMS_STORAGE`, but nothing prevents to implement it with other solutions (plain text file, NoSQL db, ...).
|
||||
The current implementation are using either:
|
||||
- EiffelStore + MySQL: recommended for production, however Eiffel MySQL requires to configure your environment by setting, for instance MYSQL variable on Windows, and MYSQLINC on Linux.
|
||||
- EiffelStore + ODBC: via ODBC, there is a large range of available database (MySQL, SQLite, SQLserver, ...), but it requires to set up your environment (for instance install sqliteODBC driver to use SQLite database).
|
||||
- Eiffel sqlite3 wrapper: it is very convenient for development, but maybe not recommended for production websites. It does not require any environment setup, so this is a simple solution to build tests for instance.
|
||||
|
||||
In practice, how to use a storage or another?
|
||||
The project needs to include the expected storage, the following instructions explains how to include sqlite3, EiffelStore+ODBC and EiffelStore+MYSQL storage.
|
||||
1. First the associated .ecf file need to be included in your project file (.ecf)
|
||||
For instance
|
||||
```xml
|
||||
<library name="persistence_sqlite3" location="$ISE_LIBRARY\unstable\library\web\cms\library\persistence\sqlite3\sqlite3-safe.ecf"/>
|
||||
<library name="persistence_store_odbc" location="$ISE_LIBRARY\unstable\library\web\cms\library\persistence\store_odbc\store_odbc-safe.ecf"/>
|
||||
<library name="persistence_store_mysql" location="$ISE_LIBRARY\unstable\library\web\cms\library\persistence\store_mysql\store_mysql-safe.ecf"/>
|
||||
```
|
||||
2. Then in the descendant of `CMS_EXECUTION`, in the demo `DEMO_CMS_EXECUTION`, see the code of `setup_storage`:
|
||||
```eiffel
|
||||
setup_storage (a_setup: CMS_SETUP)
|
||||
do
|
||||
a_setup.storage_drivers.force (create {CMS_STORAGE_SQLITE3_BUILDER}.make, "sqlite3")
|
||||
a_setup.storage_drivers.force (create {CMS_STORAGE_STORE_MYSQL_BUILDER}.make, "mysql")
|
||||
a_setup.storage_drivers.force (create {CMS_STORAGE_STORE_ODBC_BUILDER}.make, "odbc")
|
||||
end
|
||||
```
|
||||
|
||||
3. And finally, in the configuration file `site/config/demo.json` (in fact, the executable name + ".json"), define the driver and environment of the datasource. For instance the following code defines **sqlite3** as default CMS storage, and environment *sqlite3* that defines the path of SQLite database as "site/database.sqlite3". Note the way to declare sqlite with ODBC, mysql with ODBC, or mysql directly with EiffelStore.
|
||||
```json
|
||||
{
|
||||
"database": {
|
||||
"datasource": {
|
||||
"driver": "sqlite3",
|
||||
"environment": "sqlite3",
|
||||
},
|
||||
"environments": {
|
||||
"sqlite3": {
|
||||
"connection_string":"Database=./site/database.sqlite3;"
|
||||
},
|
||||
"odbc-sqlite": {
|
||||
"connection_string":"Driver=SQLite3 ODBC Driver;Database=./site/database.sqlite;LongNames=0;Timeout=1000;NoTXN=0;SyncPragma=NORMAL;StepAPI=0;"
|
||||
},
|
||||
"odbc-mysql": {
|
||||
"connection_string":"Driver=mysql ODBC Driver;Server=localhost;Port=3306;Database=roc;Uid=roc;Pwd=roc;"
|
||||
},
|
||||
"mysql": {
|
||||
"connection_string":"Driver=mysql;Server=localhost;Port=3306;Database=roc;Uid=roc;Pwd=roc;"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
To use EiffelStore+MySQL, just change the "driver" to be "mysql" and "environment" to "mysql". The connection string for server database defines the credentials with "Uid" and "Pwd".
|
||||
|
||||
### How to run the CMS site?
|
||||
As any Eiffel Web application (EWF), it can be executed as
|
||||
- **standalone**: using Eiffel standalone httpd server included in the "standalone" connector, and then no setup is needed.
|
||||
- **CGI** or **libFCGI** server: using, for instance, Apache2. Please refer to the Eiffel Web Framework documentation.
|
||||
|
||||
### Conclusion
|
||||
At this point, you know enough to build and administrate a ROC CMS site.
|
||||
However, for a real site, it is likely that you will need to build your own modules, you will learn how doing that in the Developer Documentation.
|
||||
|
||||
***
|
||||
## Developper Documentation
|
||||
|
||||
This diagram shows the main interfaces, they will be described in this documentation, but for now, it introduces those class names.
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
### CMS APIs
|
||||
An instance of CMS_API is available either via argument, or via attribute / function of various CMS components.
|
||||
It provides routine specific to the ROC CMS engine (access to setup, modules, logs, custom values, ...).
|
||||
|
||||
### CMS Hooks
|
||||
Hooks is a mechanism which provides a way for modules to interact with each other and extending blocks of the current CMS.
|
||||
|
||||
- [CMS_HOOK](../library/src/hooks/cms_hook.e): deferred class CMS_HOOK is a marker interface for CMS Hook
|
||||
- [CMS_HOOK_AUTO_REGISTER](../library/src/hooks/cms_hook_auto_register.e): when inheriting from this deferred class, the declared hooks are automatically registered (note only the CMS core hooks are supported, as opposed to hook a module may propose). Otherwise, each descendant has to register itself to the associated hook manager.
|
||||
- [CMS_HOOK_BLOCK](../library/src/hooks/cms_hook_block.e): it provides a way to declare and build blocks.
|
||||
- [CMS_HOOK_FORM_ALTER](../library/src/hooks/cms_hook_form_alter.e): it provides a way to alter a web form `CMS_FORM`.
|
||||
- [CMS_HOOK_MENU_ALTER](../library/src/hooks/cms_hook_menu_alter.e): it provides a way to alter a menu, and thus add or remove a link. This is how a module can add a link into a specific `CMS_MENU`.
|
||||
- [CMS_HOOK_MENU_SYSTEM_ALTER](../library/src/hooks/cms_hook_menu_system_alter.e): similar to CMS_HOOK_MENU_ALTER, but on built-in menu, such as management, navigation menus, and other.
|
||||
- [CMS_HOOK_VALUE_TABLE_ALTER](../library/src/hooks/cms_hook_value_table_alter.e): it provides a way to alter the values table for a response (i.e: inserting custom values, or even override existing values).
|
||||
- [CMS_HOOK_EXPORT](../library/src/hooks/cms_hook_export.e): it provides a simple export solution for each module. Typically used to archive data associated with a module, for instance for backup purpose. In the future, a `CMS_HOOK_IMPORT` should also be available, and it would allow importing data exported by `CMS_HOOK_EXPORT`.
|
||||
- and for more hooks ... please check descendants of `CMS_HOOK`.
|
||||
|
||||
### Custom Module
|
||||
How to build a new module?
|
||||
A module is usually developed as an Eiffel library, and provide one or many implementations of `CMS_MODULE`.
|
||||
It has to set or implement:
|
||||
- **name**: a unique name identifying the module
|
||||
- **description**: a human text to describe the purpose of the module, it will mainly be used by the administration front-end.
|
||||
- **package**: put the current module into a package, mainly for admin front-end.
|
||||
- **version**: version information
|
||||
- **dependencies**: defines dependencies on other modules.
|
||||
- **permissions**: defines permissions used by the modules (mainly for admin front-end)
|
||||
- **setup_router**: associate routes with request handlers (declare various url or url template and associated request handler).
|
||||
- **filters**: similar to routers setup, but for WSF Filters (See EWF documentation for more details).
|
||||
- **register_hooks**: register current module with various hooks if needed.
|
||||
|
||||
A module can also redefine `install` and `uninstall`. This could be used during installation to create new database tables, or anything needed by the module, or clean similar resources when being uninstalled.
|
||||
|
||||
In addition, a module can also implement `module_api: detachable CMS_MODULE_API` in order to be integrated easily with other modules (see for instance the CMS_NODE_API defined in **node** module).
|
||||
|
||||
Please have a look at the [tutorial](tutorial.md) page.
|
||||
|
||||
## References
|
||||
For the interface references, please have a look at the [ROC CMS source code](https://github.com/EiffelWebFramework/ROC).
|
||||
|
||||
***
|
||||
*(last modified: Nov/17/2015 by Jocelyn.)*
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="demo" uuid="3643E657-BCBE-46AA-931B-71EAEA877A18" library_target="demo">
|
||||
<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">
|
||||
<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>
|
||||
@@ -12,45 +13,50 @@
|
||||
</option>
|
||||
<setting name="concurrency" value="thread"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="cms" location="..\..\cms-safe.ecf" readonly="false"/>
|
||||
<library name="cms" location="..\..\cms-safe.ecf" readonly="false">
|
||||
<option>
|
||||
<assertions precondition="true" postcondition="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
</library>
|
||||
<library name="cms_admin_module" location="..\..\modules\admin\admin-safe.ecf" readonly="false"/>
|
||||
<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_basic_auth_module" location="..\..\modules\basic_auth\basic_auth-safe.ecf" readonly="false"/>
|
||||
<library name="cms_blog_module" location="..\..\modules\blog\cms_blog_module-safe.ecf" readonly="false"/>
|
||||
<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_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_admin_module" location="..\..\modules\admin\admin-safe.ecf" readonly="false"/>
|
||||
<library name="cms_recent_changes_module" location="..\..\modules\recent_changes\recent_changes-safe.ecf" readonly="false"/>
|
||||
<library name="persistence_store_odbc" location="..\..\library\persistence\store_odbc\store_odbc-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" readonly="false"/>
|
||||
<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>
|
||||
<target name="demo_any" extends="common">
|
||||
<root class="EWF_ROC_SERVER" feature="make_and_launch"/>
|
||||
<setting name="concurrency" value="thread"/>
|
||||
<library name="cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\cgi-safe.ecf"/>
|
||||
<library name="libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\libfcgi-safe.ecf"/>
|
||||
<library name="nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\nino-safe.ecf"/>
|
||||
<library name="standalone" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\standalone-safe.ecf"/>
|
||||
<cluster name="launcher" location=".\launcher\any\" recursive="true"/>
|
||||
<library name="any_launcher" location="..\..\launcher\any-safe.ecf" readonly="false"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
<target name="demo_standalone" extends="common">
|
||||
<root class="EWF_ROC_SERVER" feature="make_and_launch"/>
|
||||
<option debug="true">
|
||||
<debug name="dbglog" enabled="true"/>
|
||||
</option>
|
||||
<setting name="concurrency" value="thread"/>
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
<variable name="httpd_ssl_disabled" value="true"/>
|
||||
<library name="default_standalone" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\standalone-safe.ecf"/>
|
||||
<cluster name="launcher" location=".\launcher\default\" recursive="true"/>
|
||||
<library name="standalone_launcher" location="..\..\launcher\standalone-safe.ecf" readonly="false"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
<target name="demo_standalone_none" extends="demo_standalone">
|
||||
@@ -62,25 +68,14 @@
|
||||
<target name="demo_standalone_scoop" extends="demo_standalone">
|
||||
<setting name="concurrency" value="scoop"/>
|
||||
</target>
|
||||
<target name="demo_nino" extends="common">
|
||||
<root class="EWF_ROC_SERVER" feature="make_and_launch"/>
|
||||
<setting name="concurrency" value="none"/>
|
||||
<library name="default_nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\nino-safe.ecf"/>
|
||||
<cluster name="launcher" location=".\launcher\default\" recursive="true"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
<target name="demo_cgi" extends="common">
|
||||
<root class="EWF_ROC_SERVER" feature="make_and_launch"/>
|
||||
<setting name="concurrency" value="none"/>
|
||||
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
|
||||
<cluster name="launcher" location=".\launcher\default\" recursive="true"/>
|
||||
<library name="cgi_launcher" location="..\..\launcher\cgi-safe.ecf" readonly="false"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
<target name="demo_libfcgi" extends="common">
|
||||
<root class="EWF_ROC_SERVER" feature="make_and_launch"/>
|
||||
<setting name="concurrency" value="none"/>
|
||||
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
|
||||
<cluster name="launcher" location=".\launcher\default\" recursive="true"/>
|
||||
<library name="libfcgi_launcher" location="..\..\launcher\libfcgi-safe.ecf" readonly="false"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
<target name="demo" extends="demo_standalone">
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
setlocal
|
||||
set ROC_CMD=%~dp0..\..\tools\roc.exe
|
||||
set ROC_CMD=call %~dp0..\..\tools\roc.bat
|
||||
set ROC_CMS_DIR=%~dp0
|
||||
|
||||
%ROC_CMD% install --module ..\..\modules\admin --dir %ROC_CMS_DIR%
|
||||
%ROC_CMD% install --module ..\..\modules\auth --dir %ROC_CMS_DIR%
|
||||
%ROC_CMD% install --module ..\..\modules\basic_auth --dir %ROC_CMS_DIR%
|
||||
%ROC_CMD% install --module ..\..\modules\node --dir %ROC_CMS_DIR%
|
||||
%ROC_CMD% install --module ..\..\modules\blog --dir %ROC_CMS_DIR%
|
||||
%ROC_CMD% install --module ..\..\modules\node --dir %ROC_CMS_DIR%
|
||||
%ROC_CMD% install --module ..\..\modules\oauth20 --dir %ROC_CMS_DIR%
|
||||
%ROC_CMD% install --module ..\..\modules\openid --dir %ROC_CMS_DIR%
|
||||
%ROC_CMD% install --module ..\..\modules\recent_changes --dir %ROC_CMS_DIR%
|
||||
%ROC_CMD% install --module ..\..\modules\feed_aggregator --dir %ROC_CMS_DIR%
|
||||
%ROC_CMD% install --module ..\..\modules\google_search --dir %ROC_CMS_DIR%
|
||||
%ROC_CMD% install --module ..\..\modules\taxonomy --dir %ROC_CMS_DIR%
|
||||
|
||||
@@ -93,7 +93,7 @@ feature -- Hooks
|
||||
|
||||
block_list: ITERABLE [like {CMS_BLOCK}.name]
|
||||
do
|
||||
Result := <<"demo-info">>
|
||||
Result := <<"?demo-info">>
|
||||
end
|
||||
|
||||
get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
|
||||
@@ -103,8 +103,8 @@ feature -- Hooks
|
||||
m: CMS_MENU
|
||||
lnk: CMS_LOCAL_LINK
|
||||
do
|
||||
if a_block_id.is_case_insensitive_equal_general ("demo-info") then
|
||||
if a_response.request.request_uri.starts_with ("/demo/") then
|
||||
if a_block_id.same_string ("demo-info") then
|
||||
if a_response.location.starts_with_general ("demo/") then
|
||||
create m.make_with_title (a_block_id, "Demo", 2)
|
||||
create lnk.make ("demo: abc", "demo/abc")
|
||||
m.extend (lnk)
|
||||
|
||||
25
examples/demo/site/config/blocks.ini
Normal file
25
examples/demo/site/config/blocks.ini
Normal file
@@ -0,0 +1,25 @@
|
||||
### Blocks settings
|
||||
|
||||
#navigation.region=sidebar_first
|
||||
#navigation.condition=is_front
|
||||
management.conditions[]=path:admin*
|
||||
management.conditions[]=is_front
|
||||
|
||||
#Feeds
|
||||
feed.news.weight=3
|
||||
feed.news.region=feed_news
|
||||
feed.news.region=content
|
||||
feed.news.condition=is_front
|
||||
|
||||
feed.forum.weight=2
|
||||
feed.forum.region=feed_forum
|
||||
feed.forum.region=content
|
||||
feed.forum.condition=is_front
|
||||
feed.forum.options[size]=5
|
||||
|
||||
#Updates
|
||||
recent_changes.region=content
|
||||
recent_changes.condition=is_front
|
||||
recent_changes.title=Updates
|
||||
recent_changes.options[size]=4
|
||||
|
||||
@@ -9,9 +9,9 @@ email=your@email.com
|
||||
theme=bootstrap
|
||||
|
||||
[mailer]
|
||||
smtp=localhost:25
|
||||
#smtp=localhost:25
|
||||
#sendmail=/usr/bin/sendmail
|
||||
#output=@stderr
|
||||
output=@stderr
|
||||
|
||||
[modules]
|
||||
# Module status
|
||||
@@ -20,20 +20,11 @@ smtp=localhost:25
|
||||
# Default is "on"
|
||||
# for each module, this can be overwritten with
|
||||
# module_name= on or off
|
||||
*=off
|
||||
admin=on
|
||||
auth=on
|
||||
basic_auth=on
|
||||
blog=on
|
||||
debug=on
|
||||
demo=on
|
||||
node=on
|
||||
oauth20=on
|
||||
openid=on
|
||||
*=all
|
||||
|
||||
[blocks]
|
||||
#navigation.region=sidebar_first
|
||||
@include=blocks.ini
|
||||
|
||||
[admin]
|
||||
# CMS Installation, are accessible by "all", "none" or uppon "permission". (default is none)
|
||||
installation_access=permission
|
||||
installation_access=all
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
{
|
||||
"database": {
|
||||
"datasource": {
|
||||
"driver": "odbc",
|
||||
"environment": "odbc-sqlite"
|
||||
"driver": "sqlite3",
|
||||
"environment": "sqlite3",
|
||||
"-driver": "odbc",
|
||||
"-environment": "odbc-sqlite"
|
||||
},
|
||||
"environments": {
|
||||
"sqlite3": {
|
||||
"connection_string":"Database=./site/database.sqlite3;"
|
||||
},
|
||||
"odbc-sqlite": {
|
||||
"connection_string":"Driver=SQLite3 ODBC Driver;Database=./site/database.sqlite;LongNames=0;Timeout=1000;NoTXN=0;SyncPragma=NORMAL;StepAPI=0;"
|
||||
},
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>You have required a new password at <a href="...">ROC CMS</a></p>
|
||||
<p>You have required a new password at <a href="$host">ROC CMS</a></p>
|
||||
|
||||
<p>To complete your request, please click on this link to genereate a new password:<p>
|
||||
|
||||
<p><a href="$link">$link</a></p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>You have request a new activation token at<a href="...">ROC CMS</a></p>
|
||||
<p>You have request a new activation token at <a href="$host">ROC CMS</a></p>
|
||||
|
||||
<p>To complete your registration, please click on this link to activate your account:<p>
|
||||
|
||||
<p><a href="$link">$link</a></p>
|
||||
<p>Thank you for joining us.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<meta name="author" content="ROC CMS">
|
||||
</head>
|
||||
<body>
|
||||
<p>Welcome to<a href="...">ROC CMS</a></p>
|
||||
<p>Welcome to<a href="$host">ROC CMS</a></p>
|
||||
<p>Thank you for joining us.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -25,5 +25,5 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/unless}
|
||||
</div>
|
||||
{/unless}
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@ ROC_AUTH.login = function() {
|
||||
var newdiv = document.createElement('div');
|
||||
newdiv.innerHTML = "<br>Invalid Credentials</br>";
|
||||
newdiv.id = 'myModalFormId';
|
||||
$("body").append(newdiv);
|
||||
$(".primary-tabs").append(newdiv);
|
||||
}
|
||||
}else{
|
||||
|
||||
@@ -49,7 +49,7 @@ ROC_AUTH.login = function() {
|
||||
var newdiv = document.createElement('div');
|
||||
newdiv.innerHTML = "<br>Invalid Credentials</br>";
|
||||
newdiv.id = 'myModalFormId';
|
||||
$("body").append(newdiv);
|
||||
$(".primary-tabs").append(newdiv);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -93,7 +93,7 @@ ROC_AUTH.login_with_redirect = function() {
|
||||
var newdiv = document.createElement('div');
|
||||
newdiv.innerHTML = "<br>Invalid Credentials</br>";
|
||||
newdiv.id = 'myModalFormId';
|
||||
$("body").append(newdiv);
|
||||
$(".primary-tabs").append(newdiv);
|
||||
$("#imgProgressRedirect").hide();
|
||||
}
|
||||
}else{
|
||||
@@ -122,8 +122,8 @@ ROC_AUTH.login_with_redirect = function() {
|
||||
var newdiv = document.createElement('div');
|
||||
newdiv.innerHTML = "<br>Invalid Credentials</br>";
|
||||
newdiv.id = 'myModalFormId';
|
||||
$("body").append(newdiv);
|
||||
$("#imgProgressRedirect").hide();
|
||||
$(".primary-tabs").append(newdiv);
|
||||
$("#imgProgressRedirect").hide();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<div>
|
||||
<form name="cms_basic_auth" action method="POST">
|
||||
<div>
|
||||
<input type="text" name="username" required>
|
||||
<input type="text" name="username" id="username" required>
|
||||
<label>Username</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="password" name="password" required>
|
||||
<input type="password" name="password" id="password" required>
|
||||
<label>Password</label>
|
||||
</div>
|
||||
|
||||
@@ -25,10 +25,5 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{foreach item="item" from="$oauth_consumers"}
|
||||
<a href="{$site_url/}account/login-with-oauth/{$item/}">Login with {$item/}</a><br>
|
||||
{/foreach}
|
||||
</div>
|
||||
{/unless}
|
||||
</div>
|
||||
|
||||
28
examples/demo/site/modules/feed_aggregator/config/feeds.json
Normal file
28
examples/demo/site/modules/feed_aggregator/config/feeds.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"ids": ["news", "forum"],
|
||||
"feeds": {
|
||||
"news": {
|
||||
"title": "Eiffel related posts",
|
||||
"expiration": "21600",
|
||||
"size": 5,
|
||||
"locations": [
|
||||
"https://bertrandmeyer.com/feed/",
|
||||
"https://room.eiffel.com/blog/feed",
|
||||
"https://room.eiffel.com/article/feed",
|
||||
"https://room.eiffel.com/library/feed"
|
||||
]
|
||||
, "categories": ["Eiffel"]
|
||||
,"option_description": "enabled"
|
||||
},
|
||||
"forum": {
|
||||
"title": "Eiffel Forum",
|
||||
"expiration": "21600",
|
||||
"size": 5,
|
||||
"locations": [
|
||||
"https://groups.google.com/forum/feed/eiffel-users/msgs/atom.xml?num=15",
|
||||
"http://stackoverflow.com/feeds/tag?tagnames=eiffel&sort=newest"
|
||||
]
|
||||
,"option_description": "enabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
div.feed ul {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 99%;
|
||||
}
|
||||
div.feed li {
|
||||
/* border-top: solid 1px #ddd; */
|
||||
padding: 0;
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
div.feed li a {
|
||||
font-weight: bold;
|
||||
}
|
||||
div.feed li .date {
|
||||
font-weight: bold;
|
||||
font-size: small;
|
||||
}
|
||||
div.feed li .category {
|
||||
margin-left: 20px;
|
||||
font-size: 8px;
|
||||
height: 9px;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
}
|
||||
div.feed li .description {
|
||||
margin-left: 20px;
|
||||
font-size: small;
|
||||
height: 18px;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
}
|
||||
div.feed li:hover {
|
||||
margin-bottom: 23px;
|
||||
}
|
||||
div.feed li:hover .description {
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
height: auto;
|
||||
overflow-y: scroll;
|
||||
overflow-x: scroll;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
border: solid 1px #000;
|
||||
z-index: 10;
|
||||
}
|
||||
div.feed li:hover:last-child {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
div.feed li .description::after {
|
||||
content: "...";
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
div.feed {
|
||||
ul {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 99%;
|
||||
}
|
||||
li {
|
||||
/* border-top: solid 1px #ddd; */
|
||||
padding: 0;
|
||||
margin: 0 0 5px 0;
|
||||
|
||||
a {
|
||||
font-weight: bold;
|
||||
}
|
||||
.date {
|
||||
font-weight: bold;
|
||||
font-size: small;
|
||||
}
|
||||
.category {
|
||||
margin-left: 20px;
|
||||
font-size: 8px;
|
||||
height: 9px;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
}
|
||||
.description {
|
||||
margin-left: 20px;
|
||||
font-size: small;
|
||||
height: 18px;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
}
|
||||
&:hover {
|
||||
margin-bottom: 23px;
|
||||
.description {
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
height: auto;
|
||||
overflow-y: scroll;
|
||||
overflow-x: scroll;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
border: solid 1px #000;
|
||||
z-index: 10;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
}
|
||||
.description::after { content: "..."; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"gcse": {
|
||||
"cx":"",
|
||||
"secret_key":""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<section>
|
||||
<header>
|
||||
<h2>Results for <kbd>{$result.current_page.search_terms/}</kbd></h2>
|
||||
</header>
|
||||
|
||||
<!-- list of results -->
|
||||
<ol start="{$result.current_page.start_index/}">
|
||||
|
||||
<!-- Item result -->
|
||||
{foreach from="$result.items" item="item"}
|
||||
<li>
|
||||
<article>
|
||||
<header>
|
||||
<h3>
|
||||
<cite>
|
||||
<a href="{$item.link/}">{$item.title/}</a>
|
||||
</cite>
|
||||
</h3>
|
||||
</header>
|
||||
<blockquote cite="{$item.link/}">
|
||||
<p>{$item.html_snippet/}</p>
|
||||
<footer>
|
||||
<p><abbr title="Uniform Resource Locator">Source</abbr> <a href="{$item.link/}">{$item.display_link/}</a></p>
|
||||
</footer>
|
||||
</blockquote>
|
||||
</article>
|
||||
</li>
|
||||
{/foreach}
|
||||
|
||||
|
||||
</ol>
|
||||
<ul class="cms-page-links">
|
||||
{if isset="$result.previous_page"}
|
||||
<li><a href="{$site_url/}gcse/?q={$result.previous_page.search_terms/}&start={$result.previous_page.start_index/}&num={$result.previous_page.count/}">Previous</a></li>
|
||||
{/if}
|
||||
{if isset="$result.next_page"}
|
||||
<li><a href="{$site_url/}gcse/?q={$result.next_page.search_terms/}&start={$result.next_page.start_index/}&num={$result.next_page.count/}">Next</a></li>
|
||||
{/if}
|
||||
</ul>
|
||||
</section>
|
||||
21
examples/demo/site/modules/taxonomy/files/css/taxonomy.css
Normal file
21
examples/demo/site/modules/taxonomy/files/css/taxonomy.css
Normal file
@@ -0,0 +1,21 @@
|
||||
ul.taxonomy {
|
||||
font-size: 80%;
|
||||
list-style-type: none;
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
}
|
||||
ul.taxonomy li {
|
||||
padding: 2px;
|
||||
margin-right: 3px;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
}
|
||||
ul.taxonomy li a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
ul.taxonomy li:hover {
|
||||
padding: 1px;
|
||||
border-top: solid 1px #66f;
|
||||
border-bottom: solid 1px #66f;
|
||||
background-color: #ddf;
|
||||
}
|
||||
21
examples/demo/site/modules/taxonomy/files/scss/taxonomy.scss
Normal file
21
examples/demo/site/modules/taxonomy/files/scss/taxonomy.scss
Normal file
@@ -0,0 +1,21 @@
|
||||
ul.taxonomy {
|
||||
font-size: 80%;
|
||||
list-style-type: none;
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
li {
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
padding: 2px;
|
||||
margin-right: 3px;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
&:hover {
|
||||
padding: 1px;
|
||||
border-top: solid 1px #66f;
|
||||
border-bottom: solid 1px #66f;
|
||||
background-color: #ddf;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
examples/demo/site/modules/taxonomy/scripts/install.sql
Normal file
24
examples/demo/site/modules/taxonomy/scripts/install.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
CREATE TABLE taxonomy_term (
|
||||
`tid` INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT UNIQUE,
|
||||
`text` VARCHAR(255) NOT NULL,
|
||||
`weight` INTEGER,
|
||||
`description` TEXT,
|
||||
`langcode` VARCHAR(12)
|
||||
);
|
||||
|
||||
CREATE TABLE taxonomy_hierarchy (
|
||||
`tid` INTEGER NOT NULL,
|
||||
`parent` INTEGER,
|
||||
CONSTRAINT PK_tid_parent PRIMARY KEY (tid,parent)
|
||||
);
|
||||
|
||||
/* Associate tid with unique (type,entity)
|
||||
* for instance: "page" + "$nid" -> "tid"
|
||||
*/
|
||||
CREATE TABLE taxonomy_index (
|
||||
`tid` INTEGER NOT NULL,
|
||||
`entity` VARCHAR(255),
|
||||
`type` VARCHAR(255) NOT NULL,
|
||||
CONSTRAINT PK_tid_entity_type PRIMARY KEY (tid,entity,type)
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
DROP TABLE IF EXISTS taxonomy_term;
|
||||
DROP TABLE IF EXISTS taxonomy_hierarchy;
|
||||
DROP TABLE IF EXISTS taxonomy_index;
|
||||
@@ -0,0 +1,8 @@
|
||||
$(document).ready(function() {
|
||||
$('#gcse_search_form').submit(function() {
|
||||
window.open('', 'formpopup', 'width=600,height=600,resizeable,scrollbars');
|
||||
this.target = 'formpopup';
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<!-- EWF CMS -->
|
||||
<link rel="stylesheet" href="{$site_url/}theme/css/style.css">
|
||||
|
||||
|
||||
<!-- jQuery dep -->
|
||||
<script src="{$site_url/}theme/js/jquery-1.10.2.min.js"></script>
|
||||
<script src="{$site_url/}theme/js/popup_search.js"></script>
|
||||
|
||||
{if isset="$head"}{$head/}{/if}
|
||||
{if isset="$styles"}{$styles/}{/if}
|
||||
@@ -36,7 +37,17 @@
|
||||
{$page.primary_nav/}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Page search -->
|
||||
<div class="row">
|
||||
<div class="col-md-2 col-md-offset-9">
|
||||
<form action="{$site_url/}gcse" class="search-form" id="gcse_search_form">
|
||||
<div class="form-group has-feedback">
|
||||
<input type="search" class="form-control" name="q" id="gcse_search" placeholder="search">
|
||||
<span class="glyphicon glyphicon-search form-control-feedback"></span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- General Page Content -->
|
||||
<div id='content' class='row-fluid'>
|
||||
<!-- Left Sidebar sidebar_first -->
|
||||
@@ -62,9 +73,15 @@
|
||||
<!-- Main Content Section -->
|
||||
{unless isempty="$page_title"}<h1 class="page-title">{$page_title/}</h1>{/unless}
|
||||
{$page.region_content/}
|
||||
{if condition="$page.is_front"}
|
||||
{if isset="$page.region_feed_news"}
|
||||
<div class="column" style="width: 45%; float: left">{$page.region_feed_news/}</div>
|
||||
{/if}
|
||||
{if isset="$page.region_feed_forum"}
|
||||
<div class="column" style="width: 45%; float: left">{$page.region_feed_forum/}</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,31 +1,21 @@
|
||||
note
|
||||
description: "Summary description for {EWF_ROC_SERVER_EXECUTION}."
|
||||
description: "[
|
||||
CMS Execution for the demo server.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
EWF_ROC_SERVER_EXECUTION
|
||||
DEMO_CMS_EXECUTION
|
||||
|
||||
inherit
|
||||
CMS_EXECUTION
|
||||
redefine
|
||||
initialize
|
||||
end
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
SHARED_LOGGER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
initialize
|
||||
do
|
||||
Precursor
|
||||
end
|
||||
|
||||
initial_cms_setup: CMS_DEFAULT_SETUP
|
||||
-- CMS setup.
|
||||
local
|
||||
@@ -39,17 +29,17 @@ feature {NONE} -- Initialization
|
||||
create Result.make (l_env)
|
||||
end
|
||||
|
||||
feature -- CMS setup
|
||||
feature -- CMS storage
|
||||
|
||||
setup_storage (a_setup: CMS_SETUP)
|
||||
do
|
||||
debug ("refactor_fixme")
|
||||
to_implement ("To implement custom storage")
|
||||
end
|
||||
a_setup.storage_drivers.force (create {CMS_STORAGE_SQLITE3_BUILDER}.make, "sqlite3")
|
||||
-- a_setup.storage_drivers.force (create {CMS_STORAGE_STORE_MYSQL_BUILDER}.make, "mysql")
|
||||
a_setup.storage_drivers.force (create {CMS_STORAGE_STORE_ODBC_BUILDER}.make, "odbc")
|
||||
end
|
||||
|
||||
feature -- CMS modules
|
||||
|
||||
setup_modules (a_setup: CMS_SETUP)
|
||||
-- Setup additional modules.
|
||||
local
|
||||
@@ -76,12 +66,19 @@ feature -- CMS setup
|
||||
a_setup.register_module (m)
|
||||
|
||||
create {CMS_BLOG_MODULE} m.make
|
||||
a_setup.register_module (m)
|
||||
|
||||
-- Taxonomy
|
||||
create {CMS_TAXONOMY_MODULE} m.make
|
||||
a_setup.register_module (m)
|
||||
|
||||
-- Recent changes
|
||||
create {CMS_RECENT_CHANGES_MODULE} m.make
|
||||
a_setup.register_module (m)
|
||||
|
||||
-- Recent changes
|
||||
create {FEED_AGGREGATOR_MODULE} m.make
|
||||
a_setup.register_module (m)
|
||||
|
||||
-- Miscellanious
|
||||
create {CMS_DEBUG_MODULE} m.make
|
||||
@@ -89,6 +86,9 @@ feature -- CMS setup
|
||||
|
||||
create {CMS_DEMO_MODULE} m.make
|
||||
a_setup.register_module (m)
|
||||
|
||||
create {GOOGLE_CUSTOM_SEARCH_MODULE} m.make
|
||||
a_setup.register_module (m)
|
||||
end
|
||||
|
||||
end
|
||||
18
examples/demo/src/demo_cms_server.e
Normal file
18
examples/demo/src/demo_cms_server.e
Normal file
@@ -0,0 +1,18 @@
|
||||
note
|
||||
description: "[
|
||||
DEMO application server.
|
||||
]"
|
||||
date: "$Date: 2015-02-09 22:29:56 +0100 (lun., 09 févr. 2015) $"
|
||||
revision: "$Revision: 96596 $"
|
||||
|
||||
class
|
||||
DEMO_CMS_SERVER
|
||||
|
||||
inherit
|
||||
ROC_CMS_LAUNCHER [DEMO_CMS_EXECUTION]
|
||||
|
||||
create
|
||||
make_and_launch
|
||||
|
||||
end
|
||||
|
||||
10
launcher/README.txt
Normal file
10
launcher/README.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
Collection of ROC CMS launcher ready to use.
|
||||
|
||||
- Include any-safe.ecf to use any of cgi, libfcgi or standalone connector.
|
||||
- Include standalone-safe.ecf to use standalone connector.
|
||||
- Include libfcgi-safe.ecf to use libfcgi connector.
|
||||
- Include cgi-safe.ecf to use cgi connector.
|
||||
|
||||
In application, the root class need to inherit from ROC_CMS_LAUNCHER with adapted CMS_EXECUTION
|
||||
descendant.
|
||||
|
||||
18
launcher/any-safe.ecf
Normal file
18
launcher/any-safe.ecf
Normal file
@@ -0,0 +1,18 @@
|
||||
<?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="launcher" uuid="6FDCA393-AFC3-436B-A58A-870923967C86" library_target="launcher">
|
||||
<target name="common" abstract="true">
|
||||
<root all_classes="true"/>
|
||||
<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="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
|
||||
</target>
|
||||
<target name="launcher" extends="common">
|
||||
<library name="cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\cgi-safe.ecf"/>
|
||||
<library name="libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\libfcgi-safe.ecf"/>
|
||||
<library name="nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\nino-safe.ecf"/>
|
||||
<library name="standalone" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\standalone-safe.ecf"/>
|
||||
<cluster name="src" location=".\" recursive="false"/>
|
||||
<cluster name="launcher" location=".\any\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
19
launcher/any.ecf
Normal file
19
launcher/any.ecf
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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="launcher" uuid="6FDCA393-AFC3-436B-A58A-870923967C86" library_target="launcher">
|
||||
<target name="common" abstract="true">
|
||||
<root all_classes="true"/>
|
||||
<option void_safety="none" />
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="cms" location="..\cms.ecf"/>
|
||||
<library name="cms_app_env" location="..\library\app_env\app_env.ecf" readonly="false"/>
|
||||
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf.ecf"/>
|
||||
</target>
|
||||
<target name="launcher" extends="common">
|
||||
<library name="cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\cgi.ecf"/>
|
||||
<library name="libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\libfcgi.ecf"/>
|
||||
<library name="nino" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\nino.ecf"/>
|
||||
<library name="standalone" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\standalone.ecf"/>
|
||||
<cluster name="src" location=".\" recursive="false"/>
|
||||
<cluster name="launcher" location=".\any\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
19
launcher/any/application_launcher.e
Normal file
19
launcher/any/application_launcher.e
Normal file
@@ -0,0 +1,19 @@
|
||||
note
|
||||
description: "[
|
||||
Effective class for APPLICATION_LAUNCHER_I
|
||||
|
||||
You can put modification in this class
|
||||
]"
|
||||
date: "$Date: 2013-06-12 13:55:42 +0200 (mer., 12 juin 2013) $"
|
||||
revision: "$Revision: 36 $"
|
||||
|
||||
class
|
||||
APPLICATION_LAUNCHER [G -> WSF_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
APPLICATION_LAUNCHER_I [G]
|
||||
|
||||
feature -- Custom
|
||||
|
||||
end
|
||||
|
||||
127
launcher/any/application_launcher_i.e
Normal file
127
launcher/any/application_launcher_i.e
Normal file
@@ -0,0 +1,127 @@
|
||||
note
|
||||
description: "[
|
||||
Specific application launcher
|
||||
|
||||
DO NOT EDIT THIS CLASS
|
||||
|
||||
you can customize APPLICATION_LAUNCHER
|
||||
]"
|
||||
date: "$Date: 2013-06-12 13:55:42 +0200 (mer., 12 juin 2013) $"
|
||||
revision: "$Revision: 36 $"
|
||||
|
||||
deferred class
|
||||
APPLICATION_LAUNCHER_I [G -> WSF_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
SHARED_EXECUTION_ENVIRONMENT
|
||||
|
||||
feature -- Execution
|
||||
|
||||
launch (opts: detachable WSF_SERVICE_LAUNCHER_OPTIONS)
|
||||
local
|
||||
nature: like launcher_nature
|
||||
do
|
||||
nature := launcher_nature
|
||||
if nature = Void then
|
||||
launch_standalone (opts)
|
||||
elseif nature = nature_standalone then
|
||||
launch_standalone (opts)
|
||||
elseif nature = nature_nino then
|
||||
launch_nino (opts)
|
||||
elseif nature = nature_cgi then
|
||||
launch_cgi (opts)
|
||||
elseif nature = nature_libfcgi then
|
||||
launch_libfcgi (opts)
|
||||
else
|
||||
-- bye bye
|
||||
(create {EXCEPTIONS}).die (-1)
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Access
|
||||
|
||||
launcher_nature: detachable READABLE_STRING_8
|
||||
-- Initialize the launcher nature
|
||||
-- either cgi, libfcgi, or nino.
|
||||
--| We could extend with more connector if needed.
|
||||
--| and we could use WSF_DEFAULT_SERVICE_LAUNCHER to configure this at compilation time.
|
||||
local
|
||||
p: PATH
|
||||
ext: detachable READABLE_STRING_32
|
||||
do
|
||||
create p.make_from_string (execution_environment.arguments.command_name)
|
||||
if attached p.entry as l_entry then
|
||||
ext := l_entry.extension
|
||||
end
|
||||
if ext /= Void then
|
||||
if ext.same_string (nature_standalone) then
|
||||
Result := nature_standalone
|
||||
end
|
||||
if ext.same_string (nature_nino) then
|
||||
Result := nature_nino
|
||||
end
|
||||
if ext.same_string (nature_cgi) then
|
||||
Result := nature_cgi
|
||||
end
|
||||
if ext.same_string (nature_libfcgi) or else ext.same_string ("fcgi") then
|
||||
Result := nature_libfcgi
|
||||
end
|
||||
end
|
||||
Result := default_nature
|
||||
end
|
||||
|
||||
feature {NONE} -- standalone
|
||||
|
||||
nature_standalone: STRING = "standalone"
|
||||
|
||||
launch_standalone (opts: detachable WSF_SERVICE_LAUNCHER_OPTIONS)
|
||||
local
|
||||
launcher: WSF_STANDALONE_SERVICE_LAUNCHER [G]
|
||||
do
|
||||
create launcher.make_and_launch (opts)
|
||||
end
|
||||
|
||||
feature {NONE} -- nino
|
||||
|
||||
nature_nino: STRING = "nino"
|
||||
|
||||
launch_nino (opts: detachable WSF_SERVICE_LAUNCHER_OPTIONS)
|
||||
local
|
||||
launcher: WSF_NINO_SERVICE_LAUNCHER [G]
|
||||
do
|
||||
create launcher.make_and_launch (opts)
|
||||
end
|
||||
|
||||
feature {NONE} -- cgi
|
||||
|
||||
nature_cgi: STRING = "cgi"
|
||||
|
||||
launch_cgi (opts: detachable WSF_SERVICE_LAUNCHER_OPTIONS)
|
||||
local
|
||||
launcher: WSF_CGI_SERVICE_LAUNCHER [G]
|
||||
do
|
||||
create launcher.make_and_launch (opts)
|
||||
end
|
||||
|
||||
feature {NONE} -- libfcgi
|
||||
|
||||
nature_libfcgi: STRING = "libfcgi"
|
||||
|
||||
launch_libfcgi (opts: detachable WSF_SERVICE_LAUNCHER_OPTIONS)
|
||||
local
|
||||
launcher: WSF_LIBFCGI_SERVICE_LAUNCHER [G]
|
||||
do
|
||||
create launcher.make_and_launch (opts)
|
||||
end
|
||||
|
||||
feature -- Default
|
||||
|
||||
default_nature: STRING
|
||||
do
|
||||
Result := nature_standalone
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
15
launcher/cgi-safe.ecf
Normal file
15
launcher/cgi-safe.ecf
Normal file
@@ -0,0 +1,15 @@
|
||||
<?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="cgi_launcher" uuid="0FE4F1D0-BB70-4C7F-A66E-B27F1D718109" library_target="cgi_launcher">
|
||||
<target name="common" abstract="true">
|
||||
<root all_classes="true"/>
|
||||
<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="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
|
||||
</target>
|
||||
<target name="cgi_launcher" extends="common">
|
||||
<library name="default_cgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\cgi-safe.ecf"/>
|
||||
<cluster name="src" location=".\" recursive="false"/>
|
||||
<cluster name="launcher" location=".\default\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
19
launcher/default/application_launcher.e
Normal file
19
launcher/default/application_launcher.e
Normal file
@@ -0,0 +1,19 @@
|
||||
note
|
||||
description: "[
|
||||
Effective class for APPLICATION_LAUNCHER_I
|
||||
|
||||
You can put modification in this class
|
||||
]"
|
||||
date: "$Date: 2013-06-12 13:55:42 +0200 (mer., 12 juin 2013) $"
|
||||
revision: "$Revision: 36 $"
|
||||
|
||||
class
|
||||
APPLICATION_LAUNCHER [G -> WSF_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
APPLICATION_LAUNCHER_I [G]
|
||||
|
||||
feature -- Custom
|
||||
|
||||
end
|
||||
|
||||
26
launcher/default/application_launcher_i.e
Normal file
26
launcher/default/application_launcher_i.e
Normal file
@@ -0,0 +1,26 @@
|
||||
note
|
||||
description: "[
|
||||
Specific application launcher
|
||||
|
||||
DO NOT EDIT THIS CLASS
|
||||
|
||||
you can customize APPLICATION_LAUNCHER
|
||||
]"
|
||||
date: "$Date: 2015-02-09 22:29:56 +0100 (lun., 09 févr. 2015) $"
|
||||
revision: "$Revision: 96596 $"
|
||||
|
||||
deferred class
|
||||
APPLICATION_LAUNCHER_I [G -> WSF_EXECUTION create make end]
|
||||
|
||||
feature -- Execution
|
||||
|
||||
launch (opts: detachable WSF_SERVICE_LAUNCHER_OPTIONS)
|
||||
local
|
||||
launcher: WSF_DEFAULT_SERVICE_LAUNCHER [G]
|
||||
do
|
||||
create launcher.make_and_launch (opts)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
15
launcher/libfcgi-safe.ecf
Normal file
15
launcher/libfcgi-safe.ecf
Normal file
@@ -0,0 +1,15 @@
|
||||
<?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="libfcgi_launcher" uuid="04D7D1EA-059B-4024-B0DE-BBB57AB2D00C" library_target="libfcgi_launcher">
|
||||
<target name="common" abstract="true">
|
||||
<root all_classes="true"/>
|
||||
<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="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
|
||||
</target>
|
||||
<target name="libfcgi_launcher" extends="common">
|
||||
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi-safe.ecf"/>
|
||||
<cluster name="src" location=".\" recursive="false"/>
|
||||
<cluster name="launcher" location=".\default\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
16
launcher/libfcgi.ecf
Normal file
16
launcher/libfcgi.ecf
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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="libfcgi_launcher" uuid="04D7D1EA-059B-4024-B0DE-BBB57AB2D00C" library_target="libfcgi_launcher">
|
||||
<target name="common" abstract="true">
|
||||
<root all_classes="true"/>
|
||||
<option void_safety="none" />
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="cms" location="..\cms.ecf"/>
|
||||
<library name="cms_app_env" location="..\library\app_env\app_env.ecf" readonly="false"/>
|
||||
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf.ecf"/>
|
||||
</target>
|
||||
<target name="libfcgi_launcher" extends="common">
|
||||
<library name="default_libfcgi" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\libfcgi.ecf"/>
|
||||
<cluster name="src" location=".\" recursive="false"/>
|
||||
<cluster name="launcher" location=".\default\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
@@ -1,12 +1,12 @@
|
||||
note
|
||||
description: "[
|
||||
application service
|
||||
Reusable ROC CMS launcher.
|
||||
]"
|
||||
date: "$Date: 2015-02-09 22:29:56 +0100 (lun., 09 févr. 2015) $"
|
||||
revision: "$Revision: 96596 $"
|
||||
|
||||
class
|
||||
EWF_ROC_SERVER
|
||||
ROC_CMS_LAUNCHER [G -> CMS_EXECUTION create make end]
|
||||
|
||||
inherit
|
||||
WSF_LAUNCHABLE_SERVICE
|
||||
@@ -37,16 +37,27 @@ feature {NONE} -- Initialization
|
||||
-- Initialize current service.
|
||||
local
|
||||
env: CMS_ENVIRONMENT
|
||||
l_app_name: detachable READABLE_STRING_32
|
||||
do
|
||||
Precursor
|
||||
create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI} service_options.make_from_file ("demo.ini")
|
||||
create env.make_default
|
||||
l_app_name := optional_application_name
|
||||
if l_app_name = Void then
|
||||
l_app_name := env.name
|
||||
end
|
||||
create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI} service_options.make_from_file (l_app_name + ".ini")
|
||||
initialize_logger (env)
|
||||
end
|
||||
|
||||
optional_application_name: detachable READABLE_STRING_32
|
||||
-- Optional application name.
|
||||
--| Redefine if needed.
|
||||
do
|
||||
end
|
||||
|
||||
feature {NONE} -- Launch operation
|
||||
|
||||
launcher: APPLICATION_LAUNCHER [EWF_ROC_SERVER_EXECUTION]
|
||||
launcher: APPLICATION_LAUNCHER [G]
|
||||
|
||||
launch (opts: detachable WSF_SERVICE_LAUNCHER_OPTIONS)
|
||||
local
|
||||
@@ -70,7 +81,7 @@ feature {NONE} -- Launch operation
|
||||
l_message.append ("%N%N")
|
||||
end
|
||||
else
|
||||
l_message.append ("The application crash without available information")
|
||||
l_message.append ("The application crashed without information.")
|
||||
l_message.append ("%N%N")
|
||||
end
|
||||
-- send email shutdown
|
||||
16
launcher/standalone-safe.ecf
Normal file
16
launcher/standalone-safe.ecf
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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="standalone_launcher" uuid="F42660A9-26C2-466B-A63C-C7823C808BE7" library_target="standalone_launcher">
|
||||
<target name="common" abstract="true">
|
||||
<root all_classes="true"/>
|
||||
<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="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf-safe.ecf"/>
|
||||
</target>
|
||||
<target name="standalone_launcher" extends="common">
|
||||
<library name="default_standalone" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\standalone-safe.ecf"/>
|
||||
<library name="standalone" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\standalone-safe.ecf"/>
|
||||
<cluster name="src" location=".\" recursive="false"/>
|
||||
<cluster name="launcher" location=".\default\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
17
launcher/standalone.ecf
Normal file
17
launcher/standalone.ecf
Normal file
@@ -0,0 +1,17 @@
|
||||
<?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="standalone_launcher" uuid="F42660A9-26C2-466B-A63C-C7823C808BE7" library_target="standalone_launcher">
|
||||
<target name="common" abstract="true">
|
||||
<root all_classes="true"/>
|
||||
<option void_safety="none" />
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="cms" location="..\cms.ecf"/>
|
||||
<library name="cms_app_env" location="..\library\app_env\app_env.ecf" readonly="false"/>
|
||||
<library name="wsf" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\wsf.ecf"/>
|
||||
</target>
|
||||
<target name="standalone_launcher" extends="common">
|
||||
<library name="default_standalone" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\default\standalone.ecf"/>
|
||||
<library name="standalone" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf\connector\standalone.ecf"/>
|
||||
<cluster name="src" location=".\" recursive="false"/>
|
||||
<cluster name="launcher" location=".\default\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
@@ -33,11 +33,53 @@ feature -- Query
|
||||
end
|
||||
end
|
||||
|
||||
resolved_text_list_item (k: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
|
||||
-- List of String item associated with key `k',
|
||||
-- and expanded values to resolved variables ${varname}.
|
||||
do
|
||||
if attached text_list_item (k) as lst then
|
||||
from
|
||||
lst.start
|
||||
until
|
||||
lst.after
|
||||
loop
|
||||
lst.replace (resolved_expression (lst.item))
|
||||
lst.forth
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
resolved_text_table_item (k: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
|
||||
-- Table of String item associated with key `k',
|
||||
-- and expanded values to resolved variables ${varname}.
|
||||
do
|
||||
if attached text_table_item (k) as tb then
|
||||
from
|
||||
tb.start
|
||||
until
|
||||
tb.after
|
||||
loop
|
||||
tb.replace (resolved_expression (tb.item_for_iteration), tb.key_for_iteration)
|
||||
tb.forth
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
text_item (k: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
|
||||
-- String item associated with key `k'.
|
||||
deferred
|
||||
end
|
||||
|
||||
text_list_item (k: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
|
||||
-- List of String item associated with key `k'.
|
||||
deferred
|
||||
end
|
||||
|
||||
text_table_item (k: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
|
||||
-- Table of String item associated with key `k'.
|
||||
deferred
|
||||
end
|
||||
|
||||
integer_item (k: READABLE_STRING_GENERAL): INTEGER
|
||||
-- Integer item associated with key `k'.
|
||||
deferred
|
||||
@@ -109,7 +151,7 @@ feature -- Duplication
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2014, Jocelyn Fiat, Eiffel Software and others"
|
||||
copyright: "2011-2015, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -119,14 +119,47 @@ feature -- Access: Config Reader
|
||||
|
||||
text_item (k: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
|
||||
-- String item associated with key `k'.
|
||||
local
|
||||
obj: like item
|
||||
do
|
||||
obj := item (k)
|
||||
if attached {READABLE_STRING_32} obj as s32 then
|
||||
Result := s32
|
||||
elseif attached {READABLE_STRING_8} obj as s then
|
||||
Result := utf.utf_8_string_8_to_escaped_string_32 (s)
|
||||
Result := value_to_string_32 (item (k))
|
||||
end
|
||||
|
||||
text_list_item (k: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
|
||||
-- List of String item associated with key `k'.
|
||||
do
|
||||
if attached {LIST [READABLE_STRING_8]} item (k) as l_list then
|
||||
create {ARRAYED_LIST [READABLE_STRING_32]} Result.make (l_list.count)
|
||||
Result.compare_objects
|
||||
across
|
||||
l_list as ic
|
||||
until
|
||||
Result = Void
|
||||
loop
|
||||
if attached value_to_string_32 (ic.item) as s32 then
|
||||
Result.force (s32)
|
||||
else
|
||||
Result := Void
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
text_table_item (k: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
|
||||
-- Table of String item associated with key `k'.
|
||||
do
|
||||
if attached {STRING_TABLE [READABLE_STRING_8]} item (k) as l_list then
|
||||
create {STRING_TABLE [READABLE_STRING_32]} Result.make (l_list.count)
|
||||
Result.compare_objects
|
||||
across
|
||||
l_list as ic
|
||||
until
|
||||
Result = Void
|
||||
loop
|
||||
if attached value_to_string_32 (ic.item) as s32 then
|
||||
Result.force (s32, ic.key)
|
||||
else
|
||||
Result := Void
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -226,6 +259,15 @@ feature -- Access
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
value_to_string_32 (obj: detachable ANY): detachable STRING_32
|
||||
do
|
||||
if attached {READABLE_STRING_32} obj as s32 then
|
||||
Result := s32
|
||||
elseif attached {READABLE_STRING_8} obj as s then
|
||||
Result := utf.utf_8_string_8_to_escaped_string_32 (s)
|
||||
end
|
||||
end
|
||||
|
||||
item_from_values (a_values: STRING_TABLE [ANY]; k: READABLE_STRING_GENERAL): detachable ANY
|
||||
local
|
||||
i,j: INTEGER
|
||||
@@ -400,7 +442,17 @@ feature {NONE} -- Implementation
|
||||
j := k.index_of (']', i + 1)
|
||||
if j = i + 1 then -- ends_with "[]"
|
||||
k.keep_head (i - 1)
|
||||
if attached {LIST [STRING_8]} items.item (k) as l_list then
|
||||
if
|
||||
a_section_prefix /= Void and then
|
||||
attached {LIST [STRING_8]} items.item (a_section_prefix + {STRING_32} "." + k) as l_list
|
||||
then
|
||||
lst := l_list
|
||||
elseif
|
||||
attached last_section_name as l_section_prefix and then
|
||||
attached {LIST [STRING_8]} items.item (l_section_prefix + {STRING_32} "." + k) as l_list
|
||||
then
|
||||
lst := l_list
|
||||
elseif attached {LIST [STRING_8]} items.item (k) as l_list then
|
||||
lst := l_list
|
||||
else
|
||||
create {ARRAYED_LIST [STRING_8]} lst.make (1)
|
||||
@@ -414,7 +466,17 @@ feature {NONE} -- Implementation
|
||||
sk.left_adjust
|
||||
sk.right_adjust
|
||||
k.keep_head (i - 1)
|
||||
if attached {STRING_TABLE [STRING_8]} items.item (k) as l_table then
|
||||
if
|
||||
a_section_prefix /= Void and then
|
||||
attached {STRING_TABLE [STRING_8]} items.item (a_section_prefix + {STRING_32} "." + k) as l_table
|
||||
then
|
||||
tb := l_table
|
||||
elseif
|
||||
attached last_section_name as l_section_prefix and then
|
||||
attached {STRING_TABLE [STRING_8]} items.item (l_section_prefix + {STRING_32} "." + k) as l_table
|
||||
then
|
||||
tb := l_table
|
||||
elseif attached {STRING_TABLE [STRING_8]} items.item (k) as l_table then
|
||||
tb := l_table
|
||||
else
|
||||
create tb.make (1)
|
||||
@@ -460,7 +522,7 @@ feature {NONE} -- Implementation
|
||||
invariant
|
||||
|
||||
note
|
||||
copyright: "2011-2014, Jocelyn Fiat, Eiffel Software and others"
|
||||
copyright: "2011-2015, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -63,10 +63,46 @@ feature -- Access: Config Reader
|
||||
text_item (k: READABLE_STRING_GENERAL): detachable READABLE_STRING_32
|
||||
-- String item associated with query `k'.
|
||||
do
|
||||
if attached {JSON_STRING} item (k) as l_string then
|
||||
Result := l_string.unescaped_string_32
|
||||
elseif attached {JSON_NUMBER} item (k) as l_number then
|
||||
Result := l_number.item
|
||||
Result := value_to_string_32 (item (k))
|
||||
end
|
||||
|
||||
text_list_item (k: READABLE_STRING_GENERAL): detachable LIST [READABLE_STRING_32]
|
||||
-- List of String item associated with key `k'.
|
||||
do
|
||||
if attached {JSON_ARRAY} item (k) as l_array then
|
||||
create {ARRAYED_LIST [READABLE_STRING_32]} Result.make (l_array.count)
|
||||
Result.compare_objects
|
||||
across
|
||||
l_array as ic
|
||||
until
|
||||
Result = Void
|
||||
loop
|
||||
if attached value_to_string_32 (ic.item) as s32 then
|
||||
Result.force (s32)
|
||||
else
|
||||
Result := Void
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
text_table_item (k: READABLE_STRING_GENERAL): detachable STRING_TABLE [READABLE_STRING_32]
|
||||
-- Table of String item associated with key `k'.
|
||||
do
|
||||
if attached {JSON_OBJECT} item (k) as obj then
|
||||
create {STRING_TABLE [READABLE_STRING_32]} Result.make (obj.count)
|
||||
Result.compare_objects
|
||||
across
|
||||
obj as ic
|
||||
until
|
||||
Result = Void
|
||||
loop
|
||||
if attached value_to_string_32 (ic.item) as s32 then
|
||||
Result.force (s32, ic.key.item)
|
||||
else
|
||||
Result := Void
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -105,6 +141,15 @@ feature -- Access
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
value_to_string_32 (v: detachable ANY): detachable STRING_32
|
||||
do
|
||||
if attached {JSON_STRING} v as l_string then
|
||||
Result := l_string.unescaped_string_32
|
||||
elseif attached {JSON_NUMBER} v as l_number then
|
||||
Result := l_number.item
|
||||
end
|
||||
end
|
||||
|
||||
object_json_value (a_object: JSON_OBJECT; a_query: READABLE_STRING_32): detachable JSON_VALUE
|
||||
-- Item associated with query `a_query' from object `a_object' if any.
|
||||
local
|
||||
@@ -163,7 +208,7 @@ feature {NONE} -- JSON
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2014, Jocelyn Fiat, Eiffel Software and others"
|
||||
copyright: "2011-2015, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
|
||||
@@ -21,6 +21,18 @@ feature -- Test
|
||||
create {INI_CONFIG} cfg.make_from_string ("[
|
||||
foo = bar
|
||||
|
||||
collection[] = a
|
||||
collection[] = b
|
||||
collection[] = c
|
||||
collection[] = 1
|
||||
collection[] = 2
|
||||
collection[] = 3
|
||||
|
||||
table[a] = 1
|
||||
table[b] = 2
|
||||
table[c] = 3
|
||||
table[d] = test
|
||||
|
||||
[first]
|
||||
abc = 1
|
||||
def = and so on
|
||||
@@ -58,6 +70,21 @@ feature -- Test
|
||||
assert ("has_item (second.is)", cfg.has_item ("second.is"))
|
||||
assert ("item (second.is)", attached cfg.text_item ("second.is") as v and then v.same_string_general ("2"))
|
||||
|
||||
assert ("has_item (collection)", cfg.has_item ("collection"))
|
||||
assert ("item (collection)", attached cfg.text_list_item ("collection") as lst and then (
|
||||
lst.has ("a") and lst.has ("b") and lst.has ("c") and lst.has ("1") and lst.has ("2") and lst.has ("3")
|
||||
)
|
||||
)
|
||||
|
||||
assert ("has_item (table)", cfg.has_item ("table"))
|
||||
assert ("item (table)", attached cfg.text_table_item ("table") as tb and then (
|
||||
tb.item ("a") ~ {STRING_32} "1" and
|
||||
tb.item ("b") ~ {STRING_32} "2" and
|
||||
tb.item ("c") ~ {STRING_32} "3" and
|
||||
tb.item ("d") ~ {STRING_32} "test"
|
||||
)
|
||||
)
|
||||
|
||||
if attached cfg.sub_config ("second") as cfg_second then
|
||||
assert ("has_item (is)", cfg_second.has_item ("is"))
|
||||
assert ("item (is)", attached cfg_second.text_item ("is") as v and then v.same_string_general ("2"))
|
||||
@@ -141,7 +168,9 @@ feature -- Test
|
||||
"is": 2,
|
||||
"the": 3,
|
||||
"end": 4
|
||||
}
|
||||
},
|
||||
"collection": ["a", "b", "c", 1, 2, 3],
|
||||
"table": { "a": 1, "b": 2, "c": 3, "d" : "test" }
|
||||
}
|
||||
]")
|
||||
|
||||
@@ -164,6 +193,21 @@ feature -- Test
|
||||
assert ("item (second.the)", attached cfg.text_item ("second.the") as v and then v.same_string_general ("3"))
|
||||
assert ("item (second.end)", attached cfg.text_item ("second.end") as v and then v.same_string_general ("4"))
|
||||
|
||||
assert ("has_item (collection)", cfg.has_item ("collection"))
|
||||
assert ("item (collection)", attached cfg.text_list_item ("collection") as lst and then (
|
||||
lst.has ("a") and lst.has ("b") and lst.has ("c") and lst.has ("1") and lst.has ("2") and lst.has ("3")
|
||||
)
|
||||
)
|
||||
|
||||
assert ("has_item (table)", cfg.has_item ("table"))
|
||||
assert ("item (table)", attached cfg.text_table_item ("table") as tb and then (
|
||||
tb.item ("a") ~ {STRING_32} "1" and
|
||||
tb.item ("b") ~ {STRING_32} "2" and
|
||||
tb.item ("c") ~ {STRING_32} "3" and
|
||||
tb.item ("d") ~ {STRING_32} "test"
|
||||
)
|
||||
)
|
||||
|
||||
if attached cfg.sub_config ("second") as cfg_second then
|
||||
assert ("has_item (is)", cfg_second.has_item ("is"))
|
||||
assert ("item (is)", attached cfg_second.text_item ("is") as v and then v.same_string_general ("2"))
|
||||
|
||||
@@ -28,7 +28,6 @@ feature {NONE} -- Initialization
|
||||
-- Initialize service.
|
||||
do
|
||||
admin_email := parameters.admin_email
|
||||
|
||||
create {NOTIFICATION_SMTP_MAILER} mailer.make (parameters.smtp_server)
|
||||
set_successful
|
||||
end
|
||||
|
||||
4
library/gcse/Readme.md
Normal file
4
library/gcse/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Google Custom Search Engine Eiffel Lbrary
|
||||
|
||||
Based on https://developers.google.com/custom-search/json-api/v1/using_rest
|
||||
|
||||
21
library/gcse/gcse-safe.ecf
Normal file
21
library/gcse/gcse-safe.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-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="gcse" uuid="81645CEF-4651-45CF-A890-B126E4A6D78C" library_target="gcse">
|
||||
<target name="gcse">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" void_safety="all">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="http_client_extension" location="..\http_client_extension\http_client_extension-safe.ecf" readonly="false"/>
|
||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf" readonly="false"/>
|
||||
<library name="wsf_encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder-safe.ecf"/>
|
||||
<cluster name="gcse" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
21
library/gcse/gcse.ecf
Normal file
21
library/gcse/gcse.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-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="gcse" uuid="81645CEF-4651-45CF-A890-B126E4A6D78C" library_target="gcse">
|
||||
<target name="gcse">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" void_safety="none">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="http_client_extension" location="..\http_client_extension\http_client_extension.ecf" readonly="false"/>
|
||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json.ecf" readonly="false"/>
|
||||
<library name="wsf_encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder.ecf"/>
|
||||
<cluster name="gcse" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
10
library/gcse/license.lic
Normal file
10
library/gcse/license.lic
Normal file
@@ -0,0 +1,10 @@
|
||||
${NOTE_KEYWORD}
|
||||
copyright: "2011-${YEAR} Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
236
library/gcse/src/gcse_api.e
Normal file
236
library/gcse/src/gcse_api.e
Normal file
@@ -0,0 +1,236 @@
|
||||
note
|
||||
description: "[
|
||||
Simple API to call Google Custome Search Engine
|
||||
Example call:
|
||||
GET https://www.googleapis.com/customsearch/v1?key=INSERT_YOUR_API_KEY&cx=017576662512468239146:omuauf_lfve&q=lectures
|
||||
]"
|
||||
date: "$Date: 2015-10-09 08:11:07 -0300 (vi., 09 oct. 2015) $"
|
||||
revision: "$Revision: 97973 $"
|
||||
EIS: "name=Google Custom Search Engine", "src=https://developers.google.com/custom-search/json-api/v1/using_rest", "protocol=uri"
|
||||
|
||||
class
|
||||
GCSE_API
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_query_parameters: GCSE_QUERY_PARAMETERS)
|
||||
-- Create an object GCSE with query_parameters `a_query_parameters'
|
||||
do
|
||||
query_parameter := a_query_parameters
|
||||
ensure
|
||||
query_parameters_set: query_parameter = a_query_parameters
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
base_uri: STRING_8 = "https://www.googleapis.com/customsearch/v1"
|
||||
-- Google custom search base URI.
|
||||
|
||||
query_parameter: GCSE_QUERY_PARAMETERS
|
||||
-- Google custom search parameters.
|
||||
|
||||
last_result: detachable GCSE_RESPONSE
|
||||
-- Search results.
|
||||
|
||||
feature -- Status Reports
|
||||
|
||||
errors: detachable LIST [READABLE_STRING_8]
|
||||
-- optional list of error messages.
|
||||
|
||||
feature -- API
|
||||
|
||||
search
|
||||
-- Search
|
||||
local
|
||||
l_parser: JSON_PARSER
|
||||
l_gcse_response: detachable GCSE_RESPONSE
|
||||
do
|
||||
-- Data format for the response.
|
||||
-- At the moment we are using the default value: json
|
||||
-- but it's possible to define atom response using the alt parameter.
|
||||
last_result := Void
|
||||
if attached get as l_response then
|
||||
create l_gcse_response
|
||||
l_gcse_response.set_status (l_response.status)
|
||||
l_gcse_response.set_status_nessage (l_response.status_message)
|
||||
|
||||
if attached l_response.body as l_body then
|
||||
create l_parser.make_with_string (l_body)
|
||||
l_parser.parse_content
|
||||
if l_response.status = 200 and then l_parser.is_parsed and then attached {JSON_OBJECT} l_parser.parsed_json_object as jv then
|
||||
-- Queries
|
||||
if attached {JSON_OBJECT} jv.item (queries_key) as jqueries then
|
||||
-- Next Page
|
||||
if attached {GCSE_PAGE} query_page (next_page_key, jqueries) as l_page then
|
||||
l_gcse_response.set_next_page (l_page)
|
||||
end
|
||||
-- Current Page
|
||||
if attached {GCSE_PAGE} query_page (request_key, jqueries) as l_page then
|
||||
l_gcse_response.set_current_page (l_page)
|
||||
end
|
||||
-- Previous Page
|
||||
if attached {GCSE_PAGE} query_page (previous_page_key, jqueries) as l_page then
|
||||
l_gcse_response.set_previous_page (l_page)
|
||||
end
|
||||
end
|
||||
if attached {JSON_ARRAY} jv.item (items_key) as jitems then
|
||||
across jitems as ic loop
|
||||
if attached{JSON_OBJECT} ic.item as j_item then
|
||||
l_gcse_response.add_item (item (j_item))
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
put_error (l_body)
|
||||
end
|
||||
else
|
||||
put_error (l_response.status.out)
|
||||
end
|
||||
else
|
||||
put_error ("unknown")
|
||||
end
|
||||
last_result := l_gcse_response
|
||||
end
|
||||
|
||||
feature {NONE} -- REST API
|
||||
|
||||
get: detachable RESPONSE
|
||||
-- Reading Data.
|
||||
local
|
||||
l_request: REQUEST
|
||||
do
|
||||
create l_request.make ("GET", new_uri)
|
||||
Result := l_request.execute
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
new_uri: STRING_8
|
||||
-- new uri (BaseUri?key=secret_value&cx=a_cx_id&q=a_query
|
||||
-- ?key=INSERT_YOUR_API_KEY&cx=017576662512468239146:omuauf_lfve&q=lectures
|
||||
-- full template BaseUri?q={searchTerms}&num={count?}&start={startIndex?}&lr={language?}&
|
||||
-- safe={safe?}&cx={cx?}&cref={cref?}&sort={sort?}&filter={filter?}&gl={gl?}&cr={cr?}&
|
||||
-- googlehost={googleHost?}&c2coff={disableCnTwTranslation?}&hq={hq?}&hl={hl?}&siteSearch={siteSearch?}&
|
||||
-- siteSearchFilter={siteSearchFilter?}&exactTerms={exactTerms?}&excludeTerms={excludeTerms?}&linkSite={linkSite?}&
|
||||
-- orTerms={orTerms?}&relatedSite={relatedSite?}&dateRestrict={dateRestrict?}&lowRange={lowRange?}&highRange={highRange?}&
|
||||
-- searchType={searchType}&fileType={fileType?}&rights={rights?}&imgSize={imgSize?}&imgType={imgType?}&
|
||||
-- imgColorType={imgColorType?}&imgDominantColor={imgDominantColor?}&alt=json"
|
||||
|
||||
do
|
||||
create Result.make_from_string (base_uri)
|
||||
Result.append ("?key=")
|
||||
Result.append (query_parameter.secret)
|
||||
Result.append ("&cx=")
|
||||
Result.append (query_parameter.cx)
|
||||
Result.append ("&q=")
|
||||
Result.append (query_parameter.query)
|
||||
-- num
|
||||
if attached query_parameter.num as l_num then
|
||||
Result.append ("&num=")
|
||||
Result.append (l_num)
|
||||
end
|
||||
if attached query_parameter.start as l_start then
|
||||
Result.append ("&start=")
|
||||
Result.append (l_start)
|
||||
end
|
||||
end
|
||||
|
||||
put_error (a_message: READABLE_STRING_GENERAL)
|
||||
-- put error message `a_message'.
|
||||
local
|
||||
l_errors: like errors
|
||||
utf: UTF_CONVERTER
|
||||
do
|
||||
l_errors := errors
|
||||
if l_errors = Void then
|
||||
create {ARRAYED_LIST [STRING]} l_errors.make (1)
|
||||
errors := l_errors
|
||||
end
|
||||
l_errors.force (utf.utf_32_string_to_utf_8_string_8 (a_message))
|
||||
end
|
||||
|
||||
item (a_item: JSON_OBJECT): GCSE_PAGE_ITEM
|
||||
-- Google Result Metadata Item.
|
||||
do
|
||||
create Result
|
||||
if attached {JSON_STRING} a_item.item ("kind") as l_kind then
|
||||
Result.set_kind (l_kind.item)
|
||||
end
|
||||
if attached {JSON_STRING} a_item.item ("title") as l_title then
|
||||
Result.set_title (l_title.item)
|
||||
end
|
||||
if attached {JSON_STRING} a_item.item ("htmlTitle") as l_htmltitle then
|
||||
Result.set_html_title (l_htmltitle.unescaped_string_32)
|
||||
end
|
||||
if attached {JSON_STRING} a_item.item ("link") as l_link then
|
||||
Result.set_link (l_link.item)
|
||||
end
|
||||
if attached {JSON_STRING} a_item.item ("displayLink") as l_display_link then
|
||||
Result.set_display_link (l_display_link.item)
|
||||
end
|
||||
if attached {JSON_STRING} a_item.item ("snippet") as l_snippet then
|
||||
Result.set_snippet (l_snippet.unescaped_string_8)
|
||||
end
|
||||
if attached {JSON_STRING} a_item.item ("htmlSnippet") as l_html_snippet then
|
||||
Result.set_html_snippet (l_html_snippet.unescaped_string_32)
|
||||
end
|
||||
if attached {JSON_STRING} a_item.item ("formattedUrl") as l_formatted_url then
|
||||
Result.set_formatted_url (l_formatted_url.item)
|
||||
end
|
||||
end
|
||||
|
||||
query_page (a_page_key: JSON_STRING; a_queries: JSON_OBJECT): detachable GCSE_PAGE
|
||||
-- Google result medata query. Return a query page based for a query with page key `a_page_key', if any.
|
||||
do
|
||||
if
|
||||
attached {JSON_ARRAY} a_queries.item (a_page_key) as jquerypage and then
|
||||
jquerypage.count > 0 and then
|
||||
attached {JSON_OBJECT} jquerypage.i_th (1) as jpage
|
||||
then
|
||||
create Result
|
||||
if attached {JSON_STRING} jpage.item ("title") as l_title then
|
||||
Result.set_title (l_title.item)
|
||||
end
|
||||
if attached {JSON_STRING} jpage.item ("totalResults") as l_results then
|
||||
Result.set_total_results (l_results.item.to_integer)
|
||||
end
|
||||
if attached {JSON_STRING} jpage.item ("searchTerms") as l_search_terms then
|
||||
Result.set_search_terms (l_search_terms.item)
|
||||
end
|
||||
-- TODO check if we should use INTEGER_64
|
||||
if attached {JSON_NUMBER} jpage.item ("count") as l_count then
|
||||
Result.set_count (l_count.integer_64_item.as_integer_32)
|
||||
end
|
||||
if attached {JSON_NUMBER} jpage.item ("startIndex") as l_index then
|
||||
Result.set_start_index (l_index.integer_64_item.as_integer_32)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- JSON Keys
|
||||
|
||||
queries_key: STRING = "queries"
|
||||
|
||||
next_page_key: STRING = "nextPage"
|
||||
|
||||
request_key: STRING = "request"
|
||||
|
||||
previous_page_key: STRING = "previousPage"
|
||||
|
||||
items_key: STRING = "items"
|
||||
|
||||
note
|
||||
copyright: "2011-2015 Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
|
||||
end
|
||||
118
library/gcse/src/gcse_page.e
Normal file
118
library/gcse/src/gcse_page.e
Normal file
@@ -0,0 +1,118 @@
|
||||
note
|
||||
description: "Represent metadata describing the query for the current set of results."
|
||||
date: "$Date: 2015-10-09 08:11:07 -0300 (vi., 09 oct. 2015) $"
|
||||
revision: "$Revision: 97973 $"
|
||||
|
||||
class
|
||||
GCSE_PAGE
|
||||
|
||||
inherit
|
||||
|
||||
DEBUG_OUTPUT
|
||||
|
||||
feature -- Access
|
||||
|
||||
search_terms: detachable STRING_8
|
||||
-- search term
|
||||
|
||||
title: detachable STRING_8
|
||||
-- Search title.
|
||||
|
||||
total_results: INTEGER
|
||||
-- Search total results.
|
||||
|
||||
count: INTEGER
|
||||
-- Rows per page.
|
||||
|
||||
start_index: INTEGER
|
||||
-- Page index.
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_search_terms (a_search_terms: like search_terms)
|
||||
-- Assign `search_terms' with `a_search_terms'.
|
||||
do
|
||||
search_terms := a_search_terms
|
||||
ensure
|
||||
search_terms_assigned: search_terms = a_search_terms
|
||||
end
|
||||
|
||||
feature -- Change element
|
||||
|
||||
set_title (a_title: like title)
|
||||
-- Set title with `a_title'
|
||||
do
|
||||
title := a_title
|
||||
ensure
|
||||
title_set: title = a_title
|
||||
end
|
||||
|
||||
set_total_results (a_total_results: like total_results)
|
||||
-- Set total_results with `a_total_results'.
|
||||
do
|
||||
total_results := a_total_results
|
||||
ensure
|
||||
total_results_set: total_results = a_total_results
|
||||
end
|
||||
|
||||
set_count (a_count: like count)
|
||||
-- Set count with `a_count'.
|
||||
do
|
||||
count := a_count
|
||||
ensure
|
||||
count_set: count = a_count
|
||||
end
|
||||
|
||||
set_start_index (a_start_index: like start_index)
|
||||
-- Set start_index with `a_start_index'.
|
||||
do
|
||||
start_index := a_start_index
|
||||
ensure
|
||||
start_index_set: start_index = a_start_index
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
debug_output: STRING_8
|
||||
-- <Precursor>
|
||||
do
|
||||
create Result.make_from_string ("%NPage details%N")
|
||||
if attached title as l_title then
|
||||
Result.append ("Title:")
|
||||
Result.append (l_title)
|
||||
Result.append_character ('%N')
|
||||
end
|
||||
if attached search_terms as l_search_tearm then
|
||||
Result.append ("Search Tearm:")
|
||||
Result.append (l_search_tearm)
|
||||
Result.append_character ('%N')
|
||||
end
|
||||
|
||||
Result.append ("Count:")
|
||||
Result.append (count.out)
|
||||
Result.append_character ('%N')
|
||||
Result.append ("Total Result:")
|
||||
Result.append (count.out)
|
||||
Result.append_character ('%N')
|
||||
Result.append ("Count:")
|
||||
Result.append (total_results.out)
|
||||
Result.append_character ('%N')
|
||||
Result.append ("Start index:")
|
||||
Result.append (start_index.out)
|
||||
Result.append_character ('%N')
|
||||
|
||||
end
|
||||
|
||||
|
||||
note
|
||||
copyright: "2011-2015 Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
|
||||
end
|
||||
208
library/gcse/src/gcse_page_item.e
Normal file
208
library/gcse/src/gcse_page_item.e
Normal file
@@ -0,0 +1,208 @@
|
||||
note
|
||||
description: "Represent a search result, include the URL, title and text snippets that describe the result"
|
||||
date: "$Date: 2015-10-09 08:11:07 -0300 (vi., 09 oct. 2015) $"
|
||||
revision: "$Revision: 97973 $"
|
||||
|
||||
class
|
||||
GCSE_PAGE_ITEM
|
||||
|
||||
inherit
|
||||
DEBUG_OUTPUT
|
||||
|
||||
feature -- Access
|
||||
|
||||
html_formatted_url: detachable STRING_8
|
||||
-- Html formatted url of this result
|
||||
|
||||
formatted_url: detachable STRING_8
|
||||
-- Formatted url of this result
|
||||
|
||||
cache_id: detachable STRING_8
|
||||
-- Cache id of this result
|
||||
|
||||
html_snippet: detachable STRING_8
|
||||
-- Html snippet of this request
|
||||
|
||||
snippet: detachable STRING_8
|
||||
-- Snippet of this result
|
||||
|
||||
display_link: detachable STRING_8
|
||||
-- Display link of this result
|
||||
|
||||
link: detachable STRING_8
|
||||
-- link of this result
|
||||
|
||||
html_title: detachable STRING_8
|
||||
-- html title of result
|
||||
|
||||
title: detachable STRING_8
|
||||
-- title of result.
|
||||
|
||||
kind: detachable STRING_8
|
||||
-- Kind of actual search result.
|
||||
|
||||
page_map: detachable GCSE_PAGE_MAP
|
||||
-- Page map
|
||||
--! Not supported for now.
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_html_formatted_url (a_html_formatted_url: like html_formatted_url)
|
||||
-- Assign `html_formatted_url' with `a_html_formatted_url'.
|
||||
do
|
||||
html_formatted_url := a_html_formatted_url
|
||||
ensure
|
||||
html_formatted_url_assigned: html_formatted_url = a_html_formatted_url
|
||||
end
|
||||
|
||||
set_formatted_url (a_formatted_url: like formatted_url)
|
||||
-- Assign `formatted_url' with `a_formatted_url'.
|
||||
do
|
||||
formatted_url := a_formatted_url
|
||||
ensure
|
||||
formatted_url_assigned: formatted_url = a_formatted_url
|
||||
end
|
||||
|
||||
set_cache_id (a_cache_id: like cache_id)
|
||||
-- Assign `cache_id' with `a_cache_id'.
|
||||
do
|
||||
cache_id := a_cache_id
|
||||
ensure
|
||||
cache_id_assigned: cache_id = a_cache_id
|
||||
end
|
||||
|
||||
set_html_snippet (a_html_snippet: like html_snippet)
|
||||
-- Assign `html_snippet' with `a_html_snippet'.
|
||||
do
|
||||
html_snippet := a_html_snippet
|
||||
ensure
|
||||
html_snippet_assigned: html_snippet = a_html_snippet
|
||||
end
|
||||
|
||||
set_snippet (a_snippet: like snippet)
|
||||
-- Assign `snippet' with `a_snippet'.
|
||||
do
|
||||
snippet := a_snippet
|
||||
ensure
|
||||
snippet_assigned: snippet = a_snippet
|
||||
end
|
||||
|
||||
set_display_link (a_display_link: like display_link)
|
||||
-- Assign `display_link' with `a_display_link'.
|
||||
do
|
||||
display_link := a_display_link
|
||||
ensure
|
||||
display_link_assigned: display_link = a_display_link
|
||||
end
|
||||
|
||||
set_link (a_link: like link)
|
||||
-- Assign `link' with `a_link'.
|
||||
do
|
||||
link := a_link
|
||||
ensure
|
||||
link_assigned: link = a_link
|
||||
end
|
||||
|
||||
set_html_title (a_html_title: like html_title)
|
||||
-- Assign `html_title' with `a_html_title'.
|
||||
do
|
||||
html_title := a_html_title
|
||||
ensure
|
||||
html_title_assigned: html_title = a_html_title
|
||||
end
|
||||
|
||||
set_title (a_title: like title)
|
||||
-- Assign `title' with `a_title'.
|
||||
do
|
||||
title := a_title
|
||||
ensure
|
||||
title_assigned: title = a_title
|
||||
end
|
||||
|
||||
set_kind (a_kind: like kind)
|
||||
-- Assign `kind' with `a_kind'.
|
||||
do
|
||||
kind := a_kind
|
||||
ensure
|
||||
kind_assigned: kind = a_kind
|
||||
end
|
||||
|
||||
set_page_map (a_map: like page_map)
|
||||
-- Assign `kind' with `a_kind'.
|
||||
do
|
||||
page_map := a_map
|
||||
ensure
|
||||
page_map_assigned: page_map = a_map
|
||||
end
|
||||
|
||||
|
||||
feature -- Output
|
||||
|
||||
debug_output: STRING_8
|
||||
-- <Precursor>
|
||||
do
|
||||
create Result.make_from_string ("%NPage Item details%N")
|
||||
if attached title as l_title then
|
||||
Result.append ("Title:")
|
||||
Result.append (l_title)
|
||||
Result.append_character ('%N')
|
||||
end
|
||||
if attached kind as l_kind then
|
||||
Result.append ("Kind:")
|
||||
Result.append (l_kind)
|
||||
Result.append_character ('%N')
|
||||
end
|
||||
if attached html_title as l_html_title then
|
||||
Result.append ("Html title:")
|
||||
Result.append (l_html_title)
|
||||
Result.append_character ('%N')
|
||||
end
|
||||
if attached link as l_link then
|
||||
Result.append ("Link:")
|
||||
Result.append (l_link)
|
||||
Result.append_character ('%N')
|
||||
end
|
||||
if attached display_link as l_display_link then
|
||||
Result.append ("Display link:")
|
||||
Result.append (l_display_link)
|
||||
Result.append_character ('%N')
|
||||
end
|
||||
if attached snippet as l_snippet then
|
||||
Result.append ("Snippet:")
|
||||
Result.append (l_snippet)
|
||||
Result.append_character ('%N')
|
||||
end
|
||||
if attached html_snippet as l_html_snippet then
|
||||
Result.append ("Html snippet:")
|
||||
Result.append (l_html_snippet)
|
||||
Result.append_character ('%N')
|
||||
end
|
||||
if attached cache_id as l_cache_id then
|
||||
Result.append ("Cache_id:")
|
||||
Result.append (l_cache_id)
|
||||
Result.append_character ('%N')
|
||||
end
|
||||
if attached formatted_url as l_formatted_url then
|
||||
Result.append ("Formatted url:")
|
||||
Result.append (l_formatted_url)
|
||||
Result.append_character ('%N')
|
||||
end
|
||||
if attached html_formatted_url as l_html_formatted_url then
|
||||
Result.append ("Html formatted url:")
|
||||
Result.append (l_html_formatted_url)
|
||||
Result.append_character ('%N')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2015 Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
41
library/gcse/src/gcse_page_map.e
Normal file
41
library/gcse/src/gcse_page_map.e
Normal file
@@ -0,0 +1,41 @@
|
||||
note
|
||||
description: "[
|
||||
Represent a google page map
|
||||
"pagemap": {
|
||||
"cse_image": [
|
||||
{
|
||||
"src": "https://www.eiffel.org/portal/files/userpictures/picture-40.jpg"
|
||||
}
|
||||
],
|
||||
"cse_thumbnail": [
|
||||
{
|
||||
"width": "81",
|
||||
"height": "61",
|
||||
"src": "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcRnC-RKzps6BFItx_MLYBVskFI7U6u0y3VJBInomPYEF5sO6gkip94mLw"
|
||||
}
|
||||
]
|
||||
}
|
||||
]"
|
||||
date: "$Date: 2015-10-09 08:11:07 -0300 (vi., 09 oct. 2015) $"
|
||||
revision: "$Revision: 97973 $"
|
||||
EIS: "name=PageMaps", "src=https://developers.google.com/custom-search/docs/structured_data#pagemaps", "protocol=url"
|
||||
|
||||
class
|
||||
GCSE_PAGE_MAP
|
||||
|
||||
feature -- Access
|
||||
|
||||
|
||||
|
||||
|
||||
note
|
||||
copyright: "2011-2015 Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
238
library/gcse/src/gcse_query_parameters.e
Normal file
238
library/gcse/src/gcse_query_parameters.e
Normal file
@@ -0,0 +1,238 @@
|
||||
note
|
||||
description: "[
|
||||
Represent google custom search parameters
|
||||
Example url template
|
||||
"template": "https://www.googleapis.com/customsearch/v1?q={searchTerms}&num={count?}&start={startIndex?}&lr={language?}&safe={safe?}&cx={cx?}&cref={cref?}&sort={sort?}&filter={filter?}&gl={gl?}&cr={cr?}&googlehost={googleHost?}&c2coff={disableCnTwTranslation?}&hq={hq?}&hl={hl?}&siteSearch={siteSearch?}&siteSearchFilter={siteSearchFilter?}&exactTerms={exactTerms?}&excludeTerms={excludeTerms?}&linkSite={linkSite?}&orTerms={orTerms?}&relatedSite={relatedSite?}&dateRestrict={dateRestrict?}&lowRange={lowRange?}&highRange={highRange?}&searchType={searchType}&fileType={fileType?}&rights={rights?}&imgSize={imgSize?}&imgType={imgType?}&imgColorType={imgColorType?}&imgDominantColor={imgDominantColor?}&alt=json"
|
||||
]"
|
||||
optional_parameters: "[
|
||||
Optional parameters
|
||||
c2coff string Enables or disables Simplified and Traditional Chinese Search.
|
||||
The default value for this parameter is 0 (zero), meaning that the feature is enabled. Supported values are:
|
||||
1: Disabled
|
||||
0: Enabled (default)
|
||||
cr string Restricts search results to documents originating in a particular country.
|
||||
You may use Boolean operators in the cr parameter's value.
|
||||
Google Search determines the country of a document by analyzing:
|
||||
the top-level domain (TLD) of the document's URL
|
||||
the geographic location of the Web server's IP address
|
||||
See the Country Parameter Values page for a list of valid values for this parameter.
|
||||
cref string The URL of a linked custom search engine specification to use for this request.
|
||||
Does not apply for Google Site Search
|
||||
If both cx and cref are specified, the cx value is used
|
||||
cx string The custom search engine ID to use for this request.
|
||||
If both cx and cref are specified, the cx value is used.
|
||||
dateRestrict string Restricts results to URLs based on date. Supported values include:
|
||||
d[number]: requests results from the specified number of past days.
|
||||
w[number]: requests results from the specified number of past weeks.
|
||||
m[number]: requests results from the specified number of past months.
|
||||
y[number]: requests results from the specified number of past years.
|
||||
exactTerms string Identifies a phrase that all documents in the search results must contain.
|
||||
excludeTerms string Identifies a word or phrase that should not appear in any documents in the search results.
|
||||
fileType string Restricts results to files of a specified extension. A list of file types indexable by Google can be found in Webmaster Tools Help Center.
|
||||
filter string Controls turning on or off the duplicate content filter.
|
||||
See Automatic Filtering for more information about Google's search results filters. Note that host crowding filtering applies only to multi-site searches.
|
||||
By default, Google applies filtering to all search results to improve the quality of those results.
|
||||
|
||||
|
||||
Acceptable values are:
|
||||
"0": Turns off duplicate content filter.
|
||||
"1": Turns on duplicate content filter.
|
||||
gl string Geolocation of end user.
|
||||
The gl parameter value is a two-letter country code. The gl parameter boosts search results whose country of origin matches the parameter value. See the Country Codes page for a list of valid values.
|
||||
Specifying a gl parameter value should lead to more relevant results. This is particularly true for international customers and, even more specifically, for customers in English- speaking countries other than the United States.
|
||||
googlehost string The local Google domain (for example, google.com, google.de, or google.fr) to use to perform the search.
|
||||
highRange string
|
||||
Specifies the ending value for a search range.
|
||||
Use lowRange and highRange to append an inclusive search range of lowRange...highRange to the query.
|
||||
hl string Sets the user interface language.
|
||||
Explicitly setting this parameter improves the performance and the quality of your search results.
|
||||
See the Interface Languages section of Internationalizing Queries and Results Presentation for more information, and Supported Interface Languages for a list of supported languages.
|
||||
hq string Appends the specified query terms to the query, as if they were combined with a logical AND operator.
|
||||
imgColorType string Returns black and white, grayscale, or color images: mono, gray, and color.
|
||||
|
||||
Acceptable values are:
|
||||
"color": color
|
||||
"gray": gray
|
||||
"mono": mono
|
||||
imgDominantColor string Returns images of a specific dominant color.
|
||||
|
||||
Acceptable values are:
|
||||
"black": black
|
||||
"blue": blue
|
||||
"brown": brown
|
||||
"gray": gray
|
||||
"green": green
|
||||
"pink": pink
|
||||
"purple": purple
|
||||
"teal": teal
|
||||
"white": white
|
||||
"yellow": yellow
|
||||
imgSize string Returns images of a specified size.
|
||||
|
||||
Acceptable values are:
|
||||
"huge": huge
|
||||
"icon": icon
|
||||
"large": large
|
||||
"medium": medium
|
||||
"small": small
|
||||
"xlarge": xlarge
|
||||
"xxlarge": xxlarge
|
||||
imgType string Returns images of a type.
|
||||
|
||||
Acceptable values are:
|
||||
"clipart": clipart
|
||||
"face": face
|
||||
"lineart": lineart
|
||||
"news": news
|
||||
"photo": photo
|
||||
linkSite string Specifies that all search results should contain a link to a particular URL
|
||||
lowRange string Specifies the starting value for a search range.
|
||||
Use lowRange and highRange to append an inclusive search range of lowRange...highRange to the query.
|
||||
lr string Restricts the search to documents written in a particular language (e.g., lr=lang_ja).
|
||||
|
||||
Acceptable values are:
|
||||
"lang_ar": Arabic
|
||||
"lang_bg": Bulgarian
|
||||
"lang_ca": Catalan
|
||||
"lang_cs": Czech
|
||||
"lang_da": Danish
|
||||
"lang_de": German
|
||||
"lang_el": Greek
|
||||
"lang_en": English
|
||||
"lang_es": Spanish
|
||||
"lang_et": Estonian
|
||||
"lang_fi": Finnish
|
||||
"lang_fr": French
|
||||
"lang_hr": Croatian
|
||||
"lang_hu": Hungarian
|
||||
"lang_id": Indonesian
|
||||
"lang_is": Icelandic
|
||||
"lang_it": Italian
|
||||
"lang_iw": Hebrew
|
||||
"lang_ja": Japanese
|
||||
"lang_ko": Korean
|
||||
"lang_lt": Lithuanian
|
||||
"lang_lv": Latvian
|
||||
"lang_nl": Dutch
|
||||
"lang_no": Norwegian
|
||||
"lang_pl": Polish
|
||||
"lang_pt": Portuguese
|
||||
"lang_ro": Romanian
|
||||
"lang_ru": Russian
|
||||
"lang_sk": Slovak
|
||||
"lang_sl": Slovenian
|
||||
"lang_sr": Serbian
|
||||
"lang_sv": Swedish
|
||||
"lang_tr": Turkish
|
||||
"lang_zh-CN": Chinese (Simplified)
|
||||
"lang_zh-TW": Chinese (Traditional)
|
||||
|
||||
orTerms string Provides additional search terms to check for in a document, where each document in the search results must contain at least one of the additional search terms.
|
||||
relatedSite string Specifies that all search results should be pages that are related to the specified URL.
|
||||
rights string Filters based on licensing. Supported values include: cc_publicdomain, cc_attribute, cc_sharealike, cc_noncommercial, cc_nonderived, and combinations of these.
|
||||
safe string Search safety level.
|
||||
|
||||
Acceptable values are:
|
||||
"high": Enables highest level of SafeSearch filtering.
|
||||
"medium": Enables moderate SafeSearch filtering.
|
||||
"off": Disables SafeSearch filtering. (default)
|
||||
searchType string Specifies the search type: image. If unspecified, results are limited to webpages.
|
||||
|
||||
Acceptable values are:
|
||||
"image": custom image search.
|
||||
siteSearch string Specifies all search results should be pages from a given site.
|
||||
siteSearchFilter string Controls whether to include or exclude results from the site named in the siteSearch parameter.
|
||||
|
||||
Acceptable values are:
|
||||
"e": exclude
|
||||
"i": include
|
||||
sort string The sort expression to apply to the results.
|
||||
]"
|
||||
date: "$Date: 2015-10-08 07:51:29 -0300 (ju., 08 oct. 2015) $"
|
||||
revision: "$Revision: 97966 $"
|
||||
EIS: "GCSE parameters", "src=https://developers.google.com/custom-search/json-api/v1/reference/cse/list", "protocol=URI"
|
||||
|
||||
class
|
||||
GCSE_QUERY_PARAMETERS
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_secret_key, a_cx, a_query: READABLE_STRING_8)
|
||||
-- Create an object GCSE_QUERY_PARAMETERS with secret key `a_secret_key' and a custom search engine id `a_cx'.
|
||||
-- and query `a_query'.
|
||||
do
|
||||
-- TODO
|
||||
-- At the moment the API only use cx as Google Custom Search id.
|
||||
-- Custom search engine ID - Use either cx or cref to specify the custom search engine you want to use to perform this search
|
||||
|
||||
secret := a_secret_key
|
||||
cx := a_cx
|
||||
query := a_query
|
||||
ensure
|
||||
secret_set: secret.same_string (a_secret_key)
|
||||
cx_set: cx.same_string (a_cx)
|
||||
query_set: query.same_string (a_query)
|
||||
end
|
||||
|
||||
feature -- Access : Required Parameters
|
||||
|
||||
secret: READABLE_STRING_8
|
||||
-- Required. The shared key between your site and Google Custom Search Engine.
|
||||
|
||||
cx: READABLE_STRING_8
|
||||
-- Custom search engine id to perform this search.
|
||||
|
||||
query: READABLE_STRING_8
|
||||
-- Search query, query parameter to specify your search expression.
|
||||
|
||||
feature -- Optional Parameters
|
||||
|
||||
|
||||
|
||||
num : detachable STRING_8
|
||||
-- Number of search results to return.
|
||||
-- Valid values are integers between 1 and 10, inclusive.
|
||||
|
||||
start: detachable STRING_8
|
||||
-- The index of the first result to return.
|
||||
|
||||
|
||||
feature -- Change Elements
|
||||
|
||||
set_num (a_num: READABLE_STRING_8)
|
||||
require
|
||||
is_number: a_num.is_integer
|
||||
valid_range: a_num.to_integer >= 1 and then a_num.to_integer <= 10
|
||||
do
|
||||
num := a_num
|
||||
ensure
|
||||
num_set: num = a_num
|
||||
valid_rage_set: attached num as l_num and then l_num.to_integer >= 1 and then l_num.to_integer <= 10
|
||||
end
|
||||
|
||||
|
||||
set_start (a_start: READABLE_STRING_8)
|
||||
require
|
||||
is_number: a_start.is_integer
|
||||
valid_start: a_start.to_integer >= 1
|
||||
do
|
||||
start := a_start
|
||||
ensure
|
||||
start_set: start = a_start
|
||||
valid_start_set: attached start as l_start and then l_start.to_integer >= 1
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2015 Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
115
library/gcse/src/gcse_response.e
Normal file
115
library/gcse/src/gcse_response.e
Normal file
@@ -0,0 +1,115 @@
|
||||
note
|
||||
description: "[
|
||||
Represent search request metadata
|
||||
URL: search template used for the current results.
|
||||
Queries: current, next and previous page.
|
||||
Context
|
||||
Search infromation
|
||||
Items: array of actual search results.
|
||||
]"
|
||||
date: "$Date: 2015-10-08 07:51:29 -0300 (ju., 08 oct. 2015) $"
|
||||
revision: "$Revision: 97966 $"
|
||||
|
||||
class
|
||||
GCSE_RESPONSE
|
||||
|
||||
--! TODO
|
||||
--! All suppport for for url, context and search information.
|
||||
|
||||
feature -- Access
|
||||
|
||||
current_page: detachable GCSE_PAGE
|
||||
-- Metadata describing the query for the current set of results.
|
||||
-- This role is always present in the response.
|
||||
-- It is always an array with just one element.
|
||||
|
||||
next_page: detachable GCSE_PAGE
|
||||
-- Metadata describing the query to use for the next page of results.
|
||||
-- This role is not present if the current results are the last page. Note: This API returns up to the first 100 results only.
|
||||
-- When present, it is always a array with just one element.
|
||||
|
||||
previous_page: detachable GCSE_PAGE
|
||||
-- Metadata describing the query to use for the previous page of results.
|
||||
-- Not present if the current results are the first page.
|
||||
-- When present, it is always a array with just one element.
|
||||
|
||||
items: detachable LIST [GCSE_PAGE_ITEM]
|
||||
-- Contains the actual search results. The search results include the URL, title and text snippets that describe the result.
|
||||
|
||||
feature -- Change Element
|
||||
|
||||
set_current_page (a_page: GCSE_PAGE)
|
||||
-- Set `current_page' with `a_page'.
|
||||
do
|
||||
current_page := a_page
|
||||
ensure
|
||||
current_page_set: current_page = a_page
|
||||
end
|
||||
|
||||
set_next_page (a_page: GCSE_PAGE)
|
||||
-- Set `next_page' with `a_page'.
|
||||
do
|
||||
next_page := a_page
|
||||
ensure
|
||||
next_page_set: next_page = a_page
|
||||
end
|
||||
|
||||
set_previous_page (a_page: GCSE_PAGE)
|
||||
-- Set `previous_page' with `a_page'.
|
||||
do
|
||||
previous_page := a_page
|
||||
ensure
|
||||
previous_page_set: previous_page = a_page
|
||||
end
|
||||
|
||||
add_item (a_item: GCSE_PAGE_ITEM)
|
||||
-- Add item `a_item' to the list of items.
|
||||
local
|
||||
l_items: like items
|
||||
do
|
||||
l_items := items
|
||||
if l_items = Void then
|
||||
create {ARRAYED_LIST[GCSE_PAGE_ITEM]}l_items.make (10)
|
||||
items := l_items
|
||||
end
|
||||
l_items.force (a_item)
|
||||
end
|
||||
|
||||
|
||||
feature -- Acess: HTTP Response
|
||||
|
||||
status: INTEGER
|
||||
-- HTTP status code.
|
||||
|
||||
status_message: detachable READABLE_STRING_8
|
||||
-- associated textual phrase for the response status.
|
||||
|
||||
feature -- Change Element: HTTP Response
|
||||
|
||||
set_status (a_status: like status)
|
||||
-- Set `status' with `a_status'.
|
||||
do
|
||||
status := a_status
|
||||
ensure
|
||||
status_set: status = a_status
|
||||
end
|
||||
|
||||
set_status_nessage (a_message: like status_message)
|
||||
-- Set `status_message' with `a_message'.
|
||||
do
|
||||
status_message := a_message
|
||||
ensure
|
||||
status_message_set: status_message = a_message
|
||||
end
|
||||
|
||||
;note
|
||||
copyright: "2011-2015 Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
80
library/gcse/test/application.e
Normal file
80
library/gcse/test/application.e
Normal file
@@ -0,0 +1,80 @@
|
||||
note
|
||||
description : "test application root class"
|
||||
date : "$Date: 2015-12-02 10:27:38 -0300 (mi. 02 de dic. de 2015) $"
|
||||
revision : "$Revision: 98180 $"
|
||||
|
||||
class
|
||||
APPLICATION
|
||||
|
||||
inherit
|
||||
ARGUMENTS
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
-- Run application.
|
||||
local
|
||||
gcse: GCSE_API
|
||||
l_parameters: GCSE_QUERY_PARAMETERS
|
||||
do
|
||||
create l_parameters.make (key, cx, "scoop")
|
||||
create gcse.make (l_parameters)
|
||||
gcse.search
|
||||
|
||||
if attached {GCSE_RESPONSE} gcse.last_result as l_result then
|
||||
if attached l_result.current_page as l_page then
|
||||
print ("Current Page%N")
|
||||
print (l_page.debug_output)
|
||||
end
|
||||
if attached l_result.next_page as l_page then
|
||||
print ("Next Page%N")
|
||||
print (l_page.debug_output)
|
||||
end
|
||||
if attached l_result.previous_page as l_page then
|
||||
print ("Previous Page%N")
|
||||
print (l_page.debug_output)
|
||||
end
|
||||
|
||||
if attached l_result.items as l_items then
|
||||
print ("Number of items:" + l_items.count.out)
|
||||
across l_items as ic loop print (ic.item.debug_output) end
|
||||
end
|
||||
|
||||
if attached l_result.next_page as l_page then
|
||||
l_parameters.set_start (l_page.start_index.out)
|
||||
gcse.search
|
||||
end
|
||||
end
|
||||
|
||||
if attached {GCSE_RESPONSE} gcse.last_result as l_result then
|
||||
if attached l_result.current_page as l_page then
|
||||
print ("Current Page%N")
|
||||
print (l_page.debug_output)
|
||||
end
|
||||
if attached l_result.next_page as l_page then
|
||||
print ("Next Page%N")
|
||||
print (l_page.debug_output)
|
||||
end
|
||||
if attached l_result.previous_page as l_page then
|
||||
print ("Previous Page%N")
|
||||
print (l_page.debug_output)
|
||||
end
|
||||
|
||||
if attached l_result.items as l_items then
|
||||
print ("Number of items:" + l_items.count.out)
|
||||
across l_items as ic loop print (ic.item.debug_output) end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
Key: STRING = "AIzaSyBKAXNofo-RqZb6kUmpbiCwPEy7n7-E51k"
|
||||
cx : STRING = "015017565055626880074:9gdgp1fvt-g"
|
||||
end
|
||||
31
library/gcse/test/gcse_api_test_set.e
Normal file
31
library/gcse/test/gcse_api_test_set.e
Normal file
@@ -0,0 +1,31 @@
|
||||
note
|
||||
description: "[
|
||||
Eiffel tests that can be executed by testing tool.
|
||||
]"
|
||||
author: "EiffelStudio test wizard"
|
||||
date: "$Date: 2015-10-08 07:51:29 -0300 (ju., 08 oct. 2015) $"
|
||||
revision: "$Revision: 97966 $"
|
||||
testing: "type/manual"
|
||||
|
||||
class
|
||||
GCSE_API_TEST_SET
|
||||
|
||||
inherit
|
||||
EQA_TEST_SET
|
||||
|
||||
feature -- Test routines
|
||||
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
has_error (l_captcha: GCSE_API; a_error: READABLE_STRING_32): BOOLEAN
|
||||
do
|
||||
if attached l_captcha.errors as l_errors then
|
||||
l_errors.compare_objects
|
||||
Result := l_errors.has (a_error)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
20
library/gcse/test/test.ecf
Normal file
20
library/gcse/test/test.ecf
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-13-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-13-0 http://www.eiffel.com/developers/xml/configuration-1-13-0.xsd" name="test">
|
||||
<target name="test">
|
||||
<root class="APPLICATION" feature="make"/>
|
||||
<option warning="true" void_safety="transitional">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="gcse" location="..\gcse-safe.ecf" readonly="false"/>
|
||||
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
|
||||
<cluster name="test" location=".\" recursive="true">
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
26
library/http_client_extension/http_client_extension-safe.ecf
Normal file
26
library/http_client_extension/http_client_extension-safe.ecf
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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="http_client_extension" uuid="EA6A381D-2E78-448C-8A6D-B71759F1082E" library_target="http_client_extension">
|
||||
<target name="http_client_extension">
|
||||
<root all_classes="true"/>
|
||||
<option warning="true" void_safety="all">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension-safe.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL-safe.ecf"/>
|
||||
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder-safe.ecf"/>
|
||||
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="http_client" location="$ISE_LIBRARY\contrib\library\network\http_client\http_client-safe.ecf"/>
|
||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf" readonly="false"/>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||
<cluster name="http_client_extension" location=".\src\" recursive="true">
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
25
library/http_client_extension/http_client_extension.ecf
Normal file
25
library/http_client_extension/http_client_extension.ecf
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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="http_client_extension" uuid="DD90A4FA-1B7F-4C8C-A739-AE67D6F40384" library_target="http_client_extension">
|
||||
<target name="http_client_extension">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" void_safety="none">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf"/>
|
||||
<library name="encoder" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\text\encoder\encoder.ecf"/>
|
||||
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http.ecf"/>
|
||||
<library name="http_client" location="$ISE_LIBRARY\contrib\library\network\http_client\http_client.ecf"/>
|
||||
<library name="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json.ecf" readonly="false"/>
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
||||
<cluster name="http_client_extension" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
199
library/http_client_extension/src/request/request.e
Normal file
199
library/http_client_extension/src/request/request.e
Normal file
@@ -0,0 +1,199 @@
|
||||
note
|
||||
description: "Represent an HTTP request."
|
||||
date: "$Date: 2015-10-08 07:51:29 -0300 (ju., 08 oct. 2015) $"
|
||||
revision: "$Revision: 97966 $"
|
||||
|
||||
class
|
||||
REQUEST
|
||||
|
||||
inherit
|
||||
|
||||
HTTP_CONSTANTS
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_method: READABLE_STRING_8; a_uri: READABLE_STRING_8)
|
||||
require
|
||||
valid_http_method: is_http_method (a_method)
|
||||
valid_uri: is_valid_uri (a_uri)
|
||||
do
|
||||
verb := a_method
|
||||
uri := a_uri
|
||||
create headers.make (5)
|
||||
ensure
|
||||
ver_set: verb = a_method
|
||||
uri_set: uri = a_uri
|
||||
end
|
||||
|
||||
feature -- Status Report
|
||||
|
||||
is_valid_uri (a_uri: READABLE_STRING_8): BOOLEAN
|
||||
local
|
||||
l_uri: URI
|
||||
do
|
||||
create l_uri.make_from_string (a_uri)
|
||||
Result := l_uri.is_valid
|
||||
end
|
||||
|
||||
query_string: detachable READABLE_STRING_8
|
||||
local
|
||||
l_uri: URI
|
||||
do
|
||||
create l_uri.make_from_string (uri)
|
||||
Result := l_uri.query
|
||||
end
|
||||
|
||||
sanitized_url: READABLE_STRING_8
|
||||
-- Returns the URL without the query string part
|
||||
local
|
||||
l_uri: URI
|
||||
do
|
||||
create l_uri.make_from_string (uri)
|
||||
l_uri.remove_query
|
||||
Result := l_uri.string
|
||||
ensure
|
||||
sanitized: not as_uri (Result).has_query
|
||||
end
|
||||
|
||||
is_http_method (a_method: READABLE_STRING_GENERAL): BOOLEAN
|
||||
do
|
||||
if a_method.same_string (method_connect) then
|
||||
Result := True
|
||||
elseif a_method.same_string (method_delete) then
|
||||
Result := True
|
||||
elseif a_method.same_string (method_get) then
|
||||
Result := True
|
||||
elseif a_method.same_string (method_head) then
|
||||
Result := True
|
||||
elseif a_method.same_string (method_options) then
|
||||
Result := True
|
||||
elseif a_method.same_string (method_patch) then
|
||||
Result := True
|
||||
elseif a_method.same_string (method_post) then
|
||||
Result := True
|
||||
elseif a_method.same_string (method_put) then
|
||||
Result := True
|
||||
elseif a_method.same_string (method_trace) then
|
||||
Result := True
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Constants
|
||||
|
||||
content_type_header_name: STRING_8 = "Content-Type";
|
||||
|
||||
default_content_type: STRING
|
||||
once
|
||||
Result := application_json
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
uri: READABLE_STRING_8
|
||||
|
||||
verb: READABLE_STRING_8
|
||||
|
||||
headers: STRING_TABLE [READABLE_STRING_8]
|
||||
|
||||
payload: detachable READABLE_STRING_8
|
||||
|
||||
executor: detachable REQUEST_EXECUTOR
|
||||
|
||||
feature -- Change Element
|
||||
|
||||
add_payload (a_payload: like payload)
|
||||
do
|
||||
payload := a_payload
|
||||
ensure
|
||||
payload_set: attached payload as l_payload implies l_payload = a_payload
|
||||
end
|
||||
|
||||
add_header (key: READABLE_STRING_8; value: READABLE_STRING_8)
|
||||
do
|
||||
headers.force (value, key)
|
||||
end
|
||||
|
||||
feature -- Execute
|
||||
|
||||
execute: detachable RESPONSE
|
||||
do
|
||||
initialize_executor
|
||||
Result := execute_request
|
||||
end
|
||||
|
||||
initialize_executor
|
||||
do
|
||||
create executor.make (uri, verb)
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
execute_request: detachable RESPONSE
|
||||
do
|
||||
if attached executor as l_executor then
|
||||
-- add headers
|
||||
add_headers (l_executor)
|
||||
if verb.same_string (method_put) or else verb.same_string (method_post) or else verb.same_string (method_patch) then
|
||||
l_executor.set_body (body_contents)
|
||||
end
|
||||
if not l_executor.context_executor.headers.has (content_type_header_name) then
|
||||
l_executor.context_executor.add_header (content_type_header_name, default_content_type)
|
||||
end
|
||||
if attached l_executor.execute as l_response then
|
||||
create Result.make (l_response)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
add_headers (a_executor: REQUEST_EXECUTOR)
|
||||
local
|
||||
l_context_executor: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
s: READABLE_STRING_GENERAL
|
||||
utf: UTF_CONVERTER
|
||||
do
|
||||
l_context_executor := a_executor.context_executor
|
||||
across
|
||||
headers as ic
|
||||
loop
|
||||
s := ic.key
|
||||
if s.is_valid_as_string_8 then
|
||||
l_context_executor.add_header (s.as_string_8, ic.item)
|
||||
else
|
||||
l_context_executor.add_header (utf.utf_32_string_to_utf_8_string_8 (s), ic.item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
body_contents: READABLE_STRING_8
|
||||
do
|
||||
if attached payload as l_payload then
|
||||
Result := l_payload
|
||||
else
|
||||
Result := ""
|
||||
end
|
||||
end
|
||||
|
||||
as_uri (a_string: READABLE_STRING_8): URI
|
||||
require
|
||||
is_valid_uri: is_valid_uri (a_string)
|
||||
do
|
||||
create Result.make_from_string (a_string)
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2015 Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
|
||||
end
|
||||
97
library/http_client_extension/src/request/request_executor.e
Normal file
97
library/http_client_extension/src/request/request_executor.e
Normal file
@@ -0,0 +1,97 @@
|
||||
note
|
||||
description: "Executes an HTTP request"
|
||||
date: "$Date: 2015-10-08 07:51:29 -0300 (ju., 08 oct. 2015) $"
|
||||
revision: "$Revision: 97966 $"
|
||||
|
||||
class
|
||||
REQUEST_EXECUTOR
|
||||
|
||||
inherit
|
||||
|
||||
HTTP_CLIENT_HELPER
|
||||
|
||||
HTTP_CONSTANTS
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_url: READABLE_STRING_8; a_method: READABLE_STRING_8)
|
||||
do
|
||||
set_base_url (a_url)
|
||||
verb := a_method
|
||||
ensure
|
||||
base_url_set: base_url.same_string (a_url)
|
||||
method_set: verb.same_string (a_method)
|
||||
end
|
||||
|
||||
set_base_url (a_url: READABLE_STRING_8)
|
||||
-- Set base_url with `a_url'
|
||||
local
|
||||
s: STRING
|
||||
do
|
||||
create s.make_from_string (a_url)
|
||||
s.left_adjust
|
||||
s.right_adjust
|
||||
base_url := s
|
||||
ensure
|
||||
base_url_set: a_url.has_substring (base_url)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
verb: READABLE_STRING_8
|
||||
-- HTTP METHOD (Get, Post, ...)
|
||||
|
||||
body: detachable READABLE_STRING_8
|
||||
-- body content
|
||||
|
||||
feature -- Element Change
|
||||
|
||||
set_body (a_body: like body)
|
||||
-- Set body with `a_body'.
|
||||
do
|
||||
body := a_body
|
||||
ensure
|
||||
body_set: body = a_body
|
||||
end
|
||||
|
||||
feature -- Execute
|
||||
|
||||
execute: detachable HTTP_CLIENT_RESPONSE
|
||||
-- Http executor
|
||||
do
|
||||
if verb.same_string (method_connect) then
|
||||
Result := Void -- not supported for now
|
||||
elseif verb.same_string (method_delete) then
|
||||
Result := execute_delete ("")
|
||||
elseif verb.same_string (method_get) then
|
||||
Result := execute_get ("")
|
||||
elseif verb.same_string (method_head) then
|
||||
Result := Void
|
||||
elseif verb.same_string (method_options) then
|
||||
Result := Void
|
||||
elseif verb.same_string (method_patch) then
|
||||
Result := execute_patch ("", body)
|
||||
elseif verb.same_string (method_post) then
|
||||
Result := execute_post ("", body)
|
||||
elseif verb.same_string (method_put) then
|
||||
Result := execute_put ("", body)
|
||||
elseif verb.same_string (method_trace) then
|
||||
Result := Void
|
||||
end
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2015 Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
|
||||
end
|
||||
57
library/http_client_extension/src/response/response.e
Normal file
57
library/http_client_extension/src/response/response.e
Normal file
@@ -0,0 +1,57 @@
|
||||
note
|
||||
description: "Represent and HTTP Response"
|
||||
date: "$Date: 2015-10-08 07:51:29 -0300 (ju., 08 oct. 2015) $"
|
||||
revision: "$Revision: 97966 $"
|
||||
|
||||
class
|
||||
RESPONSE
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} --Initialization
|
||||
|
||||
make (a_response: HTTP_CLIENT_RESPONSE)
|
||||
do
|
||||
http_response := a_response
|
||||
body := a_response.body
|
||||
status := a_response.status
|
||||
headers := a_response.headers
|
||||
status_message := a_response.status_line
|
||||
error_message := a_response.error_message
|
||||
ensure
|
||||
http_reponse_set: http_response = a_response
|
||||
headers_set: headers = a_response.headers
|
||||
status_set: status = a_response.status
|
||||
status_message_set: status_message = a_response.status_line
|
||||
error_message_set: error_message = a_response.error_message
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
status: INTEGER
|
||||
|
||||
status_message: detachable READABLE_STRING_8
|
||||
|
||||
error_message: detachable READABLE_STRING_8
|
||||
|
||||
body: detachable READABLE_STRING_8
|
||||
|
||||
headers: LIST [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
http_response: HTTP_CLIENT_RESPONSE;
|
||||
|
||||
note
|
||||
copyright: "2011-2015 Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
|
||||
end
|
||||
99
library/http_client_extension/src/util/http_client_helper.e
Normal file
99
library/http_client_extension/src/util/http_client_helper.e
Normal file
@@ -0,0 +1,99 @@
|
||||
note
|
||||
description: "Wrapper class for HTTP_CLIENT_SESSION"
|
||||
date: "$Date: 2015-10-08 07:51:29 -0300 (ju., 08 oct. 2015) $"
|
||||
revision: "$Revision: 97966 $"
|
||||
|
||||
deferred class
|
||||
HTTP_CLIENT_HELPER
|
||||
|
||||
feature -- Access
|
||||
|
||||
http_session: detachable HTTP_CLIENT_SESSION
|
||||
|
||||
get_http_session
|
||||
local
|
||||
h: LIBCURL_HTTP_CLIENT
|
||||
b: like base_url
|
||||
do
|
||||
create h.make
|
||||
b := base_url
|
||||
if b = Void then
|
||||
b := ""
|
||||
end
|
||||
if attached {HTTP_CLIENT_SESSION} h.new_session (base_url) as sess then
|
||||
http_session := sess
|
||||
sess.set_timeout (-1)
|
||||
sess.set_connect_timeout (-1)
|
||||
sess.set_is_insecure (True)
|
||||
sess.set_any_auth_type
|
||||
debug ("curl")
|
||||
sess.set_is_debug (True)
|
||||
end
|
||||
debug ("proxy8888")
|
||||
sess.set_proxy ("127.0.0.1", 8888) --| inspect traffic with http://www.fiddler2.com/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- HTTP client helpers
|
||||
|
||||
execute_get (command_name: READABLE_STRING_8): detachable HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
get_http_session
|
||||
if attached http_session as sess then
|
||||
Result := sess.get (command_name, context_executor)
|
||||
end
|
||||
end
|
||||
|
||||
execute_post (command_name: READABLE_STRING_8; data: detachable READABLE_STRING_8): detachable HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
get_http_session
|
||||
if attached http_session as sess then
|
||||
Result := sess.post (command_name, context_executor, data)
|
||||
end
|
||||
end
|
||||
|
||||
execute_delete (command_name: READABLE_STRING_8): detachable HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
get_http_session
|
||||
if attached http_session as sess then
|
||||
Result := sess.delete (command_name, context_executor)
|
||||
end
|
||||
end
|
||||
|
||||
execute_put (command_name: READABLE_STRING_8; data: detachable READABLE_STRING_8): detachable HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
get_http_session
|
||||
if attached http_session as sess then
|
||||
Result := sess.put (command_name, context_executor, data)
|
||||
end
|
||||
end
|
||||
|
||||
execute_patch (command_name: READABLE_STRING_8; data: detachable READABLE_STRING_8): detachable HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
get_http_session
|
||||
if attached http_session as sess then
|
||||
Result := sess.patch (command_name, context_executor, data)
|
||||
end
|
||||
end
|
||||
|
||||
context_executor: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
-- request context for each request
|
||||
once
|
||||
create Result.make
|
||||
end
|
||||
|
||||
base_url: STRING;
|
||||
|
||||
note
|
||||
copyright: "2011-2015 Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
|
||||
end
|
||||
@@ -86,8 +86,23 @@ feature -- Query
|
||||
sql_post_execution
|
||||
end
|
||||
|
||||
sql_change (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
|
||||
-- Execute an sql query change `a_sql_statement' with the params `a_params'.
|
||||
sql_finalize
|
||||
-- <Precursor>
|
||||
do
|
||||
-- N/A
|
||||
end
|
||||
|
||||
sql_insert (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
|
||||
-- <Precursor>
|
||||
do
|
||||
check_sql_query_validity (a_sql_statement, a_params)
|
||||
db_handler.set_query (create {DATABASE_QUERY}.data_reader (a_sql_statement, a_params))
|
||||
db_handler.execute_change
|
||||
sql_post_execution
|
||||
end
|
||||
|
||||
sql_modify (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
|
||||
-- <Precursor>
|
||||
do
|
||||
check_sql_query_validity (a_sql_statement, a_params)
|
||||
db_handler.set_query (create {DATABASE_QUERY}.data_reader (a_sql_statement, a_params))
|
||||
@@ -133,4 +148,32 @@ feature -- Query
|
||||
end
|
||||
end
|
||||
|
||||
sql_read_integer_32 (a_index: INTEGER): INTEGER_32
|
||||
-- Retrieved value at `a_index' position in `item'.
|
||||
local
|
||||
l_item: like sql_item
|
||||
do
|
||||
l_item := sql_item (a_index)
|
||||
if attached {INTEGER_32} l_item as i then
|
||||
Result := i
|
||||
elseif attached {INTEGER_32_REF} l_item as l_value then
|
||||
Result := l_value.item
|
||||
else
|
||||
check is_integer_32: False end
|
||||
end
|
||||
end
|
||||
|
||||
sql_read_date_time (a_index: INTEGER): detachable DATE_TIME
|
||||
-- Retrieved value at `a_index' position in `item'.
|
||||
local
|
||||
l_item: like sql_item
|
||||
do
|
||||
l_item := sql_item (a_index)
|
||||
if attached {DATE_TIME} l_item as dt then
|
||||
Result := dt
|
||||
else
|
||||
check is_date_time_or_null: l_item = Void end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
34
library/persistence/sqlite3/sqlite3-safe.ecf
Normal file
34
library/persistence/sqlite3/sqlite3-safe.ecf
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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="persistence_sqlite3" uuid="4E536C92-A09F-4305-8230-2EC5ABC51416" library_target="persistence_sqlite3">
|
||||
<target name="persistence_sqlite3">
|
||||
<root all_classes="true"/>
|
||||
<option warning="true" void_safety="all">
|
||||
<assertions precondition="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<library name="app_env" location="..\..\app_env\app_env-safe.ecf"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="cms" location="..\..\..\cms-safe.ecf"/>
|
||||
<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="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf" readonly="false"/>
|
||||
<library name="logging" location="$ISE_LIBRARY\library\runtime\logging\logging-safe.ecf"/>
|
||||
<library name="model" location="..\..\model\cms_model-safe.ecf"/>
|
||||
<library name="sqlite3" location="$ISE_LIBRARY\unstable\library\persistency\database\sqlite3\sqlite-safe.ecf" readonly="false">
|
||||
<option msil_application_optimize="false">
|
||||
<assertions precondition="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
</library>
|
||||
<library name="thread" location="$ISE_LIBRARY\library\thread\thread-safe.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<cluster name="persistence_sqlite" location=".\src\" recursive="true">
|
||||
<file_rule>
|
||||
<exclude>/old$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
450
library/persistence/sqlite3/src/cms_storage_sqlite3.e
Normal file
450
library/persistence/sqlite3/src/cms_storage_sqlite3.e
Normal file
@@ -0,0 +1,450 @@
|
||||
note
|
||||
description: "Summary description for {CMS_STORAGE_MYSQL}."
|
||||
date: "$Date: 2015-02-09 22:29:56 +0100 (lun., 09 févr. 2015) $"
|
||||
revision: "$Revision: 96596 $"
|
||||
|
||||
class
|
||||
CMS_STORAGE_SQLITE3
|
||||
|
||||
inherit
|
||||
CMS_STORAGE_SQL
|
||||
redefine
|
||||
sql_read_date_time, sql_read_integer_32,
|
||||
sql_read_string_32
|
||||
end
|
||||
|
||||
CMS_CORE_STORAGE_SQL_I
|
||||
redefine
|
||||
sql_read_date_time, sql_read_integer_32,
|
||||
sql_read_string_32
|
||||
end
|
||||
|
||||
CMS_USER_STORAGE_SQL_I
|
||||
redefine
|
||||
sql_read_date_time, sql_read_integer_32,
|
||||
sql_read_string_32
|
||||
end
|
||||
|
||||
SQLITE_BIND_ARG_MARSHALLER
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (db: SQLITE_DATABASE)
|
||||
do
|
||||
sqlite := db
|
||||
create error_handler.make
|
||||
end
|
||||
|
||||
sqlite: SQLITE_DATABASE
|
||||
-- Associated SQLite database.
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_initialized: BOOLEAN
|
||||
-- Is storage initialized?
|
||||
do
|
||||
Result := has_user
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_available: BOOLEAN
|
||||
-- Is storage available?
|
||||
do
|
||||
Result := sqlite.is_interface_usable
|
||||
end
|
||||
|
||||
feature -- Basic operation
|
||||
|
||||
close
|
||||
-- Close/disconnect current storage.
|
||||
do
|
||||
sqlite.close
|
||||
end
|
||||
|
||||
feature -- Execution
|
||||
|
||||
transaction_depth: INTEGER
|
||||
|
||||
sql_begin_transaction
|
||||
-- Start a database transtaction.
|
||||
do
|
||||
if transaction_depth = 0 then
|
||||
sqlite.begin_transaction (False)
|
||||
end
|
||||
transaction_depth := transaction_depth + 1
|
||||
debug ("roc_storage")
|
||||
print ("# sql_begin_transaction (depth="+ transaction_depth.out +").%N")
|
||||
end
|
||||
end
|
||||
|
||||
sql_rollback_transaction
|
||||
-- Rollback updates in the database.
|
||||
do
|
||||
if sqlite.is_in_transaction then
|
||||
sqlite.rollback
|
||||
end
|
||||
transaction_depth := transaction_depth - 1
|
||||
debug ("roc_storage")
|
||||
print ("# sql_rollback_transaction (depth="+ transaction_depth.out +").%N")
|
||||
end
|
||||
end
|
||||
|
||||
sql_commit_transaction
|
||||
-- Commit updates in the database.
|
||||
do
|
||||
if sqlite.is_in_transaction then
|
||||
sqlite.commit
|
||||
end
|
||||
transaction_depth := transaction_depth - 1
|
||||
debug ("roc_storage")
|
||||
print ("# sql_commit_transaction (depth="+ transaction_depth.out +").%N")
|
||||
end
|
||||
end
|
||||
|
||||
sql_post_execution
|
||||
-- Post database execution.
|
||||
-- note: execute after each `sql_query' and `sql_change'.
|
||||
do
|
||||
debug ("roc_storage")
|
||||
print ("# sql_post_execution.%N")
|
||||
end
|
||||
-- FIXME
|
||||
if sqlite.has_error then
|
||||
write_critical_log (generator + ".post_execution Error occurred!")
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Operation
|
||||
|
||||
last_statement: detachable SQLITE_STATEMENT
|
||||
|
||||
last_sqlite_result_cursor: detachable SQLITE_STATEMENT_ITERATION_CURSOR
|
||||
|
||||
sql_query (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
|
||||
-- <Precursor>
|
||||
local
|
||||
st: SQLITE_QUERY_STATEMENT
|
||||
do
|
||||
debug ("roc_storage")
|
||||
print ("> sql_query (" +a_sql_statement + ").%N")
|
||||
end
|
||||
last_sqlite_result_cursor := Void
|
||||
create st.make (a_sql_statement, sqlite)
|
||||
last_statement := st
|
||||
if st.is_compiled then
|
||||
if a_params /= Void then
|
||||
check st.has_arguments end
|
||||
last_sqlite_result_cursor := st.execute_new_with_arguments (sqlite_arguments (a_params))
|
||||
else
|
||||
last_sqlite_result_cursor := st.execute_new
|
||||
end
|
||||
else
|
||||
error_handler.add_custom_error (1, "invalid query", "query compilation failed!")
|
||||
end
|
||||
debug ("roc_storage")
|
||||
print ("< sql_query (" +a_sql_statement + ").%N")
|
||||
end
|
||||
end
|
||||
|
||||
sql_finalize
|
||||
-- Finalize sql query (i.e destroy previous query statement.
|
||||
do
|
||||
debug ("roc_storage")
|
||||
print ("> sql_finalize.%N")
|
||||
end
|
||||
if attached last_statement as st then
|
||||
st.cleanup
|
||||
end
|
||||
if attached last_sqlite_result_cursor as cur then
|
||||
if cur.statement /= last_statement then
|
||||
check should_not_occurs: False end
|
||||
cur.statement.cleanup
|
||||
end
|
||||
last_sqlite_result_cursor := Void
|
||||
end
|
||||
last_statement := Void
|
||||
debug ("roc_storage")
|
||||
print ("< sql_finalize.%N")
|
||||
end
|
||||
end
|
||||
|
||||
sql_insert (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
|
||||
-- <Precursor>
|
||||
local
|
||||
st: SQLITE_INSERT_STATEMENT
|
||||
do
|
||||
debug ("roc_storage")
|
||||
print ("> sql_insert (" +a_sql_statement + ").%N")
|
||||
end
|
||||
last_sqlite_result_cursor := Void
|
||||
create st.make (a_sql_statement, sqlite)
|
||||
last_statement := st
|
||||
if st.is_compiled then
|
||||
if a_params /= Void then
|
||||
check st.has_arguments end
|
||||
last_sqlite_result_cursor := st.execute_new_with_arguments (sqlite_arguments (a_params))
|
||||
else
|
||||
last_sqlite_result_cursor := st.execute_new
|
||||
end
|
||||
else
|
||||
error_handler.add_custom_error (1, "invalid query", "query compilation failed!")
|
||||
end
|
||||
debug ("roc_storage")
|
||||
print ("< sql_insert (" +a_sql_statement + ").%N")
|
||||
end
|
||||
end
|
||||
|
||||
sql_modify (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
|
||||
-- <Precursor>
|
||||
local
|
||||
st: SQLITE_MODIFY_STATEMENT
|
||||
do
|
||||
debug ("roc_storage")
|
||||
print ("> sql_modify (" +a_sql_statement + ").%N")
|
||||
end
|
||||
last_sqlite_result_cursor := Void
|
||||
create st.make (a_sql_statement, sqlite)
|
||||
last_statement := st
|
||||
if st.is_compiled then
|
||||
if a_params /= Void then
|
||||
check st.has_arguments end
|
||||
last_sqlite_result_cursor := st.execute_new_with_arguments (sqlite_arguments (a_params))
|
||||
else
|
||||
last_sqlite_result_cursor := st.execute_new
|
||||
end
|
||||
else
|
||||
error_handler.add_custom_error (1, "invalid query", "query compilation failed!")
|
||||
end
|
||||
debug ("roc_storage")
|
||||
print ("< sql_modify (" +a_sql_statement + ").%N")
|
||||
end
|
||||
end
|
||||
|
||||
sqlite_arguments (a_params: STRING_TABLE [detachable ANY]): ARRAYED_LIST [SQLITE_BIND_ARG [ANY]]
|
||||
local
|
||||
k: READABLE_STRING_GENERAL
|
||||
k8: STRING
|
||||
utf: UTF_CONVERTER
|
||||
do
|
||||
create Result.make (a_params.count)
|
||||
across
|
||||
a_params as ic
|
||||
loop
|
||||
k := ic.key
|
||||
if k.is_valid_as_string_8 then
|
||||
k8 := k.as_string_8
|
||||
else
|
||||
k8 := utf.utf_32_string_to_utf_8_string_8 (k)
|
||||
end
|
||||
if attached {DATE_TIME} ic.item as dt then
|
||||
Result.force (new_binding_argument (date_time_to_string (dt), ":" + k8))
|
||||
elseif attached {READABLE_STRING_32} ic.item as s32 then
|
||||
Result.force (new_binding_argument (utf.utf_32_string_to_utf_8_string_8 (s32), ":" + k8))
|
||||
else
|
||||
Result.force (new_binding_argument (ic.item, ":" + k8))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
date_time_to_string (dt: DATE_TIME): STRING
|
||||
do
|
||||
create Result.make (16)
|
||||
Result.append_integer (dt.year)
|
||||
Result.append_character ('-')
|
||||
if dt.month <= 9 then
|
||||
Result.append_character ('0')
|
||||
end
|
||||
Result.append_integer (dt.month)
|
||||
Result.append_character ('-')
|
||||
if dt.day <= 9 then
|
||||
Result.append_character ('0')
|
||||
end
|
||||
Result.append_integer (dt.day)
|
||||
Result.append_character (' ')
|
||||
if dt.hour <= 9 then
|
||||
Result.append_character ('0')
|
||||
end
|
||||
Result.append_integer (dt.hour)
|
||||
Result.append_character (':')
|
||||
if dt.minute <= 9 then
|
||||
Result.append_character ('0')
|
||||
end
|
||||
Result.append_integer (dt.minute)
|
||||
Result.append_character (':')
|
||||
if dt.second <= 9 then
|
||||
Result.append_character ('0')
|
||||
end
|
||||
Result.append_integer (dt.second)
|
||||
end
|
||||
|
||||
string_to_date_time (a_string: READABLE_STRING_GENERAL): DATE_TIME
|
||||
local
|
||||
y,m,d: INTEGER
|
||||
h,min,sec: INTEGER
|
||||
s: detachable READABLE_STRING_GENERAL
|
||||
i,j: INTEGER
|
||||
do
|
||||
i := 1
|
||||
-- YYYY
|
||||
j := a_string.index_of ('-', i)
|
||||
s := a_string.substring (i, j - 1)
|
||||
y := s.to_integer
|
||||
i := j + 1
|
||||
-- /MM
|
||||
j := a_string.index_of ('-', i)
|
||||
s := a_string.substring (i, j - 1)
|
||||
m := s.to_integer
|
||||
i := j + 1
|
||||
-- /DD
|
||||
j := a_string.index_of (' ', i)
|
||||
s := a_string.substring (i, j - 1)
|
||||
d := s.to_integer
|
||||
i := j + 1
|
||||
-- %THour
|
||||
j := a_string.index_of (':', i)
|
||||
s := a_string.substring (i, j - 1)
|
||||
h := s.to_integer
|
||||
i := j + 1
|
||||
-- :Min
|
||||
j := a_string.index_of (':', i)
|
||||
s := a_string.substring (i, j - 1)
|
||||
min := s.to_integer
|
||||
i := j + 1
|
||||
-- :Sec
|
||||
j := a_string.count + 1
|
||||
s := a_string.substring (i, j - 1)
|
||||
sec := s.to_integer
|
||||
|
||||
create Result.make (y,m,d,h,min,sec)
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
sql_start
|
||||
-- <Precursor>.
|
||||
do
|
||||
-- sqlite cursor `last_sqlite_result_cursor', already at first position if any.
|
||||
end
|
||||
|
||||
sql_after: BOOLEAN
|
||||
-- <Precursor>.
|
||||
do
|
||||
if attached last_sqlite_result_cursor as l_cursor then
|
||||
Result := l_cursor.after
|
||||
end
|
||||
end
|
||||
|
||||
sql_forth
|
||||
-- <Precursor>.
|
||||
do
|
||||
if attached last_sqlite_result_cursor as l_cursor then
|
||||
l_cursor.forth
|
||||
end
|
||||
end
|
||||
|
||||
sql_valid_item_index (a_index: INTEGER): BOOLEAN
|
||||
local
|
||||
l_row: SQLITE_RESULT_ROW
|
||||
do
|
||||
if attached last_sqlite_result_cursor as l_cursor then
|
||||
l_row := l_cursor.item
|
||||
Result := a_index > 0 and a_index.to_natural_32 <= l_row.count
|
||||
end
|
||||
end
|
||||
|
||||
sql_item (a_index: INTEGER): detachable ANY
|
||||
local
|
||||
l_row: SQLITE_RESULT_ROW
|
||||
do
|
||||
if attached last_sqlite_result_cursor as l_cursor then
|
||||
l_row := l_cursor.item
|
||||
Result := l_row.value (a_index.to_natural_32)
|
||||
end
|
||||
end
|
||||
|
||||
sql_read_string_32 (a_index: INTEGER): detachable STRING_32
|
||||
-- <Precursor>
|
||||
local
|
||||
utf: UTF_CONVERTER
|
||||
do
|
||||
Result := Precursor (a_index)
|
||||
if Result = Void then
|
||||
if attached sql_read_string (a_index) as s8 then
|
||||
Result := utf.utf_8_string_8_to_string_32 (s8)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
sql_read_integer_32 (a_index: INTEGER): INTEGER_32
|
||||
-- Retrieved value at `a_index' position in `item'.
|
||||
local
|
||||
l_item: like sql_item
|
||||
i64: INTEGER_64
|
||||
do
|
||||
l_item := sql_item (a_index)
|
||||
if attached {INTEGER_32} l_item as i then
|
||||
Result := i
|
||||
elseif attached {INTEGER_32_REF} l_item as l_value then
|
||||
Result := l_value.item
|
||||
else
|
||||
if attached {INTEGER_64} l_item as i then
|
||||
i64 := i
|
||||
elseif attached {INTEGER_64_REF} l_item as l_value then
|
||||
i64 := l_value.item
|
||||
else
|
||||
check is_integer_32: False end
|
||||
end
|
||||
if i64 <= {INTEGER_32}.max_value then
|
||||
Result := i64.to_integer_32
|
||||
else
|
||||
check is_integer_32: False end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
sql_read_date_time (a_index: INTEGER): detachable DATE_TIME
|
||||
-- Retrieved value at `a_index' position in `item'.
|
||||
local
|
||||
l_item: like sql_item
|
||||
do
|
||||
l_item := sql_item (a_index)
|
||||
if attached {DATE_TIME} l_item as dt then
|
||||
Result := dt
|
||||
elseif attached {READABLE_STRING_GENERAL} l_item as s then
|
||||
Result := string_to_date_time (s)
|
||||
else
|
||||
check is_date_time_nor_null: l_item = Void end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
sql_statement (a_statement: STRING): STRING
|
||||
-- <Precursor>.
|
||||
local
|
||||
i: INTEGER
|
||||
do
|
||||
Result := a_statement
|
||||
from
|
||||
i := 1
|
||||
until
|
||||
i = 0
|
||||
loop
|
||||
i := a_statement.substring_index ("AUTO_INCREMENT", i)
|
||||
if i > 0 then
|
||||
if Result = a_statement then
|
||||
create Result.make_from_string (a_statement)
|
||||
end
|
||||
Result.remove (i + 4)
|
||||
i := i + 14
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,90 @@
|
||||
note
|
||||
description: "[
|
||||
Objects that ...
|
||||
]"
|
||||
author: "$Author: jfiat $"
|
||||
date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $"
|
||||
revision: "$Revision: 96616 $"
|
||||
|
||||
class
|
||||
CMS_STORAGE_SQLITE3_BUILDER
|
||||
|
||||
inherit
|
||||
CMS_STORAGE_SQL_BUILDER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
-- Initialize `Current'.
|
||||
do
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
storage (a_setup: CMS_SETUP; a_error_handler: ERROR_HANDLER): detachable CMS_STORAGE_SQLITE3
|
||||
local
|
||||
s: detachable READABLE_STRING_32
|
||||
p: PATH
|
||||
db: detachable SQLITE_DATABASE
|
||||
l_source: SQLITE_FILE_SOURCE
|
||||
i,j: INTEGER
|
||||
do
|
||||
if
|
||||
attached (create {APPLICATION_JSON_CONFIGURATION_HELPER}).new_database_configuration (a_setup.environment.application_config_path) as l_database_config
|
||||
then
|
||||
if l_database_config.driver.is_case_insensitive_equal ("sqlite3") then
|
||||
s := l_database_config.database_string
|
||||
i := s.substring_index ("Database=", 1)
|
||||
if i > 0 then
|
||||
i := s.index_of ('=', i) + 1
|
||||
j := s.index_of (';', i)
|
||||
if j = 0 then
|
||||
j := s.count + 1
|
||||
end
|
||||
create p.make_from_string (s.substring (i, j - 1))
|
||||
else
|
||||
create p.make_from_string (s)
|
||||
end
|
||||
|
||||
if attached reuseable_connection.item as d then
|
||||
if p.same_as (d.path) then
|
||||
db := d.database
|
||||
end
|
||||
end
|
||||
if db = Void or else db.is_closed then
|
||||
create l_source.make (p.name)
|
||||
create db.make (l_source)
|
||||
if l_source.exists then
|
||||
db.open_read_write
|
||||
else
|
||||
db.open_create_read_write
|
||||
end
|
||||
end
|
||||
if not db.is_closed then
|
||||
db.set_busy_timeout (1_000) -- FIXME
|
||||
create Result.make (db)
|
||||
-- set_map_zero_null_value (False) --| This way we map 0 to 0, instead of Null as default.
|
||||
if Result.is_available then
|
||||
if not Result.is_initialized then
|
||||
initialize (a_setup, Result)
|
||||
end
|
||||
end
|
||||
else
|
||||
a_error_handler.add_custom_error (0, "Could not connect to the ODBC storage", Void)
|
||||
end
|
||||
else
|
||||
-- Wrong mapping between storage name and storage builder!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
reuseable_connection: CELL [detachable TUPLE [path: PATH; database: SQLITE_DATABASE]]
|
||||
once
|
||||
create Result.put (Void)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
@@ -35,6 +35,7 @@ feature -- Factory
|
||||
if conn.is_connected then
|
||||
create Result.make (conn)
|
||||
set_map_zero_null_value (False) --| This way we map 0 to 0, instead of Null as default.
|
||||
set_use_extended_types (True) --| Use extended types: STRING_32 etc.
|
||||
if Result.is_available then
|
||||
if not Result.is_initialized then
|
||||
initialize (a_setup, Result)
|
||||
|
||||
@@ -48,6 +48,7 @@ feature -- Factory
|
||||
if conn.is_connected then
|
||||
create Result.make_with_driver (conn, l_database_config.item ("Driver"))
|
||||
set_map_zero_null_value (False) --| This way we map 0 to 0, instead of Null as default.
|
||||
set_use_extended_types (True) --| Use extended types: STRING_32 etc.
|
||||
if Result.is_available then
|
||||
if not Result.is_initialized then
|
||||
initialize (a_setup, Result)
|
||||
|
||||
@@ -47,18 +47,27 @@ feature -- Access: router
|
||||
configure_web (a_api: CMS_API; a_router: WSF_ROUTER)
|
||||
local
|
||||
l_admin_handler: CMS_ADMIN_HANDLER
|
||||
|
||||
l_modules_handler: CMS_ADMIN_MODULES_HANDLER
|
||||
l_users_handler: CMS_ADMIN_USERS_HANDLER
|
||||
l_roles_handler: CMS_ADMIN_ROLES_HANDLER
|
||||
|
||||
l_user_handler: CMS_USER_HANDLER
|
||||
l_role_handler: CMS_ROLE_HANDLER
|
||||
|
||||
l_admin_cache_handler: CMS_ADMIN_CACHE_HANDLER
|
||||
l_admin_export_handler: CMS_ADMIN_EXPORT_HANDLER
|
||||
|
||||
l_uri_mapping: WSF_URI_MAPPING
|
||||
do
|
||||
create l_admin_handler.make (a_api)
|
||||
create l_uri_mapping.make_trailing_slash_ignored ("/admin", l_admin_handler)
|
||||
a_router.map (l_uri_mapping, a_router.methods_get_post)
|
||||
|
||||
create l_modules_handler.make (a_api)
|
||||
create l_uri_mapping.make_trailing_slash_ignored ("/admin/modules", l_modules_handler)
|
||||
a_router.map (l_uri_mapping, a_router.methods_get_post)
|
||||
|
||||
create l_users_handler.make (a_api)
|
||||
create l_uri_mapping.make_trailing_slash_ignored ("/admin/users", l_users_handler)
|
||||
a_router.map (l_uri_mapping, a_router.methods_get_post)
|
||||
@@ -67,6 +76,14 @@ feature -- Access: router
|
||||
create l_uri_mapping.make_trailing_slash_ignored ("/admin/roles", l_roles_handler)
|
||||
a_router.map (l_uri_mapping, a_router.methods_get_post)
|
||||
|
||||
create l_admin_cache_handler.make (a_api)
|
||||
create l_uri_mapping.make_trailing_slash_ignored ("/admin/cache", l_admin_cache_handler)
|
||||
a_router.map (l_uri_mapping, a_router.methods_get_post)
|
||||
|
||||
create l_admin_export_handler.make (a_api)
|
||||
create l_uri_mapping.make_trailing_slash_ignored ("/admin/export", l_admin_export_handler)
|
||||
a_router.map (l_uri_mapping, a_router.methods_get_post)
|
||||
|
||||
create l_user_handler.make (a_api)
|
||||
a_router.handle ("/admin/add/user", l_user_handler, a_router.methods_get_post)
|
||||
a_router.handle ("/admin/user/{id}", l_user_handler, a_router.methods_get)
|
||||
@@ -91,6 +108,10 @@ feature -- Security
|
||||
Result.force ("admin roles")
|
||||
Result.force ("admin modules")
|
||||
Result.force ("install modules")
|
||||
Result.force ("admin core caches")
|
||||
Result.force ("clear blocks cache")
|
||||
Result.force ("admin export")
|
||||
Result.force ("export core")
|
||||
end
|
||||
|
||||
feature -- Hooks
|
||||
@@ -119,7 +140,20 @@ feature -- Hooks
|
||||
create lnk.make ("Admin", "admin")
|
||||
lnk.set_permission_arguments (<<"manage " + {CMS_ADMIN_MODULE}.name>>)
|
||||
a_menu_system.management_menu.extend (lnk)
|
||||
|
||||
end
|
||||
|
||||
create lnk.make ("Module", "admin/modules")
|
||||
lnk.set_permission_arguments (<<"manage module">>)
|
||||
a_menu_system.management_menu.extend (lnk)
|
||||
|
||||
-- Per module cache permission!
|
||||
create lnk.make ("Cache", "admin/cache")
|
||||
a_menu_system.management_menu.extend (lnk)
|
||||
|
||||
-- Per module export permission!
|
||||
create lnk.make ("Export", "admin/export")
|
||||
a_menu_system.management_menu.extend (lnk)
|
||||
end
|
||||
|
||||
note
|
||||
|
||||
93
modules/admin/handler/cms_admin_cache_handler.e
Normal file
93
modules/admin/handler/cms_admin_cache_handler.e
Normal file
@@ -0,0 +1,93 @@
|
||||
note
|
||||
description: "[
|
||||
Administrate cache functionality.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_ADMIN_CACHE_HANDLER
|
||||
|
||||
inherit
|
||||
CMS_HANDLER
|
||||
|
||||
WSF_URI_HANDLER
|
||||
rename
|
||||
new_mapping as new_uri_mapping
|
||||
end
|
||||
|
||||
WSF_RESOURCE_HANDLER_HELPER
|
||||
redefine
|
||||
do_get,
|
||||
do_post
|
||||
end
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Execution
|
||||
|
||||
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Execute request handler
|
||||
do
|
||||
execute_methods (req, res)
|
||||
end
|
||||
|
||||
do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
l_response: CMS_RESPONSE
|
||||
s: STRING
|
||||
f: CMS_FORM
|
||||
do
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api)
|
||||
f := clear_cache_web_form (l_response)
|
||||
create s.make_empty
|
||||
f.append_to_html (create {CMS_TO_WSF_THEME}.make (l_response, l_response.theme), s)
|
||||
l_response.set_main_content (s)
|
||||
l_response.execute
|
||||
end
|
||||
|
||||
do_post (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
l_response: CMS_RESPONSE
|
||||
s: STRING
|
||||
f: CMS_FORM
|
||||
do
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api)
|
||||
f := clear_cache_web_form (l_response)
|
||||
f.process (l_response)
|
||||
if
|
||||
attached f.last_data as fd and then
|
||||
fd.is_valid
|
||||
then
|
||||
if attached fd.string_item ("op") as l_op and then l_op.same_string (text_clear_all_caches) then
|
||||
l_response.hooks.invoke_clear_cache (Void, l_response)
|
||||
l_response.add_notice_message ("Caches cleared (if allowed)!")
|
||||
else
|
||||
fd.report_error ("Invalid form data!")
|
||||
end
|
||||
end
|
||||
create s.make_empty
|
||||
f.append_to_html (create {CMS_TO_WSF_THEME}.make (l_response, l_response.theme), s)
|
||||
l_response.set_main_content (s)
|
||||
l_response.execute
|
||||
end
|
||||
|
||||
feature -- Widget
|
||||
|
||||
clear_cache_web_form (a_response: CMS_RESPONSE): CMS_FORM
|
||||
local
|
||||
but: WSF_FORM_SUBMIT_INPUT
|
||||
do
|
||||
create Result.make (a_response.url (a_response.location, Void), "form_clear_cache")
|
||||
create but.make_with_text ("op", text_clear_all_caches)
|
||||
Result.extend (but)
|
||||
end
|
||||
|
||||
feature -- Interface text.
|
||||
|
||||
text_clear_all_caches: STRING_32 = "Clear all caches"
|
||||
|
||||
end
|
||||
116
modules/admin/handler/cms_admin_export_handler.e
Normal file
116
modules/admin/handler/cms_admin_export_handler.e
Normal file
@@ -0,0 +1,116 @@
|
||||
note
|
||||
description: "[
|
||||
Administrate export functionality.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_ADMIN_EXPORT_HANDLER
|
||||
|
||||
inherit
|
||||
CMS_HANDLER
|
||||
|
||||
WSF_URI_HANDLER
|
||||
rename
|
||||
new_mapping as new_uri_mapping
|
||||
end
|
||||
|
||||
WSF_RESOURCE_HANDLER_HELPER
|
||||
redefine
|
||||
do_get,
|
||||
do_post
|
||||
end
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Execution
|
||||
|
||||
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Execute request handler
|
||||
do
|
||||
execute_methods (req, res)
|
||||
end
|
||||
|
||||
do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
l_response: CMS_RESPONSE
|
||||
s: STRING
|
||||
f: CMS_FORM
|
||||
do
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api)
|
||||
f := exportation_web_form (l_response)
|
||||
create s.make_empty
|
||||
f.append_to_html (create {CMS_TO_WSF_THEME}.make (l_response, l_response.theme), s)
|
||||
l_response.set_main_content (s)
|
||||
l_response.execute
|
||||
end
|
||||
|
||||
do_post (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
l_response: CMS_RESPONSE
|
||||
s: STRING
|
||||
f: CMS_FORM
|
||||
l_exportation_parameters: CMS_EXPORT_PARAMETERS
|
||||
do
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} l_response.make (req, res, api)
|
||||
f := exportation_web_form (l_response)
|
||||
f.process (l_response)
|
||||
if
|
||||
attached f.last_data as fd and then
|
||||
fd.is_valid
|
||||
then
|
||||
if attached fd.string_item ("op") as l_op and then l_op.same_string (text_export_all_data) then
|
||||
if attached fd.string_item ("folder") as l_folder then
|
||||
create l_exportation_parameters.make (api.site_location.extended ("export").extended (l_folder))
|
||||
else
|
||||
create l_exportation_parameters.make (api.site_location.extended ("export").extended ((create {DATE_TIME}.make_now_utc).formatted_out ("yyyy-[0]mm-[0]dd---hh24-[0]mi-[0]ss")))
|
||||
end
|
||||
|
||||
l_response.hooks.invoke_export_to (Void, l_exportation_parameters, l_response)
|
||||
l_response.add_notice_message ("All data exported (if allowed)!")
|
||||
create s.make_empty
|
||||
across
|
||||
l_exportation_parameters.logs as ic
|
||||
loop
|
||||
s.append (ic.item)
|
||||
s.append ("<br/>")
|
||||
s.append_character ('%N')
|
||||
end
|
||||
l_response.add_notice_message (s)
|
||||
else
|
||||
fd.report_error ("Invalid form data!")
|
||||
end
|
||||
end
|
||||
create s.make_empty
|
||||
f.append_to_html (create {CMS_TO_WSF_THEME}.make (l_response, l_response.theme), s)
|
||||
l_response.set_main_content (s)
|
||||
l_response.execute
|
||||
end
|
||||
|
||||
feature -- Widget
|
||||
|
||||
exportation_web_form (a_response: CMS_RESPONSE): CMS_FORM
|
||||
local
|
||||
f_name: WSF_FORM_TEXT_INPUT
|
||||
but: WSF_FORM_SUBMIT_INPUT
|
||||
do
|
||||
create Result.make (a_response.url (a_response.location, Void), "export_all_data")
|
||||
Result.extend_raw_text ("Export CMS data to ")
|
||||
create f_name.make_with_text ("folder", (create {DATE_TIME}.make_now_utc).formatted_out ("yyyy-[0]mm-[0]dd---hh24-[0]mi-[0]ss"))
|
||||
f_name.set_label ("Export folder name")
|
||||
f_name.set_description ("Folder name under 'exports' folder.")
|
||||
f_name.set_is_required (True)
|
||||
Result.extend (f_name)
|
||||
create but.make_with_text ("op", text_export_all_data)
|
||||
Result.extend (but)
|
||||
end
|
||||
|
||||
feature -- Interface text.
|
||||
|
||||
text_export_all_data: STRING_32 = "Export all data"
|
||||
|
||||
end
|
||||
320
modules/admin/handler/cms_admin_modules_handler.e
Normal file
320
modules/admin/handler/cms_admin_modules_handler.e
Normal file
@@ -0,0 +1,320 @@
|
||||
note
|
||||
description: "[
|
||||
Administrate modules.
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
CMS_ADMIN_MODULES_HANDLER
|
||||
|
||||
inherit
|
||||
CMS_HANDLER
|
||||
|
||||
WSF_URI_HANDLER
|
||||
rename
|
||||
new_mapping as new_uri_mapping
|
||||
end
|
||||
|
||||
WSF_RESOURCE_HANDLER_HELPER
|
||||
redefine
|
||||
do_get, do_post
|
||||
end
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
CMS_SETUP_ACCESS
|
||||
|
||||
CMS_ACCESS
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Execution
|
||||
|
||||
execute (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
-- Execute request handler
|
||||
do
|
||||
execute_methods (req, res)
|
||||
end
|
||||
|
||||
do_get (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
r: CMS_RESPONSE
|
||||
s: STRING
|
||||
f: CMS_FORM
|
||||
l_denied: BOOLEAN
|
||||
do
|
||||
if
|
||||
attached {WSF_STRING} req.query_parameter ("op") as l_op and then l_op.same_string ("uninstall") and then
|
||||
attached {WSF_TABLE} req.query_parameter ("module_uninstallation") as tb
|
||||
then
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
||||
if attached api.setup.string_8_item ("admin.installation_access") as l_access then
|
||||
if l_access.is_case_insensitive_equal ("none") then
|
||||
l_denied := True
|
||||
elseif l_access.is_case_insensitive_equal ("permission") then
|
||||
l_denied := not r.has_permission ("install modules")
|
||||
end
|
||||
else
|
||||
l_denied := True
|
||||
end
|
||||
if l_denied then
|
||||
create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
||||
r.set_main_content ("You do not have permission to access CMS module uninstallation procedure!")
|
||||
else
|
||||
create s.make_empty
|
||||
across
|
||||
tb as ic
|
||||
loop
|
||||
if attached api.setup.modules.item_by_name (ic.item.string_representation) as l_module then
|
||||
if api.is_module_installed (l_module) then
|
||||
api.uninstall_module (l_module)
|
||||
if api.is_module_installed (l_module) then
|
||||
s.append ("<p>ERROR: Module " + l_module.name + " failed to be uninstalled!</p>")
|
||||
else
|
||||
s.append ("<p>Module " + l_module.name + " was successfully uninstalled.</p>")
|
||||
end
|
||||
else
|
||||
s.append ("<p>Module " + l_module.name + " is not installed.</p>")
|
||||
end
|
||||
end
|
||||
end
|
||||
s.append (r.link ("Back to modules management", r.location, Void))
|
||||
r.set_main_content (s)
|
||||
end
|
||||
r.execute
|
||||
else
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
||||
f := modules_collection_web_form (r)
|
||||
create s.make_empty
|
||||
f.append_to_html (create {CMS_TO_WSF_THEME}.make (r, r.theme), s)
|
||||
r.set_page_title ("Modules")
|
||||
r.set_main_content (s)
|
||||
r.execute
|
||||
end
|
||||
end
|
||||
|
||||
do_post (req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
r: CMS_RESPONSE
|
||||
s: STRING
|
||||
f: CMS_FORM
|
||||
l_denied: BOOLEAN
|
||||
do
|
||||
if attached {WSF_STRING} req.item ("op") as l_op then
|
||||
if l_op.same_string ("Install modules") then
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, api)
|
||||
|
||||
if attached api.setup.string_8_item ("admin.installation_access") as l_access then
|
||||
if l_access.is_case_insensitive_equal ("none") then
|
||||
l_denied := True
|
||||
elseif l_access.is_case_insensitive_equal ("permission") then
|
||||
l_denied := not r.has_permission ("install modules")
|
||||
end
|
||||
else
|
||||
l_denied := True
|
||||
end
|
||||
if l_denied then
|
||||
create {FORBIDDEN_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
||||
r.set_main_content ("You do not have permission to access CMS module installation procedure!")
|
||||
else
|
||||
f := modules_collection_web_form (r)
|
||||
if l_op.same_string ("Install modules") then
|
||||
f.submit_actions.extend (agent on_installation_submit)
|
||||
f.process (r)
|
||||
elseif l_op.same_string ("uninstall") then
|
||||
f.submit_actions.extend (agent on_uninstallation_submit)
|
||||
f.process (r)
|
||||
end
|
||||
if
|
||||
not attached f.last_data as l_data or else
|
||||
not l_data.is_valid
|
||||
then
|
||||
r.add_error_message ("Error occurred.")
|
||||
create s.make_empty
|
||||
f.append_to_html (create {CMS_TO_WSF_THEME}.make (r, r.theme), s)
|
||||
r.set_page_title ("Modules")
|
||||
r.set_main_content (s)
|
||||
else
|
||||
r.add_notice_message ("Operation on module(s) succeeded.")
|
||||
r.set_redirection (r.location)
|
||||
end
|
||||
end
|
||||
r.execute
|
||||
else
|
||||
create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, api)
|
||||
r.execute
|
||||
end
|
||||
else
|
||||
do_get (req, res)
|
||||
end
|
||||
end
|
||||
|
||||
modules_collection_web_form (a_response: CMS_RESPONSE): CMS_FORM
|
||||
local
|
||||
mod: CMS_MODULE
|
||||
f_cb: WSF_FORM_CHECKBOX_INPUT
|
||||
w_tb: WSF_WIDGET_TABLE
|
||||
w_row: WSF_WIDGET_TABLE_ROW
|
||||
w_item: WSF_WIDGET_TABLE_ITEM
|
||||
w_submit: WSF_FORM_SUBMIT_INPUT
|
||||
w_set: WSF_FORM_FIELD_SET
|
||||
|
||||
l_mods_to_install: ARRAYED_LIST [CMS_MODULE]
|
||||
do
|
||||
create Result.make (a_response.url (a_response.location, Void), "modules_collection")
|
||||
create w_tb.make
|
||||
w_tb.add_css_class ("modules_table")
|
||||
create w_row.make (5)
|
||||
create w_item.make_with_text ("Enabled ")
|
||||
w_row.add_item (w_item)
|
||||
create w_item.make_with_text ("Module")
|
||||
w_row.add_item (w_item)
|
||||
create w_item.make_with_text ("Version")
|
||||
w_row.add_item (w_item)
|
||||
create w_item.make_with_text ("Description")
|
||||
w_row.add_item (w_item)
|
||||
w_tb.add_head_row (w_row)
|
||||
|
||||
create l_mods_to_install.make (0)
|
||||
across
|
||||
a_response.api.setup.modules as ic
|
||||
loop
|
||||
mod := ic.item
|
||||
if not a_response.api.is_module_installed (mod) then
|
||||
l_mods_to_install.extend (mod)
|
||||
else
|
||||
create w_row.make (5)
|
||||
create f_cb.make ("module_" + mod.name)
|
||||
f_cb.set_text_value (mod.name)
|
||||
f_cb.set_checked (mod.is_enabled)
|
||||
f_cb.set_is_readonly (True)
|
||||
create w_item.make_with_content (f_cb)
|
||||
w_row.add_item (w_item)
|
||||
|
||||
create w_item.make_with_text (mod.name)
|
||||
w_row.add_item (w_item)
|
||||
|
||||
create w_item.make_with_text (mod.version)
|
||||
w_row.add_item (w_item)
|
||||
|
||||
if attached mod.description as l_desc then
|
||||
create w_item.make_with_text (l_desc)
|
||||
w_row.add_item (w_item)
|
||||
else
|
||||
create w_item.make_with_text ("")
|
||||
w_row.add_item (w_item)
|
||||
end
|
||||
create w_item.make_with_text (a_response.link ("Uninstall", a_response.location + "?op=uninstall&module_uninstallation[]=" + mod.name, Void))
|
||||
w_row.add_item (w_item)
|
||||
|
||||
w_tb.add_row (w_row)
|
||||
end
|
||||
end
|
||||
create w_set.make
|
||||
w_set.set_legend ("Installed modules")
|
||||
w_set.extend (w_tb)
|
||||
-- create w_submit.make ("op")
|
||||
-- w_submit.set_text_value ("Save")
|
||||
-- w_set.extend (w_submit)
|
||||
Result.extend (w_set)
|
||||
|
||||
Result.extend_html_text ("<br/>")
|
||||
|
||||
if not l_mods_to_install.is_empty then
|
||||
create w_tb.make
|
||||
w_tb.add_css_class ("modules_table")
|
||||
create w_row.make (3)
|
||||
create w_item.make_with_text ("Install ")
|
||||
w_row.add_item (w_item)
|
||||
create w_item.make_with_text ("Module")
|
||||
w_row.add_item (w_item)
|
||||
create w_item.make_with_text ("Description")
|
||||
w_row.add_item (w_item)
|
||||
w_tb.add_head_row (w_row)
|
||||
across
|
||||
l_mods_to_install as ic
|
||||
loop
|
||||
mod := ic.item
|
||||
create w_row.make (3)
|
||||
create f_cb.make ("module_installation[" + mod.name + "]")
|
||||
f_cb.set_text_value (mod.name)
|
||||
create w_item.make_with_content (f_cb)
|
||||
w_row.add_item (w_item)
|
||||
|
||||
create w_item.make_with_text (mod.name)
|
||||
w_row.add_item (w_item)
|
||||
|
||||
if attached mod.description as l_desc then
|
||||
create w_item.make_with_text (l_desc)
|
||||
w_row.add_item (w_item)
|
||||
else
|
||||
create w_item.make_with_text ("")
|
||||
w_row.add_item (w_item)
|
||||
end
|
||||
w_tb.add_row (w_row)
|
||||
end
|
||||
create w_set.make
|
||||
w_set.set_legend ("Available modules for installation")
|
||||
w_set.extend (w_tb)
|
||||
create w_submit.make ("op")
|
||||
w_submit.set_text_value ("Install modules")
|
||||
w_set.extend (w_submit)
|
||||
Result.extend (w_set)
|
||||
end
|
||||
end
|
||||
|
||||
on_installation_submit (fd: WSF_FORM_DATA)
|
||||
local
|
||||
l_mods: CMS_MODULE_COLLECTION
|
||||
do
|
||||
if attached {WSF_TABLE} fd.table_item ("module_installation") as tb and then not tb.is_empty then
|
||||
l_mods := api.setup.modules
|
||||
across
|
||||
tb as ic
|
||||
loop
|
||||
if
|
||||
attached {WSF_STRING} ic.item as l_mod_name and then
|
||||
attached l_mods.item_by_name (l_mod_name.value) as m
|
||||
then
|
||||
api.install_module (m)
|
||||
if not api.is_module_installed (m) then
|
||||
fd.report_error ("Installation failed for module " + m.name)
|
||||
end
|
||||
else
|
||||
fd.report_error ("Can not find associated module" + ic.item.as_string.url_encoded_value)
|
||||
end
|
||||
end
|
||||
else
|
||||
fd.report_error ("No module to install!")
|
||||
end
|
||||
end
|
||||
|
||||
on_uninstallation_submit (fd: WSF_FORM_DATA)
|
||||
local
|
||||
l_mods: CMS_MODULE_COLLECTION
|
||||
do
|
||||
if attached {WSF_TABLE} fd.table_item ("module_uninstallation") as tb and then not tb.is_empty then
|
||||
l_mods := api.setup.modules
|
||||
across
|
||||
tb as ic
|
||||
loop
|
||||
if
|
||||
attached {WSF_STRING} ic.item as l_mod_name and then
|
||||
attached l_mods.item_by_name (l_mod_name.value) as m
|
||||
then
|
||||
api.uninstall_module (m)
|
||||
if api.is_module_installed (m) then
|
||||
fd.report_error ("Un-Installation failed for module " + m.name)
|
||||
end
|
||||
else
|
||||
fd.report_error ("Can not find associated module" + ic.item.as_string.url_encoded_value)
|
||||
end
|
||||
end
|
||||
else
|
||||
fd.report_error ("No module to uninstall!")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -93,7 +93,7 @@ feature -- HTTP Methods
|
||||
s.append ("<a href=%"")
|
||||
s.append (req.absolute_script_url ("/admin/role/" + u.id.out))
|
||||
s.append ("%">")
|
||||
s.append (u.name)
|
||||
s.append (html_encoded (u.name))
|
||||
s.append ("</a>")
|
||||
s.append ("</li>%N")
|
||||
end
|
||||
|
||||
@@ -106,7 +106,7 @@ feature -- HTTP Methods
|
||||
s.append ("<a href=%"")
|
||||
s.append (req.absolute_script_url ("/admin/user/"+u.id.out))
|
||||
s.append ("%">")
|
||||
s.append (u.name)
|
||||
s.append (html_encoded (u.name))
|
||||
s.append ("</a>")
|
||||
s.append ("</li>%N")
|
||||
end
|
||||
|
||||
@@ -123,6 +123,7 @@ feature -- Hooks configuration
|
||||
lnk.set_weight (98)
|
||||
a_menu_system.primary_menu.extend (lnk)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
feature -- Handler
|
||||
@@ -521,26 +522,26 @@ feature {NONE} -- Helpers
|
||||
|
||||
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)
|
||||
-- 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.put_required_block (l_tpl_block, "content")
|
||||
-- else
|
||||
-- debug ("cms")
|
||||
-- a_response.add_warning_message ("Error with block [" + a_block_id + "]")
|
||||
-- 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
|
||||
-- end
|
||||
-- end
|
||||
|
||||
get_block_view_register (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
|
||||
do
|
||||
@@ -579,7 +580,6 @@ feature {NONE} -- Block views
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
get_block_view_reactivate (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
|
||||
do
|
||||
if a_response.request.is_get_request_method then
|
||||
|
||||
@@ -47,6 +47,7 @@ feature -- HTTP Methods
|
||||
l_page: CMS_RESPONSE
|
||||
l_url: STRING
|
||||
i: INTEGER
|
||||
l_message: STRING
|
||||
do
|
||||
api.logger.put_information (generator + ".do_get Processing basic auth logoff", Void)
|
||||
if attached req.query_parameter ("prompt") as l_prompt then
|
||||
@@ -72,6 +73,10 @@ feature -- HTTP Methods
|
||||
l_page.set_status_code ({HTTP_STATUS_CODE}.found)
|
||||
l_page.set_redirection (l_url)
|
||||
end
|
||||
create l_message.make_from_string (logout_message)
|
||||
l_message.replace_substring_all ("$site_login", req.absolute_script_url ("/account/roc-login"))
|
||||
l_message.replace_substring_all ("$site_home", req.absolute_script_url (""))
|
||||
l_page.set_main_content (l_message)
|
||||
l_page.execute
|
||||
end
|
||||
end
|
||||
@@ -113,4 +118,15 @@ feature -- HTTP Methods
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
feature {NONE}-- Lougout Message
|
||||
|
||||
logout_message: STRING = "[
|
||||
<div class="cms-logout-message">
|
||||
<h2>You are now signed out</h2>
|
||||
<p>You can <a href="$site_login">log</a> in again, or go to the <a href="$site_home">front page</a>.</p>
|
||||
</div>
|
||||
]"
|
||||
|
||||
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ ROC_AUTH.login = function() {
|
||||
var newdiv = document.createElement('div');
|
||||
newdiv.innerHTML = "<br>Invalid Credentials</br>";
|
||||
newdiv.id = 'myModalFormId';
|
||||
$("body").append(newdiv);
|
||||
$(".primary-tabs").append(newdiv);
|
||||
}
|
||||
}else{
|
||||
|
||||
@@ -49,7 +49,7 @@ ROC_AUTH.login = function() {
|
||||
var newdiv = document.createElement('div');
|
||||
newdiv.innerHTML = "<br>Invalid Credentials</br>";
|
||||
newdiv.id = 'myModalFormId';
|
||||
$("body").append(newdiv);
|
||||
$(".primary-tabs").append(newdiv);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -93,7 +93,7 @@ ROC_AUTH.login_with_redirect = function() {
|
||||
var newdiv = document.createElement('div');
|
||||
newdiv.innerHTML = "<br>Invalid Credentials</br>";
|
||||
newdiv.id = 'myModalFormId';
|
||||
$("body").append(newdiv);
|
||||
$(".primary-tabs").append(newdiv);
|
||||
$("#imgProgressRedirect").hide();
|
||||
}
|
||||
}else{
|
||||
@@ -122,8 +122,8 @@ ROC_AUTH.login_with_redirect = function() {
|
||||
var newdiv = document.createElement('div');
|
||||
newdiv.innerHTML = "<br>Invalid Credentials</br>";
|
||||
newdiv.id = 'myModalFormId';
|
||||
$("body").append(newdiv);
|
||||
$("#imgProgressRedirect").hide();
|
||||
$(".primary-tabs").append(newdiv);
|
||||
$("#imgProgressRedirect").hide();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<div>
|
||||
<form name="cms_basic_auth" action method="POST">
|
||||
<div>
|
||||
<input type="text" name="username" required>
|
||||
<input type="text" name="username" id="username" required>
|
||||
<label>Username</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="password" name="password" required>
|
||||
<input type="password" name="password" id="password" required>
|
||||
<label>Password</label>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -50,10 +50,10 @@ feature -- Access
|
||||
|
||||
feature -- Access: node
|
||||
|
||||
summary: detachable READABLE_STRING_8
|
||||
summary: detachable READABLE_STRING_32
|
||||
-- A short summary of the node.
|
||||
|
||||
content: detachable READABLE_STRING_8
|
||||
content: detachable READABLE_STRING_32
|
||||
-- Content of the node.
|
||||
|
||||
format: detachable READABLE_STRING_8
|
||||
|
||||
@@ -36,7 +36,7 @@ feature {NONE} -- Initialization
|
||||
Precursor
|
||||
|
||||
-- Create the node storage for type blog
|
||||
if attached {CMS_STORAGE_SQL_I} storage as l_storage_sql then
|
||||
if attached storage.as_sql_storage as l_storage_sql then
|
||||
create {CMS_BLOG_STORAGE_SQL} blog_storage.make (l_storage_sql)
|
||||
else
|
||||
create {CMS_BLOG_STORAGE_NULL} blog_storage.make
|
||||
@@ -97,6 +97,21 @@ feature -- Access node
|
||||
Result := nodes_to_blogs (blog_storage.blogs_from_user_limited (a_user, a_limit, a_offset))
|
||||
end
|
||||
|
||||
feature -- Conversion
|
||||
|
||||
full_blog_node (a_blog: CMS_BLOG): CMS_BLOG
|
||||
-- If `a_blog' is partial, return the full blog node from `a_blog',
|
||||
-- otherwise return directly `a_blog'.
|
||||
require
|
||||
a_blog_set: a_blog /= Void
|
||||
do
|
||||
if attached {CMS_BLOG} node_api.full_node (a_blog) as l_full_blog then
|
||||
Result := l_full_blog
|
||||
else
|
||||
Result := a_blog
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Helpers
|
||||
|
||||
nodes_to_blogs (a_nodes: LIST [CMS_NODE]): ARRAYED_LIST [CMS_BLOG]
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<library name="cms" location="..\..\cms-safe.ecf" readonly="false"/>
|
||||
<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="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="json" location="$ISE_LIBRARY\contrib\library\text\parser\json\library\json-safe.ecf" readonly="false"/>
|
||||
<library name="text_filter" location="$ISE_LIBRARY\unstable\library\text\text_filter\text_filter-safe.ecf"/>
|
||||
|
||||
@@ -22,6 +22,10 @@ inherit
|
||||
|
||||
CMS_HOOK_RESPONSE_ALTER
|
||||
|
||||
CMS_HOOK_EXPORT
|
||||
|
||||
CMS_EXPORT_NODE_UTILITIES
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
@@ -61,7 +65,7 @@ feature {CMS_API} -- Module Initialization
|
||||
loop
|
||||
ct.extend_format (ic.item)
|
||||
end
|
||||
l_node_api.add_content_type (ct)
|
||||
l_node_api.add_node_type (ct)
|
||||
l_node_api.add_content_type_webform_manager (create {CMS_BLOG_NODE_TYPE_WEBFORM_MANAGER}.make (ct))
|
||||
|
||||
-- Add support for CMS_BLOG, which requires a storage extension to store the optional "tags" value
|
||||
@@ -79,7 +83,7 @@ feature {CMS_API} -- Module management
|
||||
sql: STRING
|
||||
do
|
||||
-- Schema
|
||||
if attached {CMS_STORAGE_SQL_I} api.storage as l_sql_storage then
|
||||
if attached api.storage.as_sql_storage as l_sql_storage then
|
||||
if not l_sql_storage.sql_table_exists ("blog_post_nodes") then
|
||||
sql := "[
|
||||
CREATE TABLE blog_post_nodes(
|
||||
@@ -153,6 +157,7 @@ feature -- Hooks
|
||||
do
|
||||
a_response.hooks.subscribe_to_menu_system_alter_hook (Current)
|
||||
a_response.hooks.subscribe_to_response_alter_hook (Current)
|
||||
a_response.hooks.subscribe_to_export_hook (Current)
|
||||
end
|
||||
|
||||
response_alter (a_response: CMS_RESPONSE)
|
||||
@@ -168,4 +173,74 @@ feature -- Hooks
|
||||
create lnk.make ("Blogs", "blogs/")
|
||||
a_menu_system.primary_menu.extend (lnk)
|
||||
end
|
||||
|
||||
export_to (a_export_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_export_parameters: CMS_EXPORT_PARAMETERS; a_response: CMS_RESPONSE)
|
||||
-- Export data identified by `a_export_id_list',
|
||||
-- or export all data if `a_export_id_list' is Void.
|
||||
local
|
||||
n: CMS_BLOG
|
||||
p: PATH
|
||||
d: DIRECTORY
|
||||
f: PLAIN_TEXT_FILE
|
||||
lst: LIST [CMS_BLOG]
|
||||
do
|
||||
if
|
||||
a_export_id_list = Void
|
||||
or else across a_export_id_list as ic some ic.item.same_string ("blog") end
|
||||
then
|
||||
if
|
||||
a_response.has_permissions (<<"export any node", "export blog">>) and then
|
||||
attached blog_api as l_blog_api
|
||||
then
|
||||
lst := l_blog_api.blogs_order_created_desc
|
||||
a_export_parameters.log ("Exporting " + lst.count.out + " blogs")
|
||||
across
|
||||
lst as ic
|
||||
loop
|
||||
n := l_blog_api.full_blog_node (ic.item)
|
||||
a_export_parameters.log (n.content_type + " #" + n.id.out)
|
||||
p := a_export_parameters.location.extended ("nodes").extended (n.content_type).extended (n.id.out)
|
||||
create d.make_with_path (p.parent)
|
||||
if not d.exists then
|
||||
d.recursive_create_dir
|
||||
end
|
||||
create f.make_with_path (p)
|
||||
if not f.exists or else f.is_access_writable then
|
||||
f.open_write
|
||||
f.put_string (json_to_string (blog_node_to_json (n)))
|
||||
f.close
|
||||
end
|
||||
-- Revisions.
|
||||
if
|
||||
attached node_api as l_node_api and then
|
||||
attached l_node_api.node_revisions (n) as l_revisions and then l_revisions.count > 1
|
||||
then
|
||||
a_export_parameters.log (n.content_type + " " + l_revisions.count.out + " revisions.")
|
||||
p := a_export_parameters.location.extended ("nodes").extended (n.content_type).extended (n.id.out)
|
||||
create d.make_with_path (p)
|
||||
if not d.exists then
|
||||
d.recursive_create_dir
|
||||
end
|
||||
across
|
||||
l_revisions as revs_ic
|
||||
loop
|
||||
if attached {CMS_BLOG} revs_ic.item as l_blog then
|
||||
create f.make_with_path (p.extended ("rev-" + n.revision.out).appended_with_extension ("json"))
|
||||
if not f.exists or else f.is_access_writable then
|
||||
f.open_write
|
||||
f.put_string (json_to_string (blog_node_to_json (l_blog)))
|
||||
end
|
||||
f.close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
blog_node_to_json (a_blog: CMS_BLOG): JSON_OBJECT
|
||||
do
|
||||
Result := node_to_json (a_blog)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,17 +83,18 @@ feature -- Persistence
|
||||
|
||||
if l_update then
|
||||
if l_has_modif then
|
||||
sql_change (sql_update_node_data, l_parameters)
|
||||
sql_modify (sql_update_node_data, l_parameters)
|
||||
end
|
||||
else
|
||||
if l_has_modif then
|
||||
sql_change (sql_insert_node_data, l_parameters)
|
||||
sql_insert (sql_insert_node_data, l_parameters)
|
||||
else
|
||||
-- no page data, means everything is empty.
|
||||
-- FOR NOW: always record row
|
||||
-- sql_change (sql_insert_node_data, l_parameters)
|
||||
end
|
||||
end
|
||||
sql_finalize
|
||||
end
|
||||
end
|
||||
|
||||
@@ -108,6 +109,19 @@ feature -- Persistence
|
||||
end
|
||||
end
|
||||
|
||||
delete_node (a_node: CMS_BLOG)
|
||||
-- <Precursor>
|
||||
local
|
||||
l_parameters: STRING_TABLE [ANY]
|
||||
do
|
||||
if a_node.has_id then
|
||||
create l_parameters.make (1)
|
||||
l_parameters.put (a_node.id, "nid")
|
||||
sql_modify (sql_delete_node_data, l_parameters)
|
||||
sql_finalize
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
node_data (a_node: CMS_NODE): detachable TUPLE [revision: INTEGER_64; tags: READABLE_STRING_32]
|
||||
@@ -124,24 +138,30 @@ feature {NONE} -- Implementation
|
||||
l_parameters.put (a_node.revision, "revision")
|
||||
sql_query (sql_select_node_data, l_parameters)
|
||||
if not has_error then
|
||||
n := sql_rows_count
|
||||
if n = 1 then
|
||||
if not sql_after then
|
||||
-- nid, revision, tags
|
||||
l_rev := sql_read_integer_64 (2)
|
||||
l_tags := sql_read_string_32 (3)
|
||||
if l_tags /= Void then
|
||||
Result := [l_rev, l_tags]
|
||||
end
|
||||
else
|
||||
check unique_data: n = 0 end
|
||||
sql_forth
|
||||
if not sql_after then
|
||||
check unique_data: n = 0 end
|
||||
Result := Void
|
||||
end
|
||||
end
|
||||
end
|
||||
sql_finalize
|
||||
ensure
|
||||
accepted_revision: Result /= Void implies Result.revision <= a_node.revision
|
||||
end
|
||||
|
||||
feature -- SQL
|
||||
|
||||
sql_select_node_data: STRING = "SELECT nid, revision, tags FROM blog_post_nodes WHERE nid =:nid AND revision<=:revision ORDER BY revision DESC LIMIT 1;"
|
||||
sql_select_node_data: STRING = "SELECT nid, revision, tags FROM blog_post_nodes WHERE nid=:nid AND revision<=:revision ORDER BY revision DESC LIMIT 1;"
|
||||
sql_insert_node_data: STRING = "INSERT INTO blog_post_nodes (nid, revision, tags) VALUES (:nid, :revision, :tags);"
|
||||
sql_update_node_data: STRING = "UPDATE blog_post_nodes SET nid=:nid, revision=:revision, tags=:tags WHERE nid=:nid AND revision=:revision;"
|
||||
sql_delete_node_data: STRING = "DELETE FROM blog_post_nodes WHERE nid=:nid;"
|
||||
|
||||
end
|
||||
|
||||
@@ -230,9 +230,9 @@ feature -- HTML Output
|
||||
lnk := blog_api.node_api.node_link (n)
|
||||
a_output.append ("<p class=%"blog_list_summary%">")
|
||||
if attached api.format (n.format) as f then
|
||||
a_output.append (f.formatted_output (l_summary))
|
||||
f.append_formatted_to (l_summary, a_output)
|
||||
else
|
||||
a_output.append (page.formats.default_format.formatted_output (l_summary))
|
||||
page.formats.default_format.append_formatted_to (l_summary, a_output)
|
||||
end
|
||||
a_output.append ("<br />")
|
||||
a_output.append (page.link ("See more...", lnk.location, Void))
|
||||
|
||||
@@ -123,7 +123,7 @@ feature -- HTML Output
|
||||
do
|
||||
a_output.append ("<h2>Posts from ")
|
||||
if attached user as l_user then
|
||||
a_output.append (l_user.name)
|
||||
a_output.append (html_encoded (l_user.name))
|
||||
else
|
||||
a_output.append ("unknown user")
|
||||
end
|
||||
|
||||
@@ -6,8 +6,12 @@ note
|
||||
deferred class
|
||||
CMS_BLOG_STORAGE_I
|
||||
|
||||
inherit
|
||||
CMS_NODE_STORAGE_I
|
||||
feature -- Error Handling
|
||||
|
||||
error_handler: ERROR_HANDLER
|
||||
-- Error handler.
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
|
||||
@@ -23,9 +23,10 @@ feature -- Access
|
||||
error_handler.reset
|
||||
write_information_log (generator + ".blogs_count")
|
||||
sql_query (sql_select_blog_count, Void)
|
||||
if sql_rows_count = 1 then
|
||||
if not has_error and not sql_after then
|
||||
Result := sql_read_integer_64 (1)
|
||||
end
|
||||
sql_finalize
|
||||
end
|
||||
|
||||
blogs_count_from_user (a_user: CMS_USER) : INTEGER_64
|
||||
@@ -38,9 +39,10 @@ feature -- Access
|
||||
create l_parameters.make (2)
|
||||
l_parameters.put (a_user.id, "user")
|
||||
sql_query (sql_select_blog_count_from_user, l_parameters)
|
||||
if sql_rows_count = 1 then
|
||||
if not has_error and not sql_after then
|
||||
Result := sql_read_integer_64 (1)
|
||||
end
|
||||
sql_finalize
|
||||
end
|
||||
|
||||
blogs: LIST [CMS_NODE]
|
||||
@@ -62,6 +64,7 @@ feature -- Access
|
||||
end
|
||||
sql_forth
|
||||
end
|
||||
sql_finalize
|
||||
end
|
||||
|
||||
blogs_limited (a_limit: NATURAL_32; a_offset: NATURAL_32): LIST [CMS_NODE]
|
||||
@@ -88,6 +91,7 @@ feature -- Access
|
||||
end
|
||||
sql_forth
|
||||
end
|
||||
sql_finalize
|
||||
end
|
||||
|
||||
blogs_from_user_limited (a_user: CMS_USER; a_limit: NATURAL_32; a_offset: NATURAL_32): LIST [CMS_NODE]
|
||||
@@ -115,6 +119,7 @@ feature -- Access
|
||||
end
|
||||
sql_forth
|
||||
end
|
||||
sql_finalize
|
||||
end
|
||||
|
||||
feature {NONE} -- Queries
|
||||
|
||||
128
modules/feed_aggregator/feed_aggregation.e
Normal file
128
modules/feed_aggregator/feed_aggregation.e
Normal file
@@ -0,0 +1,128 @@
|
||||
note
|
||||
description: "Feed aggregation parameters."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
FEED_AGGREGATION
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_name: READABLE_STRING_GENERAL)
|
||||
do
|
||||
create name.make_from_string_general (a_name)
|
||||
create {ARRAYED_LIST [READABLE_STRING_8]} locations.make (0)
|
||||
expiration := 60*60
|
||||
description_enabled := True
|
||||
size := 10
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: IMMUTABLE_STRING_32
|
||||
-- Associated name.
|
||||
|
||||
expiration: INTEGER
|
||||
-- Suggested expiration time in seconds (default: 1 hour).
|
||||
-- If negative then never expires.
|
||||
|
||||
size: INTEGER
|
||||
-- Number of entries to display per page.
|
||||
|
||||
description: detachable IMMUTABLE_STRING_32
|
||||
-- Optional description.
|
||||
|
||||
locations: LIST [READABLE_STRING_8]
|
||||
-- List of feed location aggregated into current.
|
||||
|
||||
included_categories: detachable LIST [READABLE_STRING_32]
|
||||
-- Optional categories to filter.
|
||||
-- If Void, include any.
|
||||
|
||||
description_enabled: BOOLEAN
|
||||
-- Display description?
|
||||
|
||||
feature -- Status report
|
||||
|
||||
has_category_filter: BOOLEAN
|
||||
-- Is there any category filtering?
|
||||
-- i.e via `included_categories'
|
||||
do
|
||||
Result := attached included_categories as cats and then not cats.is_empty
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_description (a_desc: detachable READABLE_STRING_GENERAL)
|
||||
do
|
||||
if a_desc = Void then
|
||||
description := Void
|
||||
else
|
||||
create description.make_from_string_general (a_desc)
|
||||
end
|
||||
end
|
||||
|
||||
set_expiration (nb_seconds: INTEGER)
|
||||
-- Set `expiration' to `nb_seconds'.
|
||||
do
|
||||
expiration := nb_seconds
|
||||
end
|
||||
|
||||
set_size (nb: INTEGER)
|
||||
-- Set `size' to `nb'.
|
||||
do
|
||||
size := nb
|
||||
end
|
||||
|
||||
set_description_enabled (b: BOOLEAN)
|
||||
-- Set `description_enabled' to `b'.
|
||||
do
|
||||
description_enabled := b
|
||||
end
|
||||
|
||||
reset_categories
|
||||
do
|
||||
included_categories := Void
|
||||
end
|
||||
|
||||
include_category (a_cat: READABLE_STRING_GENERAL)
|
||||
local
|
||||
lst: like included_categories
|
||||
s32: STRING_32
|
||||
do
|
||||
lst := included_categories
|
||||
if lst = Void then
|
||||
create {ARRAYED_LIST [READABLE_STRING_32]} lst.make (1)
|
||||
included_categories := lst
|
||||
lst.compare_objects
|
||||
end
|
||||
s32 := a_cat.to_string_32
|
||||
if not lst.has (s32) then
|
||||
lst.force (s32)
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_included (e: FEED_ITEM): BOOLEAN
|
||||
-- Is `e' included in final aggregation?
|
||||
-- i.e: related to `included_categories'
|
||||
-- note that if `e' has no category, it is included by default,
|
||||
-- even if `included_categories' is defined.
|
||||
do
|
||||
Result := True
|
||||
if attached e.categories as e_cats then
|
||||
if attached included_categories as lst then
|
||||
Result := across lst as ic some
|
||||
across e_cats as e_ic some
|
||||
e_ic.item.same_string (ic.item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
19
modules/feed_aggregator/feed_aggregator-safe.ecf
Normal file
19
modules/feed_aggregator/feed_aggregator-safe.ecf
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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="feed_aggregator" uuid="6A78AB37-9B07-4C42-9E24-0CA7D3C61E12" library_target="feed_aggregator">
|
||||
<target name="feed_aggregator">
|
||||
<root all_classes="true"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="base_extension" location="$ISE_LIBRARY\library\base_extension\base_extension-safe.ecf"/>
|
||||
<library name="time" location="$ISE_LIBRARY\library\time\time-safe.ecf"/>
|
||||
<library name="cms" location="..\..\cms-safe.ecf"/>
|
||||
<library name="cms_model" location="..\..\library\model\cms_model-safe.ecf" readonly="false"/>
|
||||
<library name="error" location="$ISE_LIBRARY\contrib\library\utility\general\error\error-safe.ecf"/>
|
||||
<library name="feed" location="$ISE_LIBRARY\contrib\library\text\parser\feed\feed-safe.ecf"/>
|
||||
<library name="http" location="$ISE_LIBRARY\contrib\library\network\protocol\http\http-safe.ecf"/>
|
||||
<library name="http_client" location="$ISE_LIBRARY\contrib\library\network\http_client\http_client-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"/>
|
||||
<library name="wsf_html" location="$ISE_LIBRARY\contrib\library\web\framework\ewf\wsf_html\wsf_html-safe.ecf" readonly="false"/>
|
||||
<cluster name="src" location="." recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
153
modules/feed_aggregator/feed_aggregator_api.e
Normal file
153
modules/feed_aggregator/feed_aggregator_api.e
Normal file
@@ -0,0 +1,153 @@
|
||||
note
|
||||
description: "API for Feed aggregator module."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
FEED_AGGREGATOR_API
|
||||
|
||||
inherit
|
||||
CMS_MODULE_API
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Access
|
||||
|
||||
aggregations: HASH_TABLE [FEED_AGGREGATION, STRING]
|
||||
-- List of feed aggregations.
|
||||
local
|
||||
agg: FEED_AGGREGATION
|
||||
l_feed_id: READABLE_STRING_32
|
||||
l_title: detachable READABLE_STRING_GENERAL
|
||||
l_locations: detachable STRING_TABLE [READABLE_STRING_8]
|
||||
utf: UTF_CONVERTER
|
||||
l_table: like internal_aggregations
|
||||
do
|
||||
l_table := internal_aggregations
|
||||
if l_table /= Void then
|
||||
Result := l_table
|
||||
else
|
||||
create Result.make (0)
|
||||
internal_aggregations := Result
|
||||
if attached cms_api.module_configuration_by_name ({FEED_AGGREGATOR_MODULE}.name, "feeds") as cfg then
|
||||
if attached cfg.text_list_item ("ids") as l_ids then
|
||||
across
|
||||
l_ids as ic
|
||||
loop
|
||||
l_feed_id := ic.item
|
||||
create l_locations.make (1)
|
||||
|
||||
if attached cfg.text_list_item ({STRING_32} "feeds." + l_feed_id + ".locations") as l_location_list then
|
||||
across
|
||||
l_location_list as loc_ic
|
||||
loop
|
||||
l_locations.force (utf.utf_32_string_to_utf_8_string_8 (loc_ic.item), loc_ic.item)
|
||||
end
|
||||
end
|
||||
if attached cfg.text_table_item ({STRING_32} "feeds." + l_feed_id + ".locations") as l_location_table then
|
||||
across
|
||||
l_location_table as loc_tb_ic
|
||||
loop
|
||||
l_locations.force (utf.utf_32_string_to_utf_8_string_8 (loc_tb_ic.item), loc_tb_ic.key)
|
||||
end
|
||||
end
|
||||
if
|
||||
attached cfg.text_item ({STRING_32} "feeds." + l_feed_id + ".location") as l_location
|
||||
then
|
||||
l_locations.force (utf.utf_32_string_to_utf_8_string_8 (l_location), l_location)
|
||||
end
|
||||
if l_locations /= Void and then not l_locations.is_empty then
|
||||
l_title := cfg.text_item ({STRING_32} "feeds." + l_feed_id + ".title")
|
||||
if l_title = Void then
|
||||
l_title := l_feed_id
|
||||
end
|
||||
create agg.make (l_title)
|
||||
if attached cfg.text_item ({STRING_32} "feeds." + l_feed_id + ".expiration") as l_expiration then
|
||||
if l_expiration.is_integer then
|
||||
agg.set_expiration (l_expiration.to_integer)
|
||||
end
|
||||
end
|
||||
if attached cfg.text_item ({STRING_32} "feeds." + l_feed_id + ".size") as l_size then
|
||||
if l_size.is_integer then
|
||||
agg.set_size (l_size.to_integer)
|
||||
end
|
||||
end
|
||||
if attached cfg.text_item ({STRING_32} "feeds." + l_feed_id + ".option_description") as l_description_opt then
|
||||
agg.set_description_enabled (not l_description_opt.is_case_insensitive_equal_general ("disabled"))
|
||||
end
|
||||
across
|
||||
l_locations as loc_ic
|
||||
loop
|
||||
agg.locations.force (utf.utf_32_string_to_utf_8_string_8 (loc_ic.item))
|
||||
end
|
||||
Result.force (agg, l_feed_id)
|
||||
if attached cfg.text_list_item ({STRING_32} "feeds." + l_feed_id + ".categories") as l_cats then
|
||||
across
|
||||
l_cats as cats_ic
|
||||
loop
|
||||
agg.include_category (cats_ic.item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
aggregation (a_name: READABLE_STRING_GENERAL): detachable FEED_AGGREGATION
|
||||
do
|
||||
if attached a_name.is_valid_as_string_8 then
|
||||
Result := aggregations.item (a_name.as_string_8)
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Access: implementation
|
||||
|
||||
internal_aggregations: detachable like aggregations
|
||||
-- Cache value for `aggregations'.
|
||||
|
||||
feature -- Operation
|
||||
|
||||
feed (a_location: READABLE_STRING_8): detachable FEED
|
||||
local
|
||||
fac: FEED_DEFAULT_PARSERS
|
||||
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
create fac
|
||||
if attached new_http_client_session (a_location).get ("", ctx) as res then
|
||||
if attached res.body as l_content then
|
||||
Result := fac.feed_from_string (l_content)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
aggregation_feed (agg: FEED_AGGREGATION): detachable FEED
|
||||
-- Feed from aggregation `agg'.
|
||||
do
|
||||
across
|
||||
agg.locations as ic
|
||||
loop
|
||||
if attached feed (ic.item) as f then
|
||||
if Result /= Void then
|
||||
if f /= Void then
|
||||
Result := Result + f
|
||||
end
|
||||
else
|
||||
Result := f
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
new_http_client_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
local
|
||||
cl: LIBCURL_HTTP_CLIENT
|
||||
do
|
||||
create cl.make
|
||||
Result := cl.new_session (a_url)
|
||||
Result.set_is_insecure (True)
|
||||
end
|
||||
|
||||
end
|
||||
355
modules/feed_aggregator/feed_aggregator_module.e
Normal file
355
modules/feed_aggregator/feed_aggregator_module.e
Normal file
@@ -0,0 +1,355 @@
|
||||
note
|
||||
description: "CMS module bringing support for feed aggregation."
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
FEED_AGGREGATOR_MODULE
|
||||
|
||||
inherit
|
||||
CMS_MODULE
|
||||
rename
|
||||
module_api as feed_aggregator_api
|
||||
redefine
|
||||
initialize,
|
||||
register_hooks,
|
||||
permissions,
|
||||
feed_aggregator_api
|
||||
end
|
||||
|
||||
CMS_HOOK_BLOCK
|
||||
|
||||
CMS_HOOK_RESPONSE_ALTER
|
||||
|
||||
CMS_HOOK_MENU_SYSTEM_ALTER
|
||||
|
||||
CMS_HOOK_CACHE
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
-- Create Current module, disabled by default.
|
||||
do
|
||||
version := "1.0"
|
||||
description := "Feed aggregation"
|
||||
package := "feed"
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
name: STRING = "feed_aggregator"
|
||||
|
||||
permissions: LIST [READABLE_STRING_8]
|
||||
-- List of permission ids, used by this module, and declared.
|
||||
do
|
||||
Result := Precursor
|
||||
Result.force (permission__manage_feed_aggregator)
|
||||
Result.force (permission__clear_feed_cache)
|
||||
end
|
||||
|
||||
permission__manage_feed_aggregator: STRING = "manage feed aggregator"
|
||||
permission__clear_feed_cache: STRING = "clear feed cache"
|
||||
|
||||
feature {CMS_API} -- Module Initialization
|
||||
|
||||
initialize (api: CMS_API)
|
||||
-- <Precursor>
|
||||
do
|
||||
Precursor (api)
|
||||
create feed_aggregator_api.make (api)
|
||||
end
|
||||
|
||||
feature {CMS_API} -- Access: API
|
||||
|
||||
feed_aggregator_api: detachable FEED_AGGREGATOR_API
|
||||
-- Eventual module api.
|
||||
|
||||
feature -- Access: router
|
||||
|
||||
setup_router (a_router: WSF_ROUTER; a_api: CMS_API)
|
||||
-- <Precursor>
|
||||
local
|
||||
h: WSF_URI_TEMPLATE_HANDLER
|
||||
do
|
||||
a_router.handle ("/admin/feed_aggregator/", create {WSF_URI_AGENT_HANDLER}.make (agent handle_feed_aggregator_admin (a_api, ?, ?)), a_router.methods_head_get_post)
|
||||
create {WSF_URI_TEMPLATE_AGENT_HANDLER} h.make (agent handle_feed_aggregation (a_api, ?, ?))
|
||||
a_router.handle ("/feed_aggregation/", h, a_router.methods_head_get)
|
||||
a_router.handle ("/feed_aggregation/{feed_id}", h, a_router.methods_head_get)
|
||||
end
|
||||
|
||||
feature -- Handle
|
||||
|
||||
handle_feed_aggregator_admin (a_api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
nyi: NOT_IMPLEMENTED_ERROR_CMS_RESPONSE
|
||||
do
|
||||
create nyi.make (req, res, a_api)
|
||||
nyi.execute
|
||||
end
|
||||
|
||||
handle_feed_aggregation (a_api: CMS_API; req: WSF_REQUEST; res: WSF_RESPONSE)
|
||||
local
|
||||
r: CMS_RESPONSE
|
||||
s: STRING
|
||||
nb: INTEGER
|
||||
do
|
||||
if attached {WSF_STRING} req.query_parameter ("size") as p_size and then p_size.is_integer then
|
||||
nb := p_size.integer_value
|
||||
else
|
||||
nb := -1
|
||||
end
|
||||
create {GENERIC_VIEW_CMS_RESPONSE} r.make (req, res, a_api)
|
||||
if attached {WSF_STRING} req.path_parameter ("feed_id") as p_feed_id then
|
||||
if attached feed_aggregation (p_feed_id.value) as l_agg then
|
||||
create s.make_empty
|
||||
s.append ("<h1>")
|
||||
s.append (r.html_encoded (l_agg.name))
|
||||
s.append ("</h1>")
|
||||
if attached l_agg.included_categories as l_categories then
|
||||
s.append ("<span class=%"category%">")
|
||||
across
|
||||
l_categories as cats_ic
|
||||
loop
|
||||
s.append (" [")
|
||||
s.append (r.html_encoded (cats_ic.item))
|
||||
s.append ("]")
|
||||
end
|
||||
s.append ("</span>")
|
||||
end
|
||||
if attached l_agg.description as l_desc and then l_desc.is_valid_as_string_8 then
|
||||
s.append ("<div class=%"description%">")
|
||||
s.append (l_desc.as_string_8)
|
||||
s.append ("</div>")
|
||||
end
|
||||
s.append ("<ul>")
|
||||
across
|
||||
l_agg.locations as ic
|
||||
loop
|
||||
s.append ("<li><a href=%"")
|
||||
s.append (ic.item)
|
||||
s.append ("%">")
|
||||
s.append (ic.item)
|
||||
s.append ("</a></li>")
|
||||
end
|
||||
s.append ("</ul>")
|
||||
|
||||
if attached feed_to_html (p_feed_id.value, nb, True, r) as l_html then
|
||||
s.append (l_html)
|
||||
end
|
||||
r.set_main_content (s)
|
||||
else
|
||||
create {NOT_FOUND_ERROR_CMS_RESPONSE} r.make (req, res, a_api)
|
||||
end
|
||||
else
|
||||
if attached feed_aggregator_api as l_feed_agg_api then
|
||||
create s.make_empty
|
||||
across
|
||||
l_feed_agg_api.aggregations as ic
|
||||
loop
|
||||
s.append ("<li>")
|
||||
s.append (r.link (ic.key, "feed_aggregation/" + r.url_encoded (ic.key), Void))
|
||||
if attached ic.item.included_categories as l_categories then
|
||||
s.append ("<span class=%"category%">")
|
||||
across
|
||||
l_categories as cats_ic
|
||||
loop
|
||||
s.append (" [")
|
||||
s.append (r.html_encoded (cats_ic.item))
|
||||
s.append ("]")
|
||||
end
|
||||
s.append ("</span>")
|
||||
end
|
||||
if attached ic.item.description as l_desc then
|
||||
if l_desc.is_valid_as_string_8 then
|
||||
s.append ("<div class=%"description%">")
|
||||
s.append (l_desc.as_string_8)
|
||||
s.append ("</div>")
|
||||
end
|
||||
end
|
||||
s.append ("</li>")
|
||||
end
|
||||
r.set_main_content (s)
|
||||
else
|
||||
create {BAD_REQUEST_ERROR_CMS_RESPONSE} r.make (req, res, a_api)
|
||||
end
|
||||
end
|
||||
r.execute
|
||||
end
|
||||
|
||||
feature -- Hooks configuration
|
||||
|
||||
register_hooks (a_response: CMS_RESPONSE)
|
||||
-- Module hooks configuration.
|
||||
do
|
||||
a_response.hooks.subscribe_to_block_hook (Current)
|
||||
a_response.hooks.subscribe_to_response_alter_hook (Current)
|
||||
a_response.hooks.subscribe_to_menu_system_alter_hook (Current)
|
||||
a_response.hooks.subscribe_to_cache_hook (Current)
|
||||
end
|
||||
|
||||
feature -- Hook
|
||||
|
||||
clear_cache (a_cache_id_list: detachable ITERABLE [READABLE_STRING_GENERAL]; a_response: CMS_RESPONSE)
|
||||
-- <Precursor>.
|
||||
local
|
||||
p: PATH
|
||||
dir: DIRECTORY
|
||||
do
|
||||
if a_response.has_permissions (<<permission__clear_feed_cache, permission__manage_feed_aggregator>>) then
|
||||
if a_cache_id_list = Void then
|
||||
-- Clear all cache.
|
||||
p := a_response.api.files_location.extended (".cache").extended (name)
|
||||
create dir.make_with_path (p)
|
||||
if dir.exists then
|
||||
dir.recursive_delete
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
block_list: ITERABLE [like {CMS_BLOCK}.name]
|
||||
-- List of block names, managed by current object.
|
||||
local
|
||||
res: ARRAYED_LIST [like {CMS_BLOCK}.name]
|
||||
l_aggs: HASH_TABLE [FEED_AGGREGATION, STRING_8]
|
||||
do
|
||||
if attached feed_aggregator_api as l_feed_api then
|
||||
l_aggs := l_feed_api.aggregations
|
||||
create res.make (l_aggs.count)
|
||||
across
|
||||
l_aggs as ic
|
||||
loop
|
||||
res.force ("?feed." + ic.key)
|
||||
end
|
||||
else
|
||||
create res.make (0)
|
||||
end
|
||||
Result := res
|
||||
end
|
||||
|
||||
get_block_view (a_block_id: READABLE_STRING_8; a_response: CMS_RESPONSE)
|
||||
-- Get block object identified by `a_block_id' and associate with `a_response'.
|
||||
local
|
||||
s: READABLE_STRING_8
|
||||
b: CMS_CONTENT_BLOCK
|
||||
pref: STRING
|
||||
nb: INTEGER
|
||||
do
|
||||
if attached feed_aggregator_api as l_feed_api then
|
||||
pref := "feed."
|
||||
if a_block_id.starts_with (pref) then
|
||||
s := a_block_id.substring (pref.count + 1, a_block_id.count)
|
||||
else
|
||||
s := a_block_id
|
||||
end
|
||||
nb := 0
|
||||
if
|
||||
attached a_response.block_options (a_block_id) as l_options and then
|
||||
attached {READABLE_STRING_GENERAL} l_options.item ("size") as l_size and then
|
||||
l_size.is_integer
|
||||
then
|
||||
nb := l_size.to_integer
|
||||
end
|
||||
if attached feed_to_html (s, nb, True, a_response) as l_content then
|
||||
create b.make (a_block_id, Void, l_content, Void)
|
||||
b.set_is_raw (True)
|
||||
a_response.add_block (b, "feed_" + s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feed_aggregation (a_feed_id: READABLE_STRING_GENERAL): detachable FEED_AGGREGATION
|
||||
do
|
||||
if attached feed_aggregator_api as l_feed_api then
|
||||
Result := l_feed_api.aggregation (a_feed_id)
|
||||
end
|
||||
end
|
||||
|
||||
feed_to_html (a_feed_id: READABLE_STRING_GENERAL; a_count: INTEGER; with_feed_info: BOOLEAN; a_response: CMS_RESPONSE): detachable STRING
|
||||
local
|
||||
nb: INTEGER
|
||||
i: INTEGER
|
||||
e: FEED_ITEM
|
||||
l_cache: CMS_FILE_STRING_8_CACHE
|
||||
lnk: detachable FEED_LINK
|
||||
vis: FEED_TO_XHTML_VISITOR
|
||||
s: STRING
|
||||
do
|
||||
if attached feed_aggregator_api as l_feed_api then
|
||||
if attached l_feed_api.aggregation (a_feed_id) as l_agg then
|
||||
create l_cache.make (a_response.api.files_location.extended (".cache").extended (name).extended ("feed__" + a_feed_id + "__" + a_count.out + "_" + with_feed_info.out))
|
||||
Result := l_cache.item
|
||||
if Result = Void or l_cache.expired (Void, l_agg.expiration) then
|
||||
|
||||
create Result.make (1024)
|
||||
Result.append ("<!-- ")
|
||||
Result.append ("Updated: " + l_cache.cache_date_time.out)
|
||||
Result.append (" -->")
|
||||
|
||||
create vis.make (Result)
|
||||
if a_count = 0 then
|
||||
nb := l_agg.size
|
||||
else
|
||||
nb := a_count
|
||||
end
|
||||
vis.set_limit (nb)
|
||||
vis.set_description_enabled (l_agg.description_enabled)
|
||||
|
||||
if with_feed_info then
|
||||
create s.make_empty
|
||||
if attached l_agg.description as l_desc then
|
||||
s.append ("<div class=%"description%">")
|
||||
s.append_string_general (l_desc)
|
||||
s.append ("</div>")
|
||||
end
|
||||
vis.set_header (s)
|
||||
end
|
||||
create s.make_empty
|
||||
s.append_string ("<liv class=%"nav%">")
|
||||
s.append_string (a_response.link ("See more ...", "feed_aggregation/" + a_response.url_encoded (a_feed_id), Void))
|
||||
s.append_string ("</li>")
|
||||
vis.set_footer (s)
|
||||
|
||||
if attached l_feed_api.aggregation_feed (l_agg) as l_feed then
|
||||
if l_agg.has_category_filter and attached l_feed.items as lst then
|
||||
from
|
||||
lst.start
|
||||
until
|
||||
lst.after
|
||||
loop
|
||||
if not l_agg.is_included (lst.item_for_iteration) then
|
||||
lst.remove
|
||||
else
|
||||
lst.forth
|
||||
end
|
||||
end
|
||||
end
|
||||
l_feed.accept (vis)
|
||||
end
|
||||
l_cache.put (Result)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Hook
|
||||
|
||||
response_alter (a_response: CMS_RESPONSE)
|
||||
do
|
||||
a_response.add_style (a_response.url ("/module/" + name + "/files/css/feed_aggregator.css", Void), Void)
|
||||
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'.
|
||||
do
|
||||
a_menu_system.navigation_menu.extend (create {CMS_LOCAL_LINK}.make ("Feeds", "feed_aggregation/"))
|
||||
if a_response.has_permission (permission__manage_feed_aggregator) then
|
||||
a_menu_system.management_menu.extend (create {CMS_LOCAL_LINK}.make ("Feeds (admin)", "admin/feed_aggregator/"))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
28
modules/feed_aggregator/site/config/feeds.json.example
Normal file
28
modules/feed_aggregator/site/config/feeds.json.example
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"ids": ["news", "forum"],
|
||||
"feeds": {
|
||||
"news": {
|
||||
"title": "Eiffel related posts",
|
||||
"expiration": "21600",
|
||||
"size": 5,
|
||||
"locations": [
|
||||
"https://bertrandmeyer.com/feed/",
|
||||
"https://room.eiffel.com/blog/feed",
|
||||
"https://room.eiffel.com/article/feed",
|
||||
"https://room.eiffel.com/library/feed"
|
||||
]
|
||||
, "categories": ["Eiffel"]
|
||||
,"option_description": "enabled"
|
||||
},
|
||||
"forum": {
|
||||
"title": "Eiffel Forum",
|
||||
"expiration": "21600",
|
||||
"size": 5,
|
||||
"locations": [
|
||||
"https://groups.google.com/forum/feed/eiffel-users/msgs/atom.xml?num=15",
|
||||
"http://stackoverflow.com/feeds/tag?tagnames=eiffel&sort=newest"
|
||||
]
|
||||
,"option_description": "enabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
53
modules/feed_aggregator/site/files/css/feed_aggregator.css
Normal file
53
modules/feed_aggregator/site/files/css/feed_aggregator.css
Normal file
@@ -0,0 +1,53 @@
|
||||
div.feed ul {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 99%;
|
||||
}
|
||||
div.feed li {
|
||||
/* border-top: solid 1px #ddd; */
|
||||
padding: 0;
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
div.feed li a {
|
||||
font-weight: bold;
|
||||
}
|
||||
div.feed li .date {
|
||||
font-weight: bold;
|
||||
font-size: small;
|
||||
}
|
||||
div.feed li .category {
|
||||
margin-left: 20px;
|
||||
font-size: 8px;
|
||||
height: 9px;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
}
|
||||
div.feed li .description {
|
||||
margin-left: 20px;
|
||||
font-size: small;
|
||||
height: 18px;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
}
|
||||
div.feed li:hover {
|
||||
margin-bottom: 23px;
|
||||
}
|
||||
div.feed li:hover .description {
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
height: auto;
|
||||
overflow-y: scroll;
|
||||
overflow-x: scroll;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
border: solid 1px #000;
|
||||
z-index: 10;
|
||||
}
|
||||
div.feed li:hover:last-child {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
div.feed li .description::after {
|
||||
content: "...";
|
||||
}
|
||||
54
modules/feed_aggregator/site/files/scss/feed_aggregator.scss
Normal file
54
modules/feed_aggregator/site/files/scss/feed_aggregator.scss
Normal file
@@ -0,0 +1,54 @@
|
||||
div.feed {
|
||||
ul {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 99%;
|
||||
}
|
||||
li {
|
||||
/* border-top: solid 1px #ddd; */
|
||||
padding: 0;
|
||||
margin: 0 0 5px 0;
|
||||
|
||||
a {
|
||||
font-weight: bold;
|
||||
}
|
||||
.date {
|
||||
font-weight: bold;
|
||||
font-size: small;
|
||||
}
|
||||
.category {
|
||||
margin-left: 20px;
|
||||
font-size: 8px;
|
||||
height: 9px;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
}
|
||||
.description {
|
||||
margin-left: 20px;
|
||||
font-size: small;
|
||||
height: 18px;
|
||||
overflow: hidden;
|
||||
color: #999;
|
||||
}
|
||||
&:hover {
|
||||
margin-bottom: 23px;
|
||||
.description {
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
height: auto;
|
||||
overflow-y: scroll;
|
||||
overflow-x: scroll;
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
border: solid 1px #000;
|
||||
z-index: 10;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
}
|
||||
.description::after { content: "..."; }
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user