The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

App::Office::Contacts - A web-based contacts manager

Synopsis

A classic CGI script, contacts.cgi:

        #!/usr/bin/env perl

        use strict;
        use warnings;

        use CGI;
        use CGI::Snapp::Dispatch;

        # ---------------------

        my($cgi) = CGI -> new;

        CGI::Snapp::Dispatch -> new -> dispatch
        (
                args_to_new => {QUERY => $cgi},
                prefix      => 'App::Office::Contacts::Controller',
                table       =>
                [
                ''              => {app => 'Initialize', rm => 'display'},
                ':app'          => {rm => 'display'},
                ':app/:rm/:id?' => {},
                ],
        );

A Plack script, contacts.psgi:

        #!/usr/bin/env perl
        #
        # Run with:
        # starman -l 127.0.0.1:5003 --workers 1 httpd/cgi-bin/office/contacts.psgi &
        # or, for more debug output:
        # plackup -l 127.0.0.1:5003 httpd/cgi-bin/office/contacts.psgi &

        use strict;
        use warnings;

        use CGI::Snapp::Dispatch;

        use Plack::Builder;

        # ---------------------

        my($app) = CGI::Snapp::Dispatch -> new -> as_psgi
        (
                prefix => 'App::Office::Contacts::Controller',
                table  =>
                [
                ''              => {app => 'Initialize', rm => 'display'},
                ':app'          => {rm => 'display'},
                ':app/:rm/:id?' => {},
                ],
        );

        builder
        {
                enable "ContentLength";
                enable 'Static',
                path => qr!^/(assets|favicon)!,
                root => '/dev/shm/html';
                $app;
        };

The scripts discussed here, contacts.cgi and contacts.psgi, are shipped with this module, in the httpd/ directory.

For more on Plack, see My intro to Plack.

Description

App::Office::Contacts implements a utf8-aware, web-based, private and group contacts manager.

Here 'private' means you can specify which contacts are not to appear in the search results of other people using the same database. You do this by setting their visibility to 'Just me'.

App::Office::Contacts uses the light-weight module Moo.

Major features:

o utf8-aware
o Any number of people
o Any number of organizations
o People can have any number of occupations
o Organizations can have any number of staff
o People and organizations can have any number of notes

These are displayed with the most recent notes first.

o Supports using any database server having a Perl interface

This is controlled via a config file.

o 1 to 4 email addresses per person or organization

4 was chosen just to limit the amount of screen real estate occupied. It can be easily changed.

o 1 to 4 phone numbers per person or organization
o Installers can provide their own FAQ page
o On-screen information hidden in tabs is updated if appropriate

For example, if you add a person to the staff list for an organization, and the details for that person are on another, hidden, tab (the organization tab must have the focus), then the list of occupations for that peson is updated as soon as they are added.

o jQuery-style autocomplete is used for various fields

The list of fields which support autocomplete are listed both on the appropriate forms and on the default FAQ page.

o An add-on package supports importing vCards, as probably output by your email client
o An add-on package supports donations per person and per organization

But App::Office::Contacts::Donations has not yet been updated to match V 2.00 of App::Office::Contacts.

Screen shots:

The database schema.

Sample search results.

Sample personal details. The organizational details form is very similar.

Distributions

This module is available as a Unix-style distro (*.tgz).

See http://savage.net.au/Perl-modules/html/installing-a-module.html for help on unpacking and installing distros.

Installation

Installation Pre-requisites

A note to beginners

At various places I refer to a file, share/.htoffice.contacts.conf, shipped with this distro.

Please realize that if you edit this file, you must ensure the copy you are editing is the one used by the code at run-time.

After a module such as this is installed, the code will look for that file in the directory where you have installed this config file, by running:

        shell> perl scripts/copy.config.pl

The module which reads the file is App::Office::Contacts::Util::Config.

scripts/copy.config.pl installs .htoffice.contacts.conf into a shared directory.

So, if you unpack the distro and edit the file within the unpacked code, you will still need to copy the patched version by running:

        shell> perl scripts/copy.config.pl
        shell> perl scripts/find.config.pl (as a cross-check)

