One very strong feature in advanced CMSes is to offer administrators to create functions (a set of actions to take) that get executed whenever a signal is received.
The feature requires three parts:
Whenever an event occurs, we send a signal using the emit keyword (in our C++ code.) This calls an internal function that propagates the signal at the user interface level (the administrators.) It is called after all the internal features were called so only the final result is received by the user interface (this being said, if we call set_title() multiple times we'd signal that event multiple times unless we register all the signals and execute the administrator functions only at the end of the C++ HTML generator. On the other hand, if some of the signals were to be used to influence the outcome of say... the theme selection, then it would need to happen early on.)
In some automated ways, all the signals can be made available to the administrators. Some signals may need to be hidden so we want to be able to have both behaviors.
Whenever the administrator receives a signal, one can check the current state of pretty much anything in the website (i.e. is this page published, is that link valid, etc.)
The state check enables for very fined tuned events to happen. For example, maybe a page of type P needs to be forced in moderation every time. The state check can verify that the page is indeed of type P and not marked as requiring moderation before changing the moderation status of the page.
Also, the administrator could check rights and generate a "You do not have permission" error.
To Be Noted
The Cassandra system does not allow us to make sure that, at a give time and on different servers, the same state test will return the same value. In other words, a page may be marked Published on server A, and Unpublished on server B. This can be a problem as we do not want to control that all the reads from Cassandra all come from one specific Cassandra node.
Once the signal is received and the state returned TRUE, one can execute a function. Functions can be one or more instructions to the system.
For example, an instruction could be to force unpublishing a page when a broken link appears on it, create a new copy of the page, removing the broken link from the copy, publishing the new revision, sending an email to the author and moderators who worked on the document before.
The system will provide ways to avoid recursivity since an event A may generate an event B which then generates an event A. At that point, we most certainly want to avoid processing event A again. However, this should depend on the arguments (i.e. event A on page 5 is blocked, but event A on page 3 is not.)
This should be as easy as creating a map of all the events being process at a time. When an event is over, clear the map from that event. When we receive another event, first check that map, if the event doesn't show, add it and process. Otherwise, silently return.
Quite often a state defines whether an event should be emitted. However, once emitted, it should not occur again for that object (or no more than X times.) For example, you could have a status change on a page going from Active to Dormant. Maybe that status change needs to be signal once. However, the event itself can only detect that the page is Dormant, not that it actually changed from Active to Dormant. This means we need to have an extra value to know whether we signaled the Dormat status or not. We may want to support an internal S&F feature to record those statuses. We have a problem in that we want to be able to clear those statuses (i.e. the page becomes Active again...)
The backend may want to do a lot of the work. This means we may need some of the signals to be processed at a later time. In other words, we should have a way to create a batch when we receive a given signal in the front end. The batch is then executed whenever the back end gets to it.
The batch needs to include the arguments (that may be a little more complicated than just having objects in C++!)
Obviously, just like with an immediate signal processing scheme, the status check will be done at the time the batched event is processed and thus the State may be different now than it was when the batch was registered (and it may be checked from a different set of nodes on the Cassandra ring.) Yet, whenever saving a page, you want to process many things such as the content for automatic tag extraction, indexation, glossary linkage, etc. All of those things required a bit of work that is better done on the backend. Similar events could be generated by the user using the S&F.
Since we're using the Boost signals, we do not have a central place where we can ensure that every message is propagated to this Signals & Functions feature. However, the truth is that we need to know exactly what parameter we are receiving. So... the S&F plug-in will capture the Core signals and implement the resulting consequences. All the other plug-ins will have to implement their own S&F functions using the S&F helper classes, etc. (i.e. S&F will implement all the lower layer events and core functions, nothing more.)
The implementation of the functions should just be by sending a signal. That way all the modules can act on the function.
One way to make this work is to create a function in the S&F plugin where other plugins can register their signals, states, and functions by name (i.e. using a map) including arguments passed (signals) and accepted (states and functions.) The registration could actually take many more entries such as a tooltip and a link to the documentation of that specific item.
Such a set of maps would allow us to create an interface that end users can access to create their programs. Then the system can call one specific signal to generate the signal and the S&F system can then emit two signals: one for States and one for executing Functions. This way, we can offer a system where plugins implement two signal functions: one to check a status, and one to run a function.
The next problem is to find a way to offer pretty much any type of object to travel around the signal system. With Qt, all objects can be made QObjects and each module that receives a signal can then use a dynamic cast to make the object what it needs to be. Objects can be stored in an argument list when emitting the signal. That argument list would name and type objects and thus the user can pass the correct objects to the state and function signals.