Macrura

Use delimited texarea, table, or YAML for settings

Recommended Posts

Needed to show someone how to quickly setup some settings for various things in a simple text area:

could be used for slider settings, site settings, etc;

What it does: gives you a matching variable for each key of each line...

1.) setup a textarea field for the settings ; i'm calling it settings_ta

2.) add delimited settings, 1 per line; i use a pipe (|) delimiter;

example:

address|some info here
facebook|https://www.facebook.com
twitter|https://twitter.com
phone|(999) 999-9999

3.) in your _init.php, get the settings - replace the page number and the name of your settings field:

    $settings_ta = $pages->get(1644)->settings_ta;	
    $settings_lines = explode(PHP_EOL, $settings_ta);	
   
    foreach($settings_lines as $settings_row) {
        $settings_pair = explode('|', $settings_row);
        ${trim($settings_pair[0])} = trim($settings_pair[1]);
    }

more condensed version, for those of you who like brevity...

foreach(explode(PHP_EOL, $pages->get(1644)->settings_ta) as $settings_row) {
	$settings_pair = explode('|', $settings_row);
	${trim($settings_pair[0])} = trim($settings_pair[1]);	
}

now you can do this in your templates:

echo $address;
echo $facebook;
echo $twitter;
echo $phone;

Edit: made the code simpler....; 2nd edit - added trim to support using ace editor with tabs

Addendum: as an added convenience, you could use the Ace text editor module which would give you a monospaced text field and the ability to use tabs for your settings, which could be more readable and easier to edit. The code has been updated to support this by trimming the exploded strings prior to generating the variable/value.

address   |  some info here
facebook  |  http://www.facebook.com
twitter   |  http://twitter.com
phone     |  (999) 999-9999
Edited by Macrura
  • Like 14

Share this post


Link to post
Share on other sites

I wonder if owzim's new YAML fieldtype would be useful for this scenario also?

Your approach might be simpler for non-tech users to understand and format though!

  • Like 1

Share this post


Link to post
Share on other sites

@adrian - yes, i love that YAML thing; and as I see it there are maybe 4-5 good ways to do settings, depending on the site and the scenario;

I usually use a profields table, and have 2 columns, and do a similar code as above to get the $vars, and i'm using this for a lot of things like slider settings, site settings, settings for javascript plugins etc.. where i need to have a ton of settings without much effort...

For some things that are mission critical I make fields, especially if the setting can use a color picker, rangeslider, or select.

the delimited textarea is good for beginners who maybe don't have profields, and who need a quick easy way to have some editable settings in the b/e..

and there should be an easy way to use the YAML field to do this; i guess i should try it and do another tutorial for using that for settings...

  • Like 3

Share this post


Link to post
Share on other sites

Part II: Using a Profields Table [Note: this part requires you to own ProFields]

1.) Setup a field of type table for settings, I call it settings_table. It will have 2 columns, setting and value

2.) add it to your template (example would be if you had a settings template).

3.) Add some settings..

| site_title | My Great Site           |
|------------|-------------------------|
| phone      | (666) 777-8888          |
|------------|-------------------------|
| slogan     | Tulips Rule             |
|------------|-------------------------|
| facebook   | http://www.facebook.com |

3.) Place this code in your _init.php or wherever you have global stuff.. replace the page number with the appropriate page number:

$settings_table = $pages->get(1020)->settings_table;

foreach($settings_table as $row) {
    ${$row->setting} = $row->value;
}

now you can do this:

echo $site_title
echo $phone
echo $slogan
echo $facebook

- - -

...and one might wonder, what is to prevent a client from inadvertently changing the setting name or even deleting a critical setting and consequently breaking their site?

with some jQuery, and the help of admin custom files (courtesy of martijn-geerts) you can disable any existing setting name from being edited. or deleted;

add this to your AdminCustomFiles/ProcessPageEdit.js file:

$(function(){
    $('li.Inputfield_settings_table tr').each(function(){
            
        setting = $(this).find('input[name*="_setting"]');    
        value = $(this).find('input[name*="_value"]').val();
        icon = $(this).find('i.InputfieldTableRowDeleteLink');
            if(value) {
                setting.addClass("disabled").attr('readonly', true);
                icon.removeClass("InputfieldTableRowDeleteLink fa-trash-o").addClass("fa-lock");
            }
    });
});   