Alternately, edit the installed copy rather than the copy shipped with the distro.

There is no need to restart your web server after updating this file.

jQuery, jQuery UI and DataTables

This module does not ship with any of these Javascript libraries. You can get them from:

        http://jquery.com/
        http://jqueryui.com/
        http://datatables.net/

Most development was done using jQuery V 1.8.1, which ships with jQuery V 1.9.2. Lastly, DataTables V 1.9.4 was used too.

See share/.htoffice.contacts.conf, around lines 23 .. 25 and 61 .. 63, where it specifies the URLs used by the code to access these libs.

As always, do this after patching the config file:

        shell> perl scripts/copy.config.pl
        shell> perl scripts/find.config.pl (as a cross-check)

Alternately, edit the installed copy rather than the copy shipped with the distro.

The database server

I use Postgres.

So, I create a user and a database, via psql, using:

        shell>psql -U postgres
        psql>create role contact login password 'contact';
        psql>create database contacts owner contact encoding 'UTF8';
        psql>\q

Then, to view the database after using the shipped Perl scripts to create and populate it:

        shell>psql -U contact contacts
        (password...)
        psql>...

If you use another server, patch share/.htoffice.contacts.conf, around lines 22 and 36, where it specifies the database DSN and the CGI::Session driver.

As always, do this after patching the config file:

        shell> perl scripts/copy.config.pl
        shell> perl scripts/find.config.pl (as a cross-check)

Alternately, edit the installed copy rather than the copy shipped with the distro.

Installing the module

The Module Itself

Install App::Office::Contacts as you would for any Perl module:

Run:

        cpanm App::Office::Contacts

or run

        sudo cpan App::Office::Contacts

or unpack the distro, and then either:

        perl Makefile.PL
        make (or dmake)
        make test
        make install

Either way, you need to install all the other files which are shipped in the distro.

Install the Text::Xslate files

Copy the htdocs/assets/ directory, and all its subdirectories, from the distro to the doc root directory of your web server.

Specifically, my doc root is /dev/shm/html/, so I end up with /dev/shm/html/assets/.

The Configuration File

Next, tell App::Office::Contacts your values for some options. This includes the path to the files used by Text::Xslate.

For that, see share/.htoffice.contacts.conf as discussed above.

After editing the config file, ensure you run scripts/copy.config.pl. It will copy the config file using File::ShareDir, to a directory where the run-time code in App::Office::Contacts will look for it.

        shell>cd App-Office-Contacts-1.00
        shell>perl scripts/copy.config.pl
        shell>perl scripts/find.config.pl

Alternately, edit the installed copy rather than the copy shipped with the distro.

Install the FAQ web page

In share/.htoffice.contacts.conf there is a line:

        program_faq_url = /assets/templates/app/office/contacts/faq.html

This page is displayed when the user clicks FAQ on the About tab.

A sample page is shipped in htdocs/assets/templates/app/office/contacts/faq.html.

So, copying the htdocs/assets/ directory, as above, will have installed this file. Alternately, replace it with your own.

As always after editing the config file, run:

        shell> perl scripts/copy.config.pl
        shell> perl scripts/find.config.pl (as a cross-check)

Alternately, edit the installed copy rather than the copy shipped with the distro.

Install the trivial CGI script and the Plack script

Copy the httpd/cgi-bin/office/ directory to the cgi-bin/ directory of your web server, and make contacts.cgi executable.

My cgi-bin/ dir is /usr/lib/cgi-bin/, so I end up with /usr/lib/cgi-bin/office/contacts.cgi.

Now I can run http://127.0.0.1/cgi-bin/office/contacts.cgi (but not yet!).

Creating and populating the database

The distro contains a set of text files which are used to populate constant tables. All such data is in the data/ directory.

This data is loaded into the 'contacts' database using programs in the distro. All such programs are in the scripts/ directory.

