Category: "Web Applications"
Rocky Linux 9 - wkhtmltopdf
Jul 7th
I kept getting a divide by zero error (Signal 8) with wkhtmltopdf under Rocky Linux
RPM in use: wkhtmltox-0.12.6.1-2.almalinux9.x86_64.rpm (credit to: https://forums.rockylinux.org/t/need-to-install-wkhtmltopdf-for-rocky-linux-9-how/6758/2)
This was running under Ibexa, with Symfony.
I stripped it down to test with the output of the application on the command line and isolated the issue to a <link> tag for Bootstrap 4.3 by commenting out tags until I found which one was causing the issue.
I don't care what the issue was - upgrading Bootstrap to 4.6 worked
I hope this helps someone - it was frustrating.
Quick Apache Benchmark test with log in
May 25th
I have been looking at performance for a web application. I am using ab to compare the response times while adjusting the database indexing.
# 'log in'
curl -L https://www.example.com/login.php -k -b cookie -c cookie-jar -d username=admin -d password=**** -s > /dev/null
# Run the test -n times (in this case 10)
ab -C `grep PHP_session cookie-jar | cut -f6,7 | tr '\t' '='` -n 10 'https://www.example.com/main.php'
What's happening?
The curl command is logging in to the application and saving the cookies in a file named cookie-jar.
The grep command is extracting the session id cookie - name and value - from the cookie-jar and using it to authorize access to a main page for ab.
This post courtesy of Game Creek Video
DataTables - Adding Buttons to a FixedHeader
Mar 1st
I was using both the FixedHeader and Buttons extensions of DataTables and I needed the buttons to remain visible when the user scrolled.
The solution I chose was to move the buttons DOM into the table head.
Since the buttons are in a div and thead is part of a table, a tr is used with a td that has a colspan of the entire table.
var dtButtons = document.querySelector(".dt-buttons");
var row, cell;
row = document.createElement("tr");
cell = document.createElement("td");
cell.id = "magic-button-row";
cell.setAttribute("colspan", /*** Put the number of columns in your table here ***/);
cell.appendChild(dtButtons);
row.appendChild(cell);
document.querySelector("#display-table thead").insertBefore(row, document.querySelector("tr:first-of-type"));
There is a tiny bit of CSS, too.
#magic-button-row {
padding: 0;
margin: 0;
text-align: left;
}
This post courtesy of Game Creek Video.
Web application session timeout code
Aug 28th
Session timeout warning for a web application with a variety of page layouts and frequent use of multiple tabs.
Nutshell explanation - ping the server, if you get a 403, show a huge red bar across the top of whatever page pinged.
Result:
- It is immediately apparent the tab has timed out
- If one tab times out, it does not disrupt the others
- There is a link to help the user log in again
function sessionPing() {
var pinger;
function doPing() {
var pReq = new XMLHttpRequest();
pReq.addEventListener("load", function () {
var sessionExpired, sessionExpiredMessage;
var reloadLink;
if (this.status === 403) {
clearInterval(pinger);
sessionExpired = document.createElement("div");
// Sometimes an inline style is really the best solution
sessionExpired.setAttribute("style","display:block; width: 100%; line-height: 2.5em; position:absolute; top:0; z-index:10000; text-align: center; background-color: #f00; color: #fff; font-family: 'Trebuchet MS',sans; font-size: 1.5em; font-style: italic");
sessionExpiredMessage = document.createTextNode("Session expired ");
sessionExpired.appendChild(sessionExpiredMessage);
reloadLink = document.createElement("a");
reloadLink.href = location.href;
reloadLink.textContent = "Click to Continue";
reloadLink.setAttribute("style","font-size:0.7em;color:#ddd");
sessionExpired.appendChild(reloadLink);
document.body.insertBefore(sessionExpired, document.body.firstChild);
document.title = "Session expired";
}
});
pReq.open("GET", "/ping.php");
pReq.send();
}
pinger = setInterval(doPing, 30000);
}
sessionPing();
<?php
session_start();
if (empty($_SESSION['user_id'])) {
header('HTTP/1.1 403 Forbidden');
exit;
}
header('HTTP/1.1 201 No content');
exit;
One may argue that the page should be cleared, in this case, I chose to leave it up so people can copy the content off.
Confession: I didn't test this code, it is an extract.
This post courtesy of Game Creek Video
Symfony 3 Ajax (EntityType) CollectionType
Jan 10th
Objective: Create an efficient way to use a Symfony CollectionType of Entities. I wanted to be able to provide a Dijit FilteringSelect (autocomplete) for a collection of entities.
Issues: Using a CollectionType of EntityTypes loaded all the entities into a selection statement. This is fine if there are only a few entities, but not if there are hundreds. Also, model transformers cannot be used with CollectionTypes.
Solution: Create a custom FieldType and apply a model transformer.
The custom field type is based off a TextType, which allows the id of the entity to be passed as text, then transformed by the transformer. Transformers are described here.
The Dijit FilteringSelect only needs an id to serve as an anchor within the page layout. One may argue it could all be dynamic, but I like to use the HTML as a foundation. A text input is used as the base element which is replaced by the Dijit widget. The widget is created and populated with the data from a store.
<?php
namespace AppBundle\Form\Admin\Asset\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use AppBundle\Form\Admin\Asset\DataTransformer\ModelToIdTransformer;
class ModelRelationshipType extends AbstractType
{
private $modelToIdTransformer;
public function __construct( ModelToIdTransformer $modelToIdTransformer )
{
$this->modelToIdTransformer = $modelToIdTransformer;
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
->addModelTransformer( $this->modelToIdTransformer );
}
public function configureOptions( OptionsResolver $resolver )
{
// Stub
}
public function getParent()
{
return TextType::class;
}
}
The new custom field type was then added as a service, with the transformer injected in:
app.admin.field.type.model_relationship:
class: AppBundle\Form\Admin\Asset\Type\ModelRelationshipType
arguments: ['@app.form.data_transformer.model']
tags:
- { name: form.type, alias: 'app_model_relationship_type' }
The Twig template code to display each row of the collection uses a macro, because there are four of these fields.
{% block _model_extends_entry_row %} {% import 'form/macros.html.twig' as form_macros %} {{ form_macros.model_relationship_entry_row('requires',form) }} {% endblock %} {%macro relation(title,type,form) %}{%spaceless%} <div id="{{type}}" class="{{type}}"> <h3>{{title}}</h3> {{form_row(form)}} {% if form.vars.allow_add %} <div data-type="{{type}}" class="add-one-more-row">{{ 'common.add_one_more'|trans}} {% endif %} </div> {%endspaceless%}{%endmacro %}
To use the new field type in the form, you add the CollectionType, and the entry_type is ModelRelationshipType.
->add( 'requires', CollectionType::class, [
'entry_type' => ModelRelationshipType::class,
'required' => false,
'label' => false,
'empty_data' => null,
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true
] )