this does assume that the table is named settings_table, the setting column is named "setting" and the value column is named "value".

you can also add this to your AdminCustomFiles/ProcessPageEdit.css

li.Inputfield_settings_table tr input.disabled {
	background-color: #e8e5e5 !important;
	color: #949494!important;
}

Here is a screenshot of this in action:

post-136-0-61518800-1417044448_thumb.png

Edit: added instructions to protect the settings names, prevent deletion, and change the color to differentiate the field status.

Edited by Macrura
  • Like 6

Share this post


Link to post
Share on other sites

Part III: Using YAML (part of structured data module).

1.) Install the module (make sure to install the new version, FieldtypeDataStructure)

2.) create the field for it (ex. settings_ds)

3.) Add some settings - these can be more sophisticated/nested than the settings above, because of the YAML structure options:

- name: address
  street: 27 Hawthorne Lane
  city: New York
  state: NY
  zip: 10982
  phones:
    main: (999) 888 9874
    fax: (555) 548-5647
- name: social_media
  facebook: https://www.facebook.com
  twitter: https://twitter.com
- name: global
  site_title: My Awesome Site
  tagline: Tulips Rule

4.) Get the field into your template:

$settings_ds = $pages->get(1644)->settings_ds;

5a.) Option 1: Auto populate the $vars

foreach($settings_ds as $setting) ${$setting->name} = $setting;

5b.) Option 2: Query the field with PW selectors (this is awesome):

$address = $settings_ds->get("name=address");
$social_media = $settings_ds->get("name=social_media");
$global = $settings_ds->get("name=global");

this is cool because you can keep all the parts of a setting together as a group, and have multiple parameters like this:

// address
echo $address->street;
echo $address->city;
echo $address->state;
echo $address->zip;
echo $address->phones->main;
echo $address->phones->fax;

// social media
echo $social_media->facebook;
echo $social_media->twitter;

// global
echo $global->site_title;
echo $global->tagline;
Edited by Macrura
  • Like 5

Share this post


Link to post
Share on other sites

haven't tested this but i'm thinking of adding the settings to the $config class so they are available inside functions and wireInclude, wireRender..

would just need to change the loop to this i think:

foreach($settings_lines as $settings_row) {
    $settings_pair = explode('|', $settings_row);
    $config->{trim($settings_pair[0])} = trim($settings_pair[1]);
}

or

foreach($settings_table as $row) {
    $config->{$row->setting} = $row->value;
}

Share this post


Link to post
Share on other sites

A settings field based on a hooked Profields Table.

When running the code (in the spoiler), superusers are able to edit, drag & delete all,

while other users are limited to edit the setting only.

(Thanks MacRura for the idea)
 
Superuser sees:

post-577-0-94216200-1449317098_thumb.png

 
Everybody else sees:

post-577-0-58529100-1449317137_thumb.png

Code lives in the spoiler.

/**
 * How to ?
 * 
 * - Create /site/init.php
 * - Place this code in the init.php
 * - Change $wanted_field to the fieldname of the TableField
 * - Change $wanted_page_id to the id of the page where the TableField lives
 *
 */
 