After unpacking the distro, create and populate the database:

        shell>cd App-Office-Contacts-1.00
        # Naturally, you only drop /pre-existing/ tables :-),
        # so use drop.tables.pl later, when re-building the db.
        #shell>perl -Ilib scripts/drop.tables.pl -v
        shell>perl -Ilib scripts/create.tables.pl -v
        shell>perl -Ilib scripts/populate.tables.pl -v
        shell>perl -Ilib scripts/populate.fake.data.pl -v

Notes:

If using -Ilib, Perl looks in the current directory structure for the modules

That is, Perl does not use the installed version of the code, if any.

The code looks in the shared directory structure for .htoffice.contacts.conf

If you unpack the distro, and run:

        shell> perl scripts/copy.config.pl
        shell> perl scripts/find.config.pl (as a cross-check)

it will copy the config file to the install dir, and report where it is.

Alternately, edit the installed copy rather than the copy shipped with the distro.

So, if you leave out the '-Ilib', Perl will use the version of the code which has been formally installed.

Start testing

Point your broswer at http://127.0.0.1/cgi-bin/contacts.cgi.

Your first search can then be just 'a', without the quotes.

Object attributes

o See the parent module CGI::Snapp

Methods

build_about_html($user_id)

Creates a HTML table for the About tab.

Note: The code does not currently use $user_id. It is present as provision if the code is patched to identify logged-on users. See the "FAQ" for a discussion of this issue.

build_web_page()

Creates the basic web page in response to the very first request from the user.

global_prerun()

Contains code shared by this module and App::Office::Contacts::Donations.

teardown()

Shuts down database connexions, etc, as the program is exiting.

FAQ

How do I delete an organization or person?

Search for them, and then set their visibility to No-one. Hence they stay in the database but are no longer visible.

Is utf8 supported in V 2.00?

Yes. Text::CSV::Encoded is used in App::Office::Contacts::Util::Import to read data/fake.people.txt.

See "Creating and populating the database" for a discussion of scripts/populate.fake.people.pl.

Do a search for Brocard, the author of the original GraphViz, and you will find Léon Brocard.

Also, see lines 48 .. 52 in the config file for options to control the utf8 setting in the connect() attributes as used by DBI. These are the defaults:

        mysql_enable_utf8 = 1
        # pg_enable_utf8 == 0 for DBD::Pg V 3.0.0 in my code.
        pg_enable_utf8    = 0
        sqlite_unicode    = 1

These values are used in App::Office::Contacts::Util::Logger lines 44 .. 57.

Why not allow multiple Facebook and Twitter tags per org or person?

This is under consideration.

How can I update the spouses table?

You cannot. I have not yet decided how to provide an on-screen mechanism to update this table.

How is the code structured?

MVC (Model-View-Controller).

The sample scripts contacts.cgi and contacts use

        prefix => 'App::Office::Contacts::Controller'

so the files in lib/App/Office/Contacts/Controller and lib/App/Office/Contacts/Controller/Exporter are the modules which are run to respond to http requests.

Files in lib/App/Office/Contacts/View implement views, and those in lib/App/Office/Contacts/Database implement the model.

Files in lib/App/Office/Contacts/Util are a mixture:

Config.pm

This is used by all code.

Create.pm

This is just used to create tables, populate them, and drop them.

Hence it will not be used by CGI scripts, unless you write such a script yourself.

Validator.pm

This is used to validate CGI form data.

Why did you use Sub::Exporter?

The way I wrote the code, various pairs of classes, e.g. App::Office::Contacts::Controller::Note and App::Office::Contacts::Donations::Controller::Note, could share a lot of code, but they had incompatible parents. Sub::Exporter solved this problem.

And since Controller.pm is derived from CGI::Snapp and not Moo, we cannot use Moo::Role.

In the source, it seems you use singular words for the names of arrays and array refs.

Yes I do. I think in terms of the nature of each element, not the storage mechanism.

I have switched to plurals for the names of database tables though.

What is the database schema?

The database schema.

The file was created with dbigraph.pl.

dbigraph.pl ships with GraphViz::DBI. I patched it to use GraphViz::DBI::General.

