class for performing installation tasks
Overview
Dialog screens
The installer basically consists of some six dialog screens where the user is supposed to enter some data (e.g. language, install type, etc.). Each of those screens has a [Next] button and most screens have a [Previous] button. Also, every screen has a [Cancel] button. The [Cancel] button always immediately leads to the Cancel screen and the whole process is stopped (by resetting the collected information in $_SESSION['INSTALL']). The [Next] button always validates/processes the data the user entered. If the results are good, we go to the next step. If the processing fails, we stay where we are (ie. the current dialog is re-displayed). The [Previous] button backs up one step, without saving or storing the user entered data; the user MUST press [Next] to save the data entered.
The Finish-dialog
The FINISH-dialog has no [Previous] button, because all the real work is done when the user presses [Next] in the CONFIRM-dialog. This is a one-time action (creating tables, filling with demodata, etc.) so it makes no sense to backup at that point.
The stage variable and backing up via the menu
The variable 'stage' moves along with the highest dialog the user has successfully reached. This variable is responsible for greying out/disabling the menu options. The menu can be used to jump back a few steps in the procedure. However, once the transition from CONFIRM to FINISH is made, it is no longer possible to return to previous steps (it makes no sense to do so because at that point the real work is already done). The jump to a particular step/dialog is done via the GET-parameter 'step'. The buttons all work via the POST'ed parameter dialog.
Special cases
There are a few special cases:
Located in /program/install.php (line 112)
constructor
this constructs the install wizard and also makes sure that the INSTALL-array (kept in the $_SESSION array) is initialised with default values if it did not already exist.
Also we check if the user wants to set or change the debug value. If set to non-0, we show extra information lateron (and also set the parameter in $CFG in config.php.
Finally, we do record the microtime as early as possible, to calculate elapsed time after we are all done.
construct a link to appropriate legal notices as per AGPLv3 section 5
This routine constructs ready-to-use HTML-code for a link to the Appropriate Legal Notices, which are to be found in /program/about.html. Depending on the highvisibility flag we either generate a text-based link or a clickabel image.
The actual text / image to use depends on the global constant WAS_ORIGINAL. This constant is defined in /program/version.php and it should be TRUE for the original version of Website@School and FALSE for modified versions.
In the former case the anchor looks like 'Powered by Website@School', in the latter case it will look like 'Based on Website@School', which is in line with the requirements from the license agreement for Website@School, see /program/license.html.
IMPORTANT NOTE
Please respect the license agreement and change the definition of WAS_ORIGINAL to FALSE if you modify this program (see /program/version.php). You also should change the file '/program/about.html' and add a 'prominent notice' of your modifications.
Note: a comparable routine can be found in waslib.php.
shorthand for creating a submit button in the correct style
check certain compatibility issues and optionally return test results
this routine performs a few tests and returns an overall go/nogo signal Human readable test results are stored in $this-results. Return TRUE on passing all tests, FALSE otherwise + errors in $this->messages
check for name clash of new user (webmaster) and user accounts from demodata
This routine checks to see if the name the webmaster supplied is not one of the demodata accounts. If so, we flag the error in the messages.
Note: The list of accounts must be updated whenever the demodata is updated. This is a kludge but I'll leave it this way for the time being. See also the main file demodata.php.
check if the user accepts the licences
This is the companion routine for show_dialog_license(). It checks whether the user dutyfully typed 'I agree'. Note that we check caseINsensitive, so 'i agree' is acceptable, as is 'I aGrEe'. Also note that we accept non-ascii UTF-8, say "J'accept'ee" or "J'ACCEPT'EE" which has an e-acute. Case folding to lowercase accepts all those variants.
shorthand to check the validation status of the relevant dialogs
this checks the various validation flags. If a flag is false, the corresponding error message is added to $this->messages and the function returns FALSE.
try to locate clamdscan or clamscan on the server
This routine checks to see if either clamdscan or clamscan can be found somewhere. This is done via educated guessing. On success we return the path to the binary program file in $clamscan_path and the output of the version command in $clamscan_version. The funtion returns TRUE if we did find the clamscan program.
Note that we scan the directories with opendir/readdir/closedir rather than rely on file_exists() etc. because we would not find binaries with permissions 0711, which would otherwise be perfectly acceptable to execute with exec().
Also note that we suppress PHP-error messages that might be visible when we tread outside the open_basedir.
prepare a configuration file based on the collected information
create tables in database via include()'ing a file with tabledefs
Calculate the difference between two microtimes (borrowed from init.php)
unset installation data, end session and redirect the user to elsewhere
this redirects the user to one of the possible destinations as selected in the finish dialog (see show_dialog_finish()). Also, the INSTALL-array is unset, effectively erasing the collected data from the session. Subsequently the session cookie is reset, effectively ending the session. The effect is that the user has to start over if she returns to install.php. (The equivalent of 'logging off').
increment the error counter and perhaps slow things down
this routine counts the number of errors. It is used to count the number of attempts to guess a valid host/username/passord triplet. If the number of errors reaches 3, a delay is added (the installation program sleeps for a while). On every additional error an extra delay is added (3 seconds at a time) until the total delay reaches 24 seconds. At that point the collected data are reset and effectively the user has to start over.
reset the error counter
this routine resets the effect of errorcount_bump().
helper to retrieve the text of the LICENSE AGREEMENT for Website
retrieve information about GD and supported graphics file formats
this routine determines whether GD is enabled and which graphics file formats are supported (check for GIF, JPG and PNG). If GD is not installed or if none of these three formats are supported, the routine returns FALSE. Details (version number, supported formats) are returned in &$details.
GIF is a special case due to patent issues: there is a distinction between read support and write support (see the PHP-documentation for details). GD-version >= 2.0.28 provides full support. This routine makes the distinction between fully supported (GIF: Yes), reading but not writing (GIF: Readonly) and not supported (GIF: No).
There is an issue with the return value of gd_info(). Here are two real world examples:
PHP 4.3.6 (Linux): gdinfo = Array ( [GD Version] => bundled (2.0.22 compatible) [FreeType Support] => 1 [FreeType Linkage] => with freetype [T1Lib Support] => [GIF Read Support] => 1 [GIF Create Support] => [JPG Support] => 1 [PNG Support] => 1 [WBMP Support] => 1 [XBM Support] => 1 [JIS-mapped Japanese Font Support] => )
PHP 5.3.5 (XAMPP): gdinfo = Array ( [GD Version] => bundled (2.0.34 compatible) [FreeType Support] => 1 [FreeType Linkage] => with freetype [T1Lib Support] => [GIF Read Support] => 1 [GIF Create Support] => 1 [JPEG Support] => 1 [PNG Support] => 1 [WBMP Support] => 1 [XPM Support] => [XBM Support] => 1 [JIS-mapped Japanese Font Support] => )
Note the subtle difference between 'JPG Support' and 'JPEG Support'. Checking for the first one of those yields a PHP Notice: "Undefined index: JPG Support (...)" in the second installation. *sigh* We work around it with isset(), on _all_ elements of gdinfo() to be certain.
return an array with default configuration values
this routine tries to calculate/guess the best default values for config.php. We do so by looking in the global $_SERVER variable. If that doesn't work, we simply make up sensible values. In the end it is up to the user to enter the correct data; the values here are mere defaults.
A first-time install should be possible without changing/editing the default values, i.e. a standard Next-Next-Finish-type of installation.
fill an array with necessary information for the cms dialog
Note that this is a very light-weight implentation of the dialogdef idea used in the main program: we don't do fancy stuff with labels, hotkeys, etc. KISS, because I don't want to rely on all the libraries of the main program with all their interconnections and dependencies; the installer should more or less be a stand-alone application.
Note: the order of 'cms_demodata' and 'cms_demodata_password' is important: the field 'cms_demodata' must come first. If not, the validation of the password is not skipped if demodata is left unchecked by the user. See also save_cms().
fill an array with necessary information for the database dialog
Note that this is a very light-weight implentation of the dialogdef idea used in the main program: we don't do fancy stuff with labels, hotkeys, etc. KISS, because I don't want to rely on all the libraries of the main program with all their interconnections and dependencies; the installer should more or less be a stand-alone application.
fill an array with necessary information for finish / jump dialog
Note that this is a very light-weight implentation of the dialogdef idea used in the main program: we don't do fancy stuff with labels, hotkeys, etc. KISS, because I don't want to rely on all the libraries of the main program with all their interconnections and dependencies; the installer should more or less be a stand-alone application.
fill an array with necessary information for installtype dialog
Note that this is a very light-weight implentation of the dialogdef idea used in the main program: we don't do fancy stuff with labels, hotkeys, etc. KISS, because I don't want to rely on all the libraries of the main program with all their interconnections and dependencies; the installer should more or less be a stand-alone application.
fill an array with necessary information for language dialog
Note that this is a very light-weight implentation of the dialogdef idea used in the main program: we don't do fancy stuff with labels, hotkeys, etc. KISS, because I don't want to rely on all the libraries of the main program with all their interconnections and dependencies; the installer should more or less be a stand-alone application.
fill an array with necessary information for the first user dialog
Note that this is a very light-weight implentation of the dialogdef idea used in the main program: we don't do fancy stuff with labels, hotkeys, etc. KISS, because I don't want to rely on all the libraries of the main program with all their interconnections and dependencies; the installer should more or less be a stand-alone application.
retrieve a list of available languages by querying the file system for install.php translation files
this routine constructs a list of language codes and language names (in the languages themselves) based on the language subdirectories available under /program/install/. The resulting array of code-name-pairs is sorted by name.
Note that because the names of the languages are expressed in the languages themselves, this routine has the side-effect of reading _all_ of the available language files into memory (see t()).
Also note that the sort order is based on UTF-8 byte sequences, i.e. the order is 7-bit ASCII bytes, followed by 2-byte, 3-byte and 4-byte UTF-8 sequences. No problem for this list of languages IMHO.
retrieve an array of manifests for modules, themes or languages
this examines the file system starting in the directory $path, looking for manifest files. These manifest files are named after the subdirectory they are in as follows. Example: If $path is /program/modules, this routine steps through that directory and may find subdirectories 'htmlpage', 'guestbook' and 'forum'. Eventually these manfest files are include()'d: /program/modules/htmlpage/htmlpage_manifest.php, /program/modules/guestbook/guestbook_manifest.php and /program/modules/forum/forum_manifest.php.
Every manifest file must describe the module (or language or theme) via the following construct:
After processing all the subdirectories of $path, the resulting array $manifests is returned. Note that pseudo-directories like '.' and '..' are not considered. Also, subdirectories 'foo' without the file 'foo_manifest.php' are also ignored.
Note that the name of the manifest file itself is also stored in the array, but excluding the subdirectory name.
construct a clickable menu which helps the user to jump back and forth in the funnel
this constructs a menu that allows the user to jump to a another step in the procedure when appropriate. Two parameters are important: the $dialog and the the $stage. The $dialog indicates the current dialog, i.e. the dialog that is currently displayed in the content area. This item in the menu is emphasised (e.g. the link is underlined via the style sheet). The $stage indicates how far we are in the procedure. The installation consists of some eight steps, and the used is encouraged to perform all steps in the natural order, by repeatedly pressing the [Next] button in the dialogs. Every time a dialog appears to have valid data, the $stage is incremented.
All the menu items after the current stage are greyed out and basically inaccessible for the user. Menu items before the current stage are accessible, so it is possible to jump backwards to dialogs that were already processed but the only way to advance to a new screen is to use the [Next] (and provide valid data, obviously).
The greyed-out menu items have a href property consisting of a simple "#". This is interpreted by the browser as a relative link within the current page. The effect is that the current page stays on the screen, including any unsaved data the user may already have entered. By showing the links in a different colour (grey and not blue), the user can visually see which items are clickable and which are not.
By showing all the menu items and greying-out the inaccessible ones we effectively build a funnel and at the same time indicating which steps will follow in the procedure.
There is a special case when $stage hits the FINISH-dialog. If the user has not yet reached that stage, all dialogs before the finish-dialog are active and the rest is greyed-out. Once the $stage reaches the FINISH-dialog, all precious dialogs become instantly unreachable. This is because the step between the confirmation dialog and the finish dialog is the place where the actual installation takes place. That is a one-time operation and the user should not be able to jump backwards and change data after all the actual installation work is already done.
Note that the menu item to download the config.php is displayed before the finish dialog. This is because it appears more logical to me (but YMMV).
construct a list of database options
This constructs an array with key-value-pairs indicating all available databases. Currently (february 2015) there is only one database supported: MySQL. However, for backwards compatibility both the 'mysqli' and the deprecated 'mysql' APIs are supported. In the future we may support more databases, such as PostgreSQL.
Note that we present ordinary users with a single choice (MySQL) while distinguishing between the two variants. Anyone using the debug-option will have an exact choice between the two flavours.
construct a complete HTML-page that can be sent to the user's browser
this routine constructs a string containing the complete page to send to the user's browser, starting with the <html> opening tag and ending with the </html> closing tag. The constructed page is returned as a string.
This routine also peeks into the INSTALL-array, e.g. for the language key and the high_visibility flag.
If $this->messages is not empty, the items in that array are displayed between the page header (logo+helpbutton) and the menu/content area. This is the feedback of the previous action to the user (if any).
Note that we hard-code the content type to be "text/html; charset=UTF-8" in a http-equiv right at the top of the <head>. This indicates the correct charset to the browser even if a generated page is stored by the user and later retrieved in which case there will be no http-header. (We cannot be sure that the user's browser correctly identifies the stored document as UTF-8 if that is not the default).
return a valid (unquoted) UTF-8 string parameter typically from $_POST, or default value if none
this retrieves a parameter string, typically from $_POST, and checks basic properties.
Note that we check the stringlength in two steps. First we check the worst-case UTF-8 string with 4-byte sequences for a length of 4 * $maximum_length. Then, after establishing that the string is valid UTF-8 we make a more precise check of the length expressed in chars rather than bytes.
educated guesses for scheme, host and portname from $_SERVER
this routine tries to guess the various components of the url that was used to reach this script, based on the information in the global array $_SERVER. If no information can be guessed at all, the result is something like 'http://localhost'.
Note that the 'authority' is a combination of hostname and portnumber, but only if the portnumber is non-standard. For http and port 80, and https and port 443 the portnumber is suppressed, because these are the default ports for those schemes.
Note that we actually guess the url that should correspond with the document root.
fill tables in database via include()'ing a file with tabledata
create a link to an invisible image to test the friendly URL feature
this routine is part of a trick to discover whether the web server understands a frienly url like /index.php/1/23/Welcome rather than /index.php?area=1&node=23. This trick works as follows.
I chose the Website dialog for this trick, because you can only get there after you have entered a valid username/password for the database. This means that only genuine users will perform this trick; outsiders never get to that dialog without a valid password.
Obviously the setting is eventually entered in the configuration table, just like the title and the website_from_address etc.
Note: both the Install Wizard itself and the code that generates the test image (in run()) access the session variables. According to the PHP documentation this works because the file that holds the session information is locked between the calls to session_start() and session_write_close(). The only visible effect _might_ be that the one call has to wait for the other to finish. Oh well, it is only a 1x1 gif of 35 bytes. It probably will go unnoticed.
check for previous install
this routine checks to see if another installation should be allowed. Returns TRUE if the program was already installed or FALSE otherwise.
this circumvents the 'magic' in magic_quotes_gpc() by conditionally stripping slashes
This routine borrowed from waslib.php.
a trick for systems without the microtime function
if the system function does not exist, we return the current time with a 1-second resolution.
unified mysql/mysqli wrapper for close
unified mysql/mysqli wrapper for connect
unified mysql/mysqli wrapper for get_server_info
unified mysql/mysqli wrapper for num_rows
unified mysql/mysqli wrapper for query
unified mysql/mysqli wrapper for real_escape_string
unified mysql/mysqli wrapper for select_db
unified mysql/mysqli wrapper for set_charset
massage string to contain only 3-byte UTF8-sequences
this replaces an otherwise perfectly valid 4-byte UTF-8 sequence in $utf8str with a 3-byte UTF-8 sequence equivalent with the Unicode replacement character U+FFFD.
The effect is that it leaves a hint that there used to be some character instead of silently discarding 4-byte sequences which MySQL does.
determine the level of UTF-8 support based on MySQL-server version
MySQL support for UTF-8 was non-existent before 4.1.x and limited until 5.5.3. In this context 'limited' means: only the Basic Multilingual Plane (U+0000 ... U+FFFF) is supported, i.e. a maximum of 3-byte sequences per character.
As of 5.5.3 the full UTF-8 specification according to RFC 3629 is implemented. MySQL now has 'invented' yet another proprietary name for this character set: 'utf8mb4' (WTF?), and introduces the alias 'utf8mb3' for the pre 5.5.3 limited support for 'utf8' (WTF??), hinting that the meaning of 'utf8' may change in future versions to indicate 'utf8mb4' (WTF???). IM(NS)HO this is yet another reason to go looking for a decent replacement for MySQL. YMMV.
This routine returns exactly one of the values below (based on the server version).
perform the actual initialisation of the cms
this routine initialises the database: creates tables, inserts essential data (first user account, other defaults) and optional demonstration data.
The strategy is as follows.
generate a string with quasi-random characters
This routine borrowed from waslib.php.
Note that this too is an ASCII-centric routine: we only use plain ASCII letters and digits and nothing of the 64000 other UNicode characters in the Basic Multilingual Plane. The reason is simple: 7-bit ASCII characters have the best chance of getting through communication channels unmangled so there.
quick and dirty dialogdef renderer
This is a small routine to render simple dialogs with strings, lists and checkboxes.
Note that every element in the $dialogdef is in itself an array. Recognised elements in those arrays are:
main dispatcher for the Installation Wizard
This routine termines what needs to be done and does it by calling the corresponding workhorse routines.
Note the special trick with the test for a friendly URL; see invisible_test_image() for the gory details.
sanitise a string to make it acceptable as a filename/directoryname
This routine more or less borrowed from waslib.php.
Note that this routine too is very ASCII-centric: in the end only ASCII-characters (52 letters, 10 digits and dash, dot and underscore) are allowed in the resulting file/directory name. However, by first mapping UTF-8 to ASCII (getting rid of diacriticals) we can make names more readable.
validate and store the CMS-data the user supplied
This is the companion routine for show_dialog_cms(). It stores the user-supplied data about the website (paths etc.) We always store the data in the global _SESSION, even if something goes wrong. This makes that the user will use the latest values when re-doing the dialog.
We try to validate the specified directories: they should at least exist. The datadirectory should also be writable by us. If it not exists we try to create it (and remove it after the test). Note that the PHP safe_mode may complicate things here.
validate database information
This is the companion routine for show_dialog_database(). It stores the user-supplied data about the database. We always store the data in the global _SESSION, even if something goes wrong. This makes that the user will use the latest values when re-doing the dialog. However, only when the values are valid, the parameter db_validated is set to TRUE. This is used lateron (in the confirmation phase).
This routine doubles as a gate keeper. Every time the user makes a mistake, an error counter is incremented and the script pauses for some time (see errorcount_bump()). If there are too many errors, the script resets the data collected sofar and the procedure starts from scratch. This is a (probably futile) attempt to make it harder to brute force an entry by repeatedly probing for database credentials. Unfortunately there is no easy way (at least one I can think of) to protect this script from repeated break-in attempts other than by simply removing or renaming this script. Oh well.
The following tests are performed:
store the selected install type + high visibility flag
This is the companion routine for show_dialog_installtype(). It stores the user-supplied choices for custom install and high visibility. The values in the radio button should be 0 for standard and 1 for custom install. The value we keep is a boolean indicating a custom install yes or no.
store the selected language
This is the companion routine for show_dialog_language(). It validates and stores the user-supplied language key.
validate and store the data for the first user account
This is the companion routine for show_dialog_user(). It stores the information about the first user account. We always store the data in the global _SESSION, even if something goes wrong. This makes that the user will use the latest values when re-doing the dialog.
We try to validate at least the password:
show the user that the process has been cancelled
this shows a screen with the message that the installation procedure has been cancelled. There is a single [OK] button that effectively allows the user to try again. The existing session (and all the data it might contain) is destroyed.
Note that we first construct the page (possibly in another language) in a separate variable before we destroy all data collected sofar.
construct the dialog for essential cms data (title, paths, e-mail address)
This dialog contains the following fields:
construct the comptibility overview
this routine displays a tabular overview of minimal compatibility requirements and the current status/testresults. The table is constructed in a subroutine; we only deal with the display here.
Q: Why here, at the last stop before installation? A: Because we otherwise would leak information about our environment to complete strangers (assuming anyone can execute this script).
construct the overview/confirmation dialog
This dialog contains an overview of the information entered (excluding the passwords which are visually replaced with asterisks). This is the last chance the user gets to change the data entered. Once the user presses the [Next] button, the actual installation takes off.
construct the dialog for database (server, host, username, password, etc.)
This dialog contains the following fields:
construct the finish screen
this dialog is displayed after a succesful installation. The user is prompted to select the next destination, which can be either admin.php (the backoffice), index.php (the frontpage), manual.php (the documentation) or the project's home page.
There is also an option to download config.php. Once the user's choice is submitted, the session is reset and config.php can no longer be downloaded. This session reset is done in the 'Done' dialog (see end_session_and_redirect()).
Note that the boolean parameter INSTALL['config_php_written'] indicates whether the file config.php was already succesfully written in the designated location. If this is the case, we show a different message compared to the case where the user still needs to download+upload config.php.
construct the installtype + high visibility selection dialog
This dialog contains a radio button where the user selects 'standard' or 'custom' and also a checkbox for high visibility. As always the dialog ends with buttons to move forward, backward or to cancel the installation process alltogether.
construct the language selection dialog
this dialog allows the user to pick a language. The choices are determined by looking for translation files in the file system, specifically for files /program/install/languages/LL/install.php where LL is the language code, see get_list_of_install_languages().
construct a full license agreement and an input where the user must enter 'I agree'
This constructs a (long) license agreement dialog. We more or less force the user to actually scroll through it by having the input box + buttons after the agreement text. Note that the phrase to enter into the box is also translated, it may be 'I agree' in English but something else in other languages (done via translation file). If the user already accepted the license, the 'I agree'-text is already displayed in the dialog, in the current language. (A user could type 'I agree' in English, change the language into Dutch, in which case the $value in the textbox would be something like 'Ik ga accoord' or whatever the translation of 'I agree' is.) This is done via an extra level of translation with the 'dialog_license_i_agree' translation in the prompt.
Also, the instruction to enter the exact words are repeated near the bottom of the screen, just to make sure the user understands what to do.
Note that the phrase the user enters is compared to the requested phrase in a more or less case-INsensitive way (see check_license().
construct the dialog for the first user account
This dialog contains the following fields:
retrieve a translated string with optional parameters filled in
this routine tries to find a translated string based on the $key. If $replace is not empty, any keys in that array that are found in the translation are replace by the corresponding value.
The translations are read from the files /program/install/languages/LL/install.php, where LL is a language code (usually two-letter e.g. en, nl, de, fr). If the desired language is not available, English is used instead. If the requested translation is not found, the key of the translation is returned, sandwiched between the language codes. Usually this does not happen (all keys used have a translation), but it can be helpful when creating and testing a new translation.
Note that a language file is retrieved completely. This means that all the translations are read from file in one swoop; there is no need to go to the disk for every individual translation. The translation file is buffered in memory via the static variable $phrases, which is an array keyed by language. That means that multiple languages can co-exist in this static variable. That last feature is used in constructing a list of available languages where the name of the language is expressed in the language itself (see get_list_of_install_languages()).
Because the English language is the basis for all other languages, we always read the English translation the first time around, as a backup plan for missing prompts in other languages.
Note: By convention the keys in $replace are upper case words, with optional underscores, sandwiched between curly braces. Examples: '{FIELD}', '{DATABASE}', '{RELEASE_DATE}'. The idea is that these words make life easier for translators.
minimal validation of data input
this routine is a KISS-validator; we check for min/max stringlengths in strings and passwords (defaults: 0 and 255) and a valid item in listboxes. In case of error, a message is added to $this->messages and the function returns FALSE. On success we return TRUE. Note that additional validation could or should be done on some fields, e.g. a password of sufficient complexity, etc. This routine does not do that.
validation of password input
this routine analyses the password provided against the minimal requirements of password complexity. Here 'complexity' means a minimum number of upper case and lower case characters and a minimum number of digits. The required 'complexity' of a password is quite ASCII-centric. The idea is to make a user pick from a pool of at least 62 different characters rather than say 26 lowercase latin letters a-z. However, with UTF-8 the story is actually different: what is more "complex": "Aa1" or U+1F150 NEGATIVE CIRCLED LATIN CAPITAL LETTER A encoded as "\xF0\x9F\x85\x90"? Even though it is only 1 character, the result is byte string of length 4 rather than the meager 3-byte string that satisfies the minimum requirements...
Anyway. In order to take into account some additional upper case and lower case characters without resorting to loading the complete Unicode database, I opt for the following tricks. If lowercase($str) differs from $str itself, it was apparently not a lower case character but probably an upper case (or title case) character; good enough to count it toward the upper case character count. The same idea is used for the opposite: if upper($str) != $str => $str probably lowercase.
This algoritm works for A-Z and a-z but also for the 1100+ other Unicode characters that are case-aware. So, the user isn't held back if she insists on the password p a \xE1 \xBA \x9E w 0 r d (where \xE1\xBA\x9E equates to U+1E9E LATIN CAPITAL LETTER SHARP S
Note that by default we still require at least 1 digit 0-9 and do not count any of the 410 "digits" like these zeros: U+0660: ARABIC-INDIC DIGIT ZERO U+06F0: EXTENDED ARABIC-INDIC DIGIT ZERO U+07C0: NKO DIGIT ZERO U+0966: DEVANAGARI DIGIT ZERO ... U+1D7EC: MATHEMATICAL SANS-SERIF BOLD DIGIT ZERO U+1D7F6: MATHEMATICAL MONOSPACE DIGIT ZERO
Perhaps some other time... (Did I mention this is quite ASCII-centric?)
attempt to write the file config.php in the correct location
this routine tries to write the file config.php. If this fails, we return FALSE, otherwise TRUE. Note that the permissions of the file are set to read-only (chmod 0400) for the owner of the file, and nothing for group and world. That should be enough for the webserver.
Documentation generated on Tue, 28 Jun 2016 19:09:54 +0200 by phpDocumentor 1.4.0