wire()->addHookAfter('InputfieldTable::render', function($event) {
    
    if (wire('user')->isSuperuser()) return;
    
    // What field
    $wanted_field = 'table_settings';
    // On what page ?
    $wanted_page_id = 1;
    $inputfield = $event->object;
 
    if ($inputfield->name !== $wanted_field) return;
    if (wire('input')->get->id != $wanted_page_id) return;
 
    $html = new DOMDocument;
    $html->loadHTML($event->return);
 
    $table = $html->getElementsByTagName('table')->item(0);
    $trs = $table->getElementsByTagName('tr');
 
    foreach ($trs as $index => $tr) {
 
        // td & th's
        $columns = $tr->childNodes;
 
        $columnMove = $columns->item(0);
        $columName  =  $columns->item(1);
        $columLabel = $columns->item(2);
        $columValue = $columns->item(3);
        $columTrash = $columns->item($columns->length - 1);
 
        // Skip table header
        if ($tr->getElementsByTagName('td')) {
            // Name column
            foreach ($columName->childNodes as $key => $child) {
                if ($child instanceof DOMElement) {
                    $columLabel->appendChild($child);
                }
            }
 
            // Trash column (append to label)
            foreach ($columTrash->getElementsByTagName('input') as $i => $child) {
                $inputfield = $html->createElement('input', $child->getAttribute('value'));
                $inputfield->setAttribute('name', $child->getAttribute('name'));
                $inputfield->setAttribute('value', $child->getAttribute('value'));
                $columLabel->appendChild($inputfield);
            }
 
            // Label column
            foreach ($columLabel->childNodes as $i => $child) {
                if ($child instanceof DOMElement) {
                    // Set all inputfields to hidden hidden
                    $child->setAttribute('type', 'hidden');
                    if ($i === 0) {
                        $label = $html->createTextNode($child->getAttribute('value'));
                        $columLabel->appendChild($label);
                    }
                }
            }
        }
 
        // Put width of name column to value column
        $widthName = (int) filter_var($columName->getAttribute('style'), FILTER_SANITIZE_NUMBER_INT);
        $widthValue = (int) filter_var($columValue->getAttribute('style'), FILTER_SANITIZE_NUMBER_INT);
        $columValue->setAttribute('style', 'width: ' . ($widthName + $widthValue) . '%;');
 
        // Remove from table column
        $tr->removeChild($columnMove);
        $tr->removeChild($columName);
        $tr->removeChild($columTrash);
    }
 
    $result = new DOMDocument;
    $result->preserveWhiteSpace = false;
    $result->appendChild($result->importNode($table, true));
    $table = $result->saveHTML();
    $markup = preg_replace('/<table(.*?)<\/table>/', $table, $event->return);
    $markup = str_replace("<p><a class='InputfieldTableAddRow' href='#'><i class='fa fa-plus-circle'></i> Add Row</a></p>", '', $markup);
 
    $event->return = $markup;
});

Maybe some time I build a Module for this one.

  • Like 7

Share this post


Link to post
Share on other sites

Sure, but that's true for every field in the admin :)

Share this post


Link to post
Share on other sites

Sure, but that's true for every field in the admin

Not in my example :-), they only can remove the value, not the variable.

  • Like 2

Share this post


Link to post
Share on other sites

@Martijn - thanks - that's very cool.  I like the idea of the label - so i'm going to see about integrating your version into my current project's settings table... It is definitely more user friendly to have the label - some clients put wrong stuff into value because they only see the variable name and sort of don't get it.

Also in newer versions of my settings i have an options column, which inside i can place pipe-delimited options, such as enabled|disabled, or true|false, or on|off; then in the value row, when you click into the cell, it brings up a select; this helps to control the spelling and to also remind people of what sort of values are acceptable for the field.

To achieve this i'm using a commercial jQuery plugin called Pickle, which lets you create a select out of any text input;(if i could find a non-commercial plugin that did the same thing i would post the instructions here)

  • Like 1

Share this post


Link to post
Share on other sites

I guess when you add an extra column to that table you can can solve it with PHP. The script loops over over each row, and you could modify it as you wish.

  • Like 1

Share this post


Link to post
Share on other sites

update - if you need to be able to access your variables inside functions or the admin you can use $config..

in config.php, setup an empty array for siteSettings like this:

$config->siteSettings = array();

then in ready.php file, array merge values from a settings table (profields table) or any other type of setting (MultiValue Textformatter, YAML settings ...), in the admin like this:

// Get your settings from wherever
$st = $pages->get('/settings/')->settings_table;

// temporary array to merge with global
$settings = array();

foreach($st as $row) {
    if(!$row->value) continue;
    if($row->disable == 1) continue;
    $settings[$row->setting] = $row->value;
}
// merge
$config->siteSettings = array_merge($config->siteSettings, $settings);

i'm curious if anyone sees any possible issues with this; it works now and i can access my siteSettings everywhere doing this;

you can access the settings like this from templates:

echo $config->siteSettings['yourKey'];

or in function..

echo wire('config')>siteSettings['yourKey'];
  • Like 1

Share this post


Link to post
Share on other sites

Curious to know what other people are doing for this now - especially in light of the new FunctionalFields feature.

Share this post


Link to post
Share on other sites

I'm using a highly modded version of ProcessGeneralSettings; got it working with most fieldtypes, so i can setup settings dashboard now and have fields for most things i need on the frontend, social media urls, metas, company info as well as visual settings; it all stores as module config, so i can have a lot of fields (50-60) without having to add those fields to PW...

  • Like 4