The command is:

        dbigraph.pl --dsn 'dbi:Pg:dbname=contacts' --user contact --pass contact > docs/contacts.schema.png

The username and password are as shipped in share/.htapp.office.contacts.conf.

As always after editing the config file, run:

        shell> perl scripts/copy.config.pl
        shell> perl scripts/find.config.pl (as a cross-check)

Alternately, edit the installed copy rather than the copy shipped with the distro.

Why do the email_addresses and phone_numbers tables have upper-case fields?

Because the search feature always uses upper-case. And, e.g., phones can have eXtension information built-in, as in '123456x78'. So the 'x' in a search request needs to be upper-cased. And yes, I have worked on a personnel + phone number system (at Monash University) which stores (Malaysian) phone numbers like that.

The case for email addresses is rather more obvious.

Does the database server have pre-requisites?

The code is DBI-based, of course.

Also, the code assumes the database server supports $dbh -> last_insert_id(undef, undef, $table_name, undef).

What engine type do you use when I use MySQL?

Engine type defaults to innodb when you use MySQL in the dsn.

See share/.htapp.office.contacts.conf for the dsn and the source code of App::Office::Contacts::Util::Create for the create statements.

As always after editing the config file, run:

        shell> perl scripts/copy.config.pl
        shell> perl scripts/find.config.pl (as a cross-check)

Alternately, edit the installed copy rather than the copy shipped with the distro.

How do I add tables to the schema?

Do all of these things:

o Choose a new name which does not conflict with names used by my add-on packages!
o Add the table initialization code to App::Office::Contacts::Util::Create

You will need code to create, drop and (perhaps) populate your new table.

There are many examples already in that module.

o Add your code to utilize the new table

Please explain the program, text file, and database table names

Programs are shipped in scripts/, and data files in data/.

I prefer to use '.' to separate words in the names of programs.

However, for database table names, I use '_' in case '.' would case problems.

Programs such as mail.labels.pl and populate.tables.pl, use table names for their data file names. Hence the '_' in the names of their data files.

Where do I get data for Localities and Postcodes?

In Australia, a list of localities and postcodes is available from http://www1.auspost.com.au/postcodes/.

In America, you can buy a list from companies such as http://www.zipcodeworld.com/index.htm, who are an official re-seller of US Mail database.

The licence says the list cannot be passed on in its original format, but encoding it with DBD::SQLite solves that problem :-).

Is printing supported?

Not specifically, although a huge range of labels is supported via PostScript::MailLabels.

Printing might one day be shipped as App::Office::Contacts::Export::StickyLabels.

What is it with user_id and creator_id?

Ahhh, you have been reading the source code, eh? Well done!

Originally (i.e. in my home-use module Local::Contacts), users had to log on to use this code.

So, there was a known user at all times, and the modules used user_id to identify that user.

Then, when records in (some) tables were created, the value of user_id was stored in the creator_id field.

Now I take the view that you should implement Single Sign-on, meaning this set of modules is never responsible to tracking who is logged on.

Hence this line in App::Office::Contacts::Controller:

        $self -> param(user_id => 0); # 0 means we don't have anyone logged on.

That in turn means there is now no knowledge of the id of the user who is logged on, if any.

To match this, various table definitions have been changed, so that instead of App::Office::Contacts::Util::Create using:

        creator_id integer not null, references people(id),

the code says:

        creator_id integer not null,

This allows a user_id of 0 to be stored in those tables.

Also, the transaction logging code (since deleted) could identify the user who made each edit.

What is special about Person id == 1?

Nothing. Very early versions of the code reserved this id, but that is not done now.

What about Occupation title id == 1?

In a similar manner (to Person id == 1), there is a special occupation title with id == 1, whose name is '-'.

This allow you to indicate someone works for a given organization without knowing exactly what their job is.

You can search for all such special code with 'ack Special'. ack is part of App::Ack.

Do not delete this occupation! It is needed. The delete/update occupation code checks to ensure you do not delete it with this module, but of course there is always the possibility that you delete it using some other tool.

What about Organization id == 1?

In a similar manner (to Occupation id == 1), there is a special organization with id == 1, whose name is '-'.

You can search for all such special code with 'ack Special'. ack is part of App::Ack.

Do not delete this organization! It is needed. The delete/update organization code checks to ensure you do not delete it with this module, but of course there is always the possibility that you delete it using some other tool.

What data files have fake data in them?

Their names match "data/fake.$table_name.txt".

Why use File::ShareDir and not File::HomeDir?

Some CPAN testers test with users who do not have home directories.

How many database handles are used?

2. One for DBIx::Simple (See App::Office::Contacts::Util::Logger), which is used throughout App::Office::Contacts::Database, and one for Log::Handler::Output::DBI (for which also see App::Office::Contacts::Util::Logger), which is used just for logging.

What scripts ship with this module?

All scripts are shipped in the scripts/ directory.

o check.org.cgi.fields.pl

This compares the CGI form field names in the add_org_form CGI form to their equivalents in the Javascript in htdocs/assets/templates/app/office/contacts/homepage.tx, and reports discrepancies.

The form is shipped in docs/add.organization.form.html which I copied from the web page displayed when the program starts.

o check.template.pl

This just prints the output of a HTML template, to help debugging.

o copy.config.pl

This copy share/.htapp.office.contactcs.conf to a shared directory, as per the dist_dir() method in File::ShareDir.

o create.tables.pl

This creates the database tables. See "Creating and populating the database".

o drop.tables.pl

This drops the database tables. See "Creating and populating the database".

o export.as.csv.pl

This exports just the name and upper-case name from the people table. This is not really useful, but does provide a template if you wish to expand the code.

It outputs to the file specified by the output_file option.

o export.as.html.pl

This exports just the name and upper-case name from the people table. This is not really useful, but does provide a template if you wish to expand the code.

It has a standalone_page option for using either a web page or just a table as the template.

It outputs a string.

o find.config.pl

This tells you where share/.htapp.office.contactcs.conf is installed, after running copy.config.pl.

o populate.db.sh

A bash script which runs a set of programs.

Warning: This includes drop.tables.pl.

o populate.fake.data.pl

This populates some database tables. See "Creating and populating the database".

o populate.tables.pl

This populates vital database tables. See "Creating and populating the database".

o utf8.1.pl

This helps me fight the dread utf8.

o utf8.2.pl

This prints singly- and doubly-encoded and decoded string, as a debugging aid.

TODO

o report.tx has a hidden field 'report_id'

This will be replaced with a menu when donations are re-implemented in V 2.00 of *::Donations.

o Adjust focus after Enter is hit inputting occupations

Currently, the focus goes to the Reset button and not the Add button in these cases (Occupation and Staff).

o If Search or Report get an error, the status line turns red (good) but still says OK (bad).

The error message is lost in these cases, and I cannot explain that.

o Should basic.table.tx be used instead of incorporating HTML in the source code?

See View::*::format_*().

The 2 enclosing divs in basic.table.tx could be optional, perhaps via a separate template.

o Some View::*::report_*() methods do too much

Code could be shifted into Database::*::save_*().

o Add date-of-birth
o Re-write App::Office::Contacts::Donations for V 2.00
o Re-write App::Office::Contacts::Import::vCards for V 2.00

Done.

o Write App::Office::Contacts::Sites V 2.00

This provides N sites per person or organization.

The country/state/locality/postcode (zipcode) data will be shipped in SQLite format, as part of this module.

Data for Australia and America with be included in the distro.

Note: The country/etc data is imported into whatever database you choose to use for your contacts database, even if that is another SQLite database.

Support

Email the author, or log a bug on RT:

https://rt.cpan.org/Public/Dist/Display.html?Name=App-Office-Contacts.

Author

App::Office::Contacts was written by Ron Savage <ron@savage.net.au> in 2009.

Home page.

Copyright

Australian copyright (c) 2013, Ron Savage. All Programs of mine are 'OSI Certified Open Source Software'; you can redistribute them and/or modify them under the terms of The Artistic License V 2, a copy of which is available at: http://www.opensource.org/licenses/index.html