Share this post


Link to post
Share on other sites

@Macrura Thanks for the reply. It's good to know that you are having success with the module approach - I did take a brief look at it as I like the idea, but I ended up passing it over when I realised there was no support for images out of the box. Could you perhaps issue a pull-request to the module author with your changes?  They sound like a worthwhile addition.

  • Like 1

Share this post


Link to post
Share on other sites

it might really need to be released as a whole new module as it is massively changed;

for example, my version has 2 modules, one process module for setting up the fields and one that actually handles the object for providing the settings to the front end; has to be done like this if you want to give access to that settings panel to any other role besides superuser;

Also, i think i tried to ask some questions to the original module author but they seem to be no longer around (mostly in reference to the major conflict the original module has with the $settings global variable which is also used by ListerPro)..

The way i handle images is to have a page tree select where you select the page holding the image; this works well for me because most sites i'm building have some type of media library, so this allows users to select media pages containing stuff like logos, open graph fallback images, schema profile images etc;

  • Like 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By Fran
      Hi, I'm using this piece of code to retrieve the names of two arrays of images and it works really well. But my question is how can I use only one foreach so i can use both arrays inside of it? Thanks very much.
      $precioschico = $page->get("planos"); $preciosgrande = $page->get("mapas"); foreach( $preciosgrande as $preciogrande ) { echo $preciogrande . '<br/>'; } foreach( $precioschico as $preciochico ) { echo $preciochico . '<br/>'; }  
    • By Nukro
      $stats = array( "total" => array( "main" => array( "selector" => "template=50, parent=$formSuperSelector", "count" => $this->pages->find($stats['total']['main']['selector'])->count ), "yes" => array( "selector" => $stats['total']['main']['selector'].", teilnahme=yes", "count" => $this->pages->find($stats['total']['yes']['selector'])->count ), "no" => array( "selector" => $stats['total']['main']['selector'].", teilnahme=no", "count" => $this->pages->find($stats['total']['no']['selector'])->count ) ), "intern" => array( "main" => array( "selector" => "template=50, formtype=intern, parent=$formSuperSelector", "count" => $this->pages->find($stats['intern']['main']['selector'])->count ), "yes" => array( "selector" => $stats['intern']['main']['selector'].", teilnahme=yes", "count" => $this->pages->find($stats['intern']['yes']['selector'])->count ), "no" => array( "selector" => $stats['intern']['main']['selector'].", teilnahme=no", "count" => $this->pages->find($stats['intern']['no']['selector'])->count ) ), "extern" => array( "main" => array( "selector" => "template=50, formtype=extern, parent=$formSuperSelector", "count" => $this->pages->find($stats['extern']['main']['selector'])->count ), "yes" => array( "selector" => $stats['extern']['main']['selector'].", teilnahme=yes", "count" => $this->pages->find($stats['extern']['yes']['selector'])->count ), "no" => array( "selector" => $stats['extern']['main']['selector'].", teilnahme=no", "count" => $this->pages->find($stats['extern']['no']['selector'])->count ) ), ); var_dump($stats['total']['main']['count']); How can I bring this to work? I always get a "Notice: Undefined variable: stats in..." when trying to access:
      "count" => $this->pages->find($stats['intern']['yes']['selector'])->count or
      "selector" => $stats['intern']['main']['selector'].", teilnahme=yes",
    • By benbyf
      Hi!
      I'm writing some tutorials for Tuts+ and I'll put some information here when I have it. Love to hear your suggestions, and I think a couple of other people are wirting tutorials there and of course many other places.
    • By jean-luc
        Docker (http://www.docker.com) is an open platform for building, shipping and running distributed applications.   Docker containers are a great way to package a complete application with its specific dependencies in a portable way so that it can easily be deployed on any compatible network or cloud infrastructure.   Recently I spent a few days making my ProcessWire site run in a Docker container, and - as I could not find any good tutorial for this - it sounded like a good idea to write one.    You will find on the web plenty of presentations and tutorials about Docker, so I won't start with the basic concepts, and this tuto assumes that you have a first understanding of Docker's fundamentals.   What we want to do here is to migrate an existing site to a set of docker containers.   Therefore, to start with, you should have: - docker installed on your computer; - the site directory of your ProcessWIre site - a backup of your site's MySQL database   Let's start.   Create a docker container for the site database   For several reasons (insulation, security, scalability), it is preferable to host the site database in a separate docker container.    1. Set-up a SQL database with MariaDb or MySQL $ docker run --name database -e MYSQL_ROOT_PASSWORD=rootdbpassword -d mariadb Here I choose to use the MariaDB official container in its latest version, but MySQLwould be just fine as well.   2. Run a PhpMyAdmin container and create the ProcessWire database      We first select an simple image with PhpMyAdmin on the Docker Hub: nazarpc/phpmyadmin and we create a docker container based on this image. This container will  access the port exposed by the database container via a private networking interface. We specify this with the `--link` option.   It can be run temporarily (and exited by ctrl-C): docker run --rm --link database:mysql -p 8881:80 nazarpc/phpmyadmin Or it can be run as a daemon in the background: docker run -d --name phpmyadmin --link database:mysql -p 8881:80 nazarpc/phpmyadmin From phpmyadmin (accessed from your browser at http://hostaddress:8881) you can now create your ProcessWire database, create a dedicated user for it, and import the database content from a previously saved SQL file.   Note: alternatively, you can do all database operations from the command line in the database docker container created during step 1, or use another mysql user interface container if you prefer…   3. Update the database parameters in your site configuration   In your site's `config.php` file, the sql server name shall be set to `mysql`: $config->dbHost = 'mysql'; Other `$config->dbXxx` settings shall match the database name, user and password of the just-created database.     Create a Docker Image for Apache, PHP and the Processwire site   1. Create an image-specific directory with the following contents and `cd` to it bash-3.2$ ls -l . config .: total 16 -rw-rw-rw-   1 jean-luc  staff  1163 21 aoû 12:09 Dockerfile drwxr-xr-x  17 jean-luc  staff   578 17 aoû 12:48 ProcessWire drwxr-xr-x   7 jean-luc  staff   238 21 aoû 12:07 config drwxr-xr-x   7 jean-luc  staff   238 20 aoû 18:46 site config: total 160 -rw-rw-rw-  1 jean-luc  staff    160 20 aoû 18:28 msmtprc -rw-rw-rw-  1 jean-luc  staff  72518 20 aoû 18:56 php.ini where: `ProcessWire` contains the version of ProcessWire that we want to use for this site;  
      It can be retrieved from github with a link like https://github.com/ryancramerdesign/ProcessWire/archive/{version}.zip`  
      For example, the 2.6.13 dev version can be obtained by the link https://github.com/ryancramerdesign/ProcessWire/archive/7d37db8d6b4ca6a132e50aff496a70e48fcd2284.zip `site`: our site-specific files `Dockerfile`: the dockerfile for building the image (see below) `config`: a directory containing specific configuration files copied to the docker image (see below) 2.  Set the `Dockerfile` content FROM php:5.6-apache RUN    apt-get update \ && apt-get install -y libfreetype6-dev libjpeg62-turbo-dev libmcrypt-dev libpng12-dev zziplib-bin msmtp\ && a2enmod rewrite \ && a2enmod ssl \ && docker-php-ext-install mysqli pdo_mysql iconv mcrypt zip \ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \ && docker-php-ext-install gd  EXPOSE 80 EXPOSE 443 # Add a specific php.ini file COPY config/php.ini /usr/local/etc/php/ # Configure the mail sent utility msmtp (http://msmtp.sourceforge.net) and make it readable only by www-data COPY config/msmtprc /usr/local/etc/php/ RUN chmod 600 /usr/local/etc/php/msmtprc \ && chown www-data:www-data /usr/local/etc/php/msmtprc # Remove all default site files in /var/www/html RUN rm -fR /var/www/html/* # Copy ProcessWire core files COPY ProcessWire/wire /var/www/html/wire COPY ProcessWire/index.php /var/www/html/index.php COPY ProcessWire/htaccess.txt /var/www/html/.htaccess # Copy site-specific files COPY site /var/www/html/site # Make www-data the owner of site-specific files RUN chown -R www-data:www-data /var/www/html/site VOLUME /var/www/html/site Based on the official image `php:5.6-apache`, it installs missing packages to the system, adds  mod-rewrite and mod-ssl to Apache, plus a number of PHP modules needed by Processwire (core or modules): mysqli, pdo_mysql, iconv, mcrypt, zip, and gd.   Then it copies the site files to the location expected by the Apache server.  Finally it declares a Docker volume `/var/www/html/site` (i.e. the site files and assets), so that it can be shared with other containers.   3. Set the msmtp configuration   We need to configure a sendmail utility, so that we can send emails from php, for example when a user registers on the website. The simplest way to do it is to rely on an external smtp server to do the actual sending. That's why we use msmtp.   - define the desired smtp account in `config/msmtprc` account celedev-webmaster tls on tls_certcheck off auth on host smtp.celedev.com port 587 user webmaster@celedev.com from webmaster@celedev.com password thepasswordofwebmasteratceledevdotcom   - in `config/php.ini`, configure the sendmail command so it uses msmtp: sendmail_path = /usr/bin/msmtp -C /usr/local/etc/php/msmtprc --logfile /var/log/msmtp.log -a celedev-webmaster -t   4. Build the Docker image docker build -t php-5.6-pw-celedev . 5. Create a Data-only container for the site files docker run --name celedev-data php-5.6-pw-celedev echo "Celedev site data-only container" 6. Run the web server container docker run --name celedev-site -p 8088:80 --link database:mysql --volumes-from celedev-data -d php-5.6-pw-celedev Note that this container is linked to our database and shares the 'celedev-data' volume created previously   During development, it can be convenient to keep an access to the host file system from the container. For this, we can add a shared volume to the previous command: docker run --name celedev-site -p 8088:80 --link database:mysql -v /Users/jean-luc/web/test-docker:/hostdir --volumes-from celedev-data -d php-5.6-pw-celedev   Our ProcessWire website is now up and running and we can test it in our browser at http://hostaddress:8088. Great!   What we now have in Docker bash-3.2$ docker images REPOSITORY            TAG                 IMAGE ID            CREATED             VIRTUAL SIZE php-5.6-pw-celedev    latest              2aaeb241c2e2        3 hours ago         1.149 GB nazarpc/phpmyadmin    latest              e25cd4fd48b3        8 days ago          521 MB mariadb               latest              dd208bafcc33        2 weeks ago         302.2 MB debian                latest              9a61b6b1315e        5 weeks ago         125.2 MB bash-3.2$ docker ps -a CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                    PORTS                                         NAMES 68cc5d976f0d        php-5.6-pw-celedev    "apache2-foreground"     20 hours ago        Up 20 hours               443/tcp, 0.0.0.0:8088->80/tcp                 celedev-site 0729fe6d6752        php-5.6-pw-celedev    "echo 'Celedev site d"   20 hours ago        Exited (0) 20 hours ago                                                 celedev-data e3e9e3a4715c        mariadb               "/docker-entrypoint.s"   3 days ago          Up 3 days                 3306/tcp                                      database Saving the site data   We can create an archive of the site files by running a tar command in a dedicated container: bash-3.2$ docker run --rm -it --volumes-from celedev-data -v /Users/jean-luc/web/test-docker:/hostdir debian /bin/bash root@2973c5af3eaf:/# cd /var/www/html/ root@2973c5af3eaf:/var/www/html# tar cvf /hostdir/backup.tar site root@2973c5af3eaf:exit bash-3.2$ Tagging and archiving the Docker image   We can also add a tag to the docker image that we have created in step 4 (recommended): bash-3.2$ docker tag 2aaeb241c2e2 php-5.6-pw-celedev:0.11 bash-3.2$ docker images REPOSITORY            TAG                 IMAGE ID            CREATED             VIRTUAL SIZE php-5.6-pw-celedev    latest              2aaeb241c2e2        3 hours ago         1.149 GB php-5.6-pw-celedev    0.11                2aaeb241c2e2        3 hours ago         1.149 GB nazarpc/phpmyadmin    latest              e25cd4fd48b3        8 days ago          521 MB mariadb               latest              dd208bafcc33        2 weeks ago         302.2 MB And we can archive this image locally if we dont want to push it now to the Docker Hub: bash-3.2$ docker save php-5.6-pw-celedev:0.11 | gzip > php-5.6-pw-celedev-0.11.tar.gz  And that's it!
      You now have a portable image of your ProcessWire website that you can run directly on any docker-compatible system.
    • By gemini
      The tutorials for getting started were excellent resources. Awesome starter to go with a solid core. 
      loving PW.