diff --git a/README.md b/README.md index 3e19543..52d53e0 100644 --- a/README.md +++ b/README.md @@ -3,20 +3,27 @@ Admin Pack A PHP toolkit designed specifically for programmers to quickly create a nice-looking, custom-built, secure administrative web interface. Choose from a MIT or LGPL license. Proven to cut development time by at least 50% over traditional frameworks and template engines. But it isn't a CMS nor a framework or a template engine. Admin Pack is very different. Give it a go to power your next PHP-based administrative backend. +Be sure to check out [Admin Pack with Extras](https://github.com/cubiclesoft/admin-pack-with-extras). It is Admin Pack plus some extra options with Javascript components (e.g. a convenient visual date picker widget) in a slightly beefier package. + Features -------- * Quick-n-dirty custom administrative interface builder. -* The default template and print stylesheet looks nice. +* The default templates look nice enough. Gets the job done. * Integrated CSRF/XSRF defenses. -* Very lightweight (~140KB). +* Very lightweight (~180KB). * Has a liberal open source license. MIT or LGPL, your choice. * Designed for relatively painless integration into your project. * Sits on GitHub for all of that pull request and issue tracker goodness to easily submit changes and ideas respectively. +Under the Hood +-------------- + +Admin Pack uses [FlexForms](https://github.com/cubiclesoft/php-flexforms), which makes it easy to extend Admin Pack with custom functionality (see `support/view_print_layout.php`). For example, [FlexForms Modules](https://github.com/cubiclesoft/php-flexform-modules) contains several official extensions (e.g. charts, HTML editor, character/word counter). + More Information ---------------- -Documentation, demos, examples, and official downloads of this project sit on the Barebones CMS website: +Documentation, demos, examples, and official downloads of this project sit on the Barebones CMS website (Admin Pack does not depend on Barebones CMS): http://barebonescms.com/documentation/admin_pack/ diff --git a/admin.php b/admin.php new file mode 100644 index 0000000..2b63ef7 --- /dev/null +++ b/admin.php @@ -0,0 +1,530 @@ + array( + // Replace these menu options with your own. The examples contain useful boilerplate code. + "Manage" => BB_GetRequestURLBase() . "?action=manageexample&sec_t=" . BB_CreateSecurityToken("manageexample"), + "Add Entry" => BB_GetRequestURLBase() . "?action=addeditexample&sec_t=" . BB_CreateSecurityToken("addeditexample"), + "Bulk Edit" => BB_GetRequestURLBase() . "?action=bulkeditexample&sec_t=" . BB_CreateSecurityToken("bulkeditexample"), + "View/Print" => BB_GetRequestURLBase() . "?action=viewprintexample&id=1&sec_t=" . BB_CreateSecurityToken("viewprintexample") + ) + ); + + // An example function used later on to demonstrate loading user information from a database. + function LoadUserDetails($info) + { + $defaults = array( + "first" => "John", "last" => "Smith", "email" => "", "city" => "", "state" => "", "zip" => "", + "status" => "Approved", "notes" => "What a great guy.\n\nUser approved on " . date("m/d/Y") . ".", "othernotes" => "" + ); + + return BB_ProcessInfoDefaults($info, $defaults); + } + + if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "deleteexample") + { + $id = (isset($_REQUEST["id"]) ? (int)$_REQUEST["id"] : 0); +// $db->Query("DELETE FROM userdetails WHERE id = ?", array($id)); + + BB_RedirectPage("success", "Successfully deleted the details entry. (Just imagine that it worked. After all, this is only an example.)", array("action=manageexample&sec_t=" . BB_CreateSecurityToken("manageexample"))); + } + else if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "manageexample") + { + // Demonstrates a common pattern to show all entries in a MySQL/MariaDB database and provide options. + +// $rows = array(); +// $result = $db->Query("SELECT * FROM userdetails"); +// while ($row = $result->NextRow()) +// { +// $info = LoadUserDetails(unserialize($row->info)); +// +// $rows[] = array(htmlspecialchars($info["first"]), htmlspecialchars($info["last"]), "id . "&sec_t=" . BB_CreateSecurityToken("viewprintexample") . "\">View | id . "&sec_t=" . BB_CreateSecurityToken("addeditexample") . "\">Edit | id . "&sec_t=" . BB_CreateSecurityToken("deleteexample") . "\" onclick=\"return confirm('Are you sure you want to delete these details?');\">Delete"); +// } + + // A fake entry to show what this pattern looks like. + $info = LoadUserDetails(array()); + $rows[] = array(htmlspecialchars($info["first"]), htmlspecialchars($info["last"]), "View | Edit | Delete"); + + // Custom HTML for a mini-menu. + $desc = "
"; + $desc .= "Add New Entry"; + + $contentopts = array( + "desc" => "Manage user details.", + "htmldesc" => $desc, + "fields" => array( + array( + "type" => "table", + "cols" => array("First", "Last", "Options"), + "rows" => $rows + ) + ) + ); + + BB_GeneratePage("Manage Entries Example", $menuopts, $contentopts); + } + else if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "addeditexample") + { + // Demonstrates a common pattern to easily add new entries AND edit existing entries in a MySQL/MariaDB database with writing code only one time. + // Less code results in fewer logic errors. Some code is commented out so that the example actually functions. + + $id = (isset($_REQUEST["id"]) ? (int)$_REQUEST["id"] : 0); +// $row = $db->GetRow("SELECT * FROM userdetails WHERE id = ?", array($id)); +// if ($row) $info = LoadUserDetails(unserialize($row->info)); +// else +// { + $info = LoadUserDetails(array()); +// $id = 0; +// } + + if (isset($_REQUEST["first"])) + { + if ($_REQUEST["first"] == "") BB_SetPageMessage("error", "Please fill in 'Field 1'."); + + if (BB_GetPageMessageType() != "error") + { + // [Save data here.] + $originfo = $info; + $info["first"] = $_REQUEST["first"]; + $info["last"] = $_REQUEST["last"]; +// $info["email"] = $_REQUEST["email"]; + +// if ($id) $db->Query("UPDATE userdetails SET email = ?, info = ? WHERE id = ?", array($info["email"], serialize($info), $id)); +// else $db->Query("INSERT INTO userdetails SET email = ?, info = ?", array($info["email"], serialize($info))); + + BB_RedirectPage("success", ($_REQUEST["id"] > 0 ? "Successfully saved the details." : "Successfully created the details."), array("action=manageexample&sec_t=" . BB_CreateSecurityToken("manageexample"))); + } + } + + // [Do processing here to generate any dynamic content options.] + $somevar = "default value 2"; + $items = array("Furries", "Fuzzies", "Fluffies", "Puppies", "Kitties", "Penguins", "Ponies", "Tribbles", "Unicorns"); + $rows = array(); + foreach ($items as $num => $item) + { + $rows[] = array(($num + 1), htmlspecialchars($item), "Edit"); + } + + // Load and use most FlexForms Modules if available. + if (file_exists("support/flex_forms_calendar_table.php")) require_once "support/flex_forms_calendar_table.php"; + if (file_exists("support/flex_forms_chart.php")) require_once "support/flex_forms_chart.php"; + if (file_exists("support/flex_forms_tablefilter.php")) require_once "support/flex_forms_tablefilter.php"; + if (file_exists("support/flex_forms_htmledit.php")) require_once "support/flex_forms_htmledit.php"; + if (file_exists("support/flex_forms_textcounter.php")) require_once "support/flex_forms_textcounter.php"; + + $tomorrow = mktime(0, 0, 0, date("n"), date("j") + 1); + + $contentopts = array( + "desc" => ($id ? "Edit the user details." : "Add user details."), + "hidden" => array( + "id" => $id + ), + "fields" => array( + // The accordions only show correctly when FlexForms Extras is used. + array( + "title" => "Some Options", + "type" => "accordion" + ), + // Field 1 demonstrates current "best practice". + array( + "title" => "Field 1", + "type" => "text", + "name" => "first", + "default" => $info["first"], + "desc" => "Basic text field." + ), + array( + "title" => "Field 2", + "type" => "text", + "name" => "last", + "value" => BB_GetValue("last", $info["last"]), + // ^^^ Using 'value' directly is an older solution. Using 'default' usually accomplishes the same thing with less code (see 'Field 1' above). + "desc" => "Another text field." + ), + array( + "title" => "Some More Options", + "type" => "accordion" + ), + "startrow", + array( + "title" => "Field 2a", + "type" => "text", + "width" => "200px", + "name" => "field2a", + "default" => "", + ), + array( + "title" => "Field 2b", + "type" => "text", + "width" => "50px", + "name" => "field2b", + "default" => "", + ), + "endrow", + array( + "title" => "Date", + "type" => "date", + "name" => "date", + "default" => date("Y-m-d"), + "desc" => "Description for Date. Requires FlexForms Extras to show this field." + ), + array( + "title" => "Table", + "split" => false, + "type" => "table", + "cols" => array("ID", "Type", "Options"), + "rows" => $rows, + "order" => "Order", + "stickyheader" => true, + "desc" => "Description for Table. When used with FlexForms Extras, drag-and-drop and sticky header support are enabled." + ), + "nosplit", + "startrow", + array( + "title" => "Field 2c", + "type" => "text", + "width" => "200px", + "name" => "field2c", + "default" => "", + ), + array( + "title" => "Field 2d", + "type" => "text", + "width" => "50px", + "name" => "field2d", + "default" => "", + ), + "endrow", + array( + "title" => "Cookies?", + "type" => "checkbox", + "name" => "field2e", + "value" => "Yes", + "display" => "I like cookies", + "default" => true + ), + array( + "title" => "File", + "type" => "file", + "name" => "file", + "desc" => "Description for File." + ), + "endaccordion", + "split", + "startrow", + array( + "title" => "Field 3", + "type" => "text", + "width" => "200px", + "name" => "field3", + "default" => "default value", + "desc" => "Description for Field 3." + ), + array( + "title" => "Field 4", + "type" => "text", + "width" => "200px", + "name" => "field4", + "default" => $somevar + ), + "startrow", + array( + "title" => "Field 5", + "type" => "text", + "width" => "220px", + "name" => "field5", + "default" => "default value", + "desc" => "Description for Field 5." + ), + array( + "title" => "Field 6", + "type" => "text", + "width" => "220px", + "name" => "field6", + "default" => $somevar, + "desc" => "Description for Field 6." + ), + "endrow", + array( + "title" => "Informational", + "type" => "static", + "value" => "Did you know that FlexForms, which powers Admin Pack, actually has its origins in Barebones CMS? BB_PropertyForm() started it all." + ), + array( + "title" => "Admin Notes", + "type" => "textarea", + "name" => "notes", + "default" => $info["notes"] + ), + array( + "title" => "Custom HTML Output", + "type" => "custom", + "value" => "
Give HTML some lovin'
" + ), + "split", + // NOTE: Dropdown and flat are incompatible and can't be on the same page. +// array( +// "title" => "Field 7", +// "type" => "select", +// "multiple" => true, +// "mode" => "dropdown", +// "height" => "250px", +// "name" => "field7", +// "options" => array("name" => "Name", "email" => "E-mail Address", "phone" => "Phone Number"), +// "select" => BB_SelectValues(BB_GetValue("field7", array())), +// // ^^^ Using 'select' directly is an older solution. Using 'default' accomplishes the same thing with less code (see 'Field 7' and 'Field 8' below). +// "desc" => "Description for Field 7. FlexForms Extras turns this field into a multiselect 'dropdown' widget." +// ), + array( + "title" => "Field 7", + "type" => "select", + "multiple" => true, + "mode" => "flat", + "height" => "250px", + "name" => "field7", + "options" => array("name" => "Name", "email" => "E-mail Address", "phone" => "Phone Number"), + "default" => array("name"), + "desc" => "Description for Field 7. FlexForms Extras turns this field into a multiselect 'flat' widget." + ), + array( + "title" => "Field 8", + "type" => "select", + "width" => "100%", + "multiple" => true, + "mode" => "tags", + "name" => "field8", + "options" => array("name" => "Name", "email" => "E-mail Address", "phone" => "Phone Number"), + "default" => array(), + "desc" => "Description for Field 8. FlexForms Extras turns this field into a multiselect 'tags' widget." + ), + "split", + array( + "title" => "Module: Calendar Table", + "type" => "calendar", + "startyear" => date("Y"), + "startmonth" => date("m"), + "endyear" => date("Y"), + "endmonth" => date("m"), + "cols" => array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"), + "data" => array(date("Y-m-d", $tomorrow) => "" . date("j", $tomorrow) . ""), + "desc" => "Description for calendar. This feature requires FlexForms Modules to be included." + ), + array( + "title" => "Module: Chart (Line)", + "type" => "chart", + "chart" => "line", + "data" => array("key1" => array(1, 5, 10, 7, 6, 9, 8), "key2" => array(10, 6, 3, 4, 5, 6, 7)), + "options" => array("grid.x.show" => true, "zoom.enabled" => true), + "desc" => "Description for line chart. This feature requires FlexForms Modules to be included." + ), + array( + "title" => "Module: Chart (Pie)", + "type" => "chart", + "chart" => "pie", + "data" => array("key1" => array(1, 5, 10, 7, 6, 9, 8), "key2" => array(10, 6, 3, 4, 5, 6, 7)), + "desc" => "Description for pie chart. This feature requires FlexForms Modules to be included." + ), + array( + "title" => "Module: Chart (Gauge)", + "type" => "chart", + "chart" => "gauge", + "height" => 180, + "colors" => array("#FF0000", "#F97600", "#F6C600", "#60B044"), + "thresholds" => array(30, 60, 90, 100), + "data" => array("value" => array(91.4)), + "desc" => "Description for gauge. This feature requires FlexForms Modules to be included." + ), + array( + "title" => "Module: Table Filter", + "type" => "table", + "cols" => array("ID", "Type", "Options"), + "rows" => $rows, + "filter" => true, + "desc" => "Description for table. Try typing 'ies' into the filter field. This feature requires FlexForms Modules to be included." + ), + array( + "title" => "Module: HTML Editor", + "type" => "textarea", + "width" => "100%", + "height" => "300px", + "name" => "htmledit", + "default" => "

Why, hello there!

Have you had your Wheaties today?

", + "html" => true, + "desc" => "Description for HTML editor. This feature requires FlexForms Modules to be included." + ), + array( + "title" => "Module: Text Counter", + "type" => "text", + "name" => "textcount", + "default" => "", + "counter" => 150, + "desc" => "Description for Text Counter. This feature requires FlexForms Modules to be included." + ), + ), + "submit" => ($id ? "Save" : "Create") + ); + + if (file_exists("support/flex_forms_passwordmanager.php")) + { + require_once "support/flex_forms_passwordmanager.php"; + + $contentopts["fields"][] = array( + "title" => "Module: Stop Password Manager", + "type" => "password", + "name" => "signature", + "default" => "", + "passwordmanager" => false, + "desc" => "Description for Stop Password Manager. This feature requires FlexForms Modules to be included." + ); + } + + BB_GeneratePage(($id ? "Edit Entry Example" : "Add Entry Example"), $menuopts, $contentopts); + } + else if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "bulkeditexample_getinfo") + { + $id = (int)$_REQUEST["id"]; + +?> + + "mainitem_" . $x, + "display" => "Item #" . $x, + "onclick" => "LoadItem('" . $x . "');" + ); + } + + ob_start(); +?> + + $items, + "topbarhtml" => "An optional top bar", + "initialhtml" => "Select an option from the left.", + "bottombarhtml" => "An optional bottom bar", + "javascript" => $js + ); + + BB_GenerateBulkEditPage("Example Bulk Edit Page", $contentopts); + } + else if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "viewprintexample") + { + // Demonstrates a common pattern to display a summary view that is also print-ready. + // The view/print layout adds additional features to FlexForms. + + $id = (isset($_REQUEST["id"]) ? (int)$_REQUEST["id"] : 0); +// $row = $db->GetRow("SELECT * FROM userdetails WHERE id = ?", array($id)); +// if ($row) +// { +// $info = LoadUserDetails(unserialize($row->info)); + + // Using fake user details here so that the example works. + $info = LoadUserDetails(array()); + + require_once "support/view_print_layout.php"; + + // Any extra/custom HTML goes here. + $desc = ""; + + $contentopts = array( + "desc" => "", + "htmldesc" => $desc, + "fields" => array( + array( + "type" => "viewmedia", + "value" => "", + "desc" => "A floating image. Well, it would show if the file existed." + ), + array( + "type" => "viewtable", + "data" => array( + "First" => $info["first"], + "Last" => $info["last"], + "E-mail" => $info["email"], + "Status" => $info["status"] + ), + "desc" => "Shows first name, last name, and status but not e-mail since 'compact' data mode is enabled (the default). Values are also HTML encoded by default for safety." + ), + "endmedia", + array( + "use" => ($info["notes"] != ""), + "title" => "Admin Notes", + "type" => "viewstatic", + "value" => $info["notes"] + ), + array( + "use" => ($info["othernotes"] != ""), + "title" => "Other Notes", + "type" => "viewstatic", + "value" => $info["othernotes"], + "desc" => "This field won't show at all since 'use' is false. Value for viewstatic is HTML encoded by default." + ) + ) + ); + + BB_GeneratePage("View Entry Example for #" . $id, $menuopts, $contentopts); +// } + } + else + { + $contentopts = array( + "desc" => "Pick an option from the menu." + ); + + BB_GeneratePage("Home", $menuopts, $contentopts); + } +?> \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..e69de29 diff --git a/index.php b/index.php deleted file mode 100644 index 3e02258..0000000 --- a/index.php +++ /dev/null @@ -1,204 +0,0 @@ - array( - "Some Page" => BB_GetRequestURLBase() . "?action=somepage&sec_t=" . BB_CreateSecurityToken("somepage"), - "Some Page 2" => BB_GetRequestURLBase() . "?action=somepage2&sec_t=" . BB_CreateSecurityToken("somepage2") - ) - ); - - if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "somepage") - { - if (isset($_REQUEST["field1"])) - { - if ($_REQUEST["field1"] == "") BB_SetPageMessage("error", "Please fill in 'Field 1'."); - - if (BB_GetPageMessageType() != "error") - { - // [Save data here.] - - BB_RedirectPage("success", "Successfully saved the data."); - } - } - - // [Do processing here to generate content options dynamically.] - $somevar = "default value 2"; - - $contentopts = array( - "desc" => "This is some page.", - "nonce" => "action", - "hidden" => array( - "action" => "somepage" - ), - "fields" => array( - array( - "title" => "Field 1", - "type" => "text", - "name" => "field1", - "value" => BB_GetValue("field1", ""), - "desc" => "Description for Field 1." - ), - array( - "title" => "Field 2", - "type" => "text", - "name" => "field2", - "default" => $somevar, - "desc" => "Description for Field 2." - ), - array( - "title" => "Date", - "type" => "date", - "name" => "date", - "default" => date("Y-m-d"), - "desc" => "Description for Date." - ), - array( - "title" => "File", - "type" => "file", - "name" => "file", - "desc" => "Description for File." - ), - "split", - "startrow", - array( - "title" => "Field 3", - "type" => "text", - "width" => "200px", - "name" => "field3", - "default" => "default value", - "desc" => "Description for Field 3." - ), - array( - "title" => "Field 4", - "type" => "text", - "width" => "200px", - "name" => "field4", - "default" => $somevar - ), - "startrow", - array( - "title" => "Field 5", - "type" => "text", - "width" => "220px", - "name" => "field5", - "default" => "default value", - "desc" => "Description for Field 5." - ), - array( - "title" => "Field 6", - "type" => "text", - "width" => "220px", - "name" => "field6", - "default" => $somevar, - "desc" => "Description for Field 6." - ), - "endrow", - "split", - array( - "title" => "Field 7", - "type" => "select", - "multiple" => true, - "mode" => "dropdown", - "height" => "250px", - "name" => "field7", - "options" => array("name" => "Name", "email" => "E-mail Address", "phone" => "Phone Number"), - "default" => array(), - "desc" => "Description for Field 7." - ), - ), - "submit" => "Save", - "focus" => true - ); - - BB_GeneratePage("Some Page", $menuopts, $contentopts); - } - else if (isset($_REQUEST["action"]) && $_REQUEST["action"] == "somepage2_getinfo") - { - $id = (int)$_REQUEST["id"]; - -?> - - "mainitem_" . $x, - "display" => "Item #" . $x, - "onclick" => "LoadItem('" . $x . "');" - ); - } - - ob_start(); -?> - - $items, - "topbarhtml" => "An optional top bar", - "initialhtml" => "Select an option from the left.", - "bottombarhtml" => "An optional bottom bar", - "javascript" => $js - ); - - BB_GenerateBulkEditPage("Some Bulk Edit Page", $contentopts); - } - else - { - $contentopts = array( - "desc" => "Pick an option from the menu." - ); - - BB_GeneratePage("Home", $menuopts, $contentopts); - } - - // [Put any cleanup/finalization logic here.] -?> \ No newline at end of file diff --git a/support/admin.css b/support/admin.css index 9e34bf3..ef0c141 100644 --- a/support/admin.css +++ b/support/admin.css @@ -369,7 +369,7 @@ div.maincontent div.propmain div.formfields div.formitem div.formitemtitle { margin-bottom: 2px; } -div.maincontent div.propmain div.formfields div.formitem div.static { +div.maincontent div.propmain div.formfields div.formitem div.static, div.maincontent div.propmain div.formfields div.formitem div.staticwrap { margin-left: 7px; width: 95%; font-size: 0.95em; diff --git a/support/admin.js b/support/admin.js index 259b4af..0a500cb 100644 --- a/support/admin.js +++ b/support/admin.js @@ -9,20 +9,4 @@ $(function() { }); $('.leftnav').clone().appendTo('#navdropdown'); - - $('input.nopasswordmanager[type=password]').each(function() { - $(this).attr('data-background-color', $(this).css('background-color')); - $(this).css('background-color', $(this).css('color')); - $(this).attr('type', 'text'); - - $(this).focus(function() { - $(this).attr('type', 'password'); - $(this).css('background-color', $(this).attr('data-background-color')); - }); - - $(this).blur(function() { - $(this).css('background-color', $(this).css('color')); - $(this).attr('type', 'text'); - }); - }); }); \ No newline at end of file diff --git a/support/admin_view.css b/support/admin_view.css new file mode 100644 index 0000000..80bfe2a --- /dev/null +++ b/support/admin_view.css @@ -0,0 +1,374 @@ +body { + margin: 0; + padding: 0; + border: 0; + font-family: Verdana, Arial, Helvetica, sans-serif; + max-width: 800px; + margin: 0 auto; +} + +div.pagewrap { + margin: 0 5px 5px 5px; +} + +div.pagewrap div.headerwrap { + text-align: center; + font-weight: bold; + font-size: 1.7em; + border-left: 1px solid #BBBBBB; + border-right: 1px solid #BBBBBB; + border-bottom: 1px solid #BBBBBB; + background-color: #F5F5F5; + padding: 0.1em; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; + -moz-border-radius-bottomright: 3px; + -moz-border-radius-bottomleft: 3px; + -webkitborder-bottom-right-radius: 3px; + -webkitborder-bottom-left-radius: 3px; +} + +div.pagewrap div.contentwrap { + margin-top: 10px; +} + +div.pagewrap div.menuwrap { + margin-top: 7px; + border-top: 1px solid #CCCCCC; + padding: 25px 0 10px; +} + +/* Basic styles for elements. */ +img { + border: 0px none; + margin: 0px; +} + +form { + border: 0px none; + margin: 0px; +} + +a { + color: #035488; + text-decoration: none; +} + +a:hover { + color: #444444; + text-decoration: underline; +} + +/* Optional message styles. */ +div.message { + margin: 10px 7px; + font-weight: bold; +} + +div.message .success { + background-color: #E8FCDC; + border: 1px solid #008800; + color: #008800; + padding: 7px 15px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +div.message .error { + background-color: #FCDCDC; + border: 1px solid #880000; + color: #880000; + padding: 7px 15px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +div.message .info { + background-color: #DCDCDC; + border: 1px solid #333333; + color: #333333; + padding: 7px 15px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +div.maincontent { +} + +div.maincontent div.proptitle { + font-weight: bold; + font-size: 1.3em; + color: #1F1F1F; +} + +div.maincontent div.propdesc { + margin-top: 5px; + padding: 10px 7px; + border-top: 1px solid #CCCCCC; + color: #333333; + font-size: 1.0em; +} + +div.maincontent div.propinfo { +} + +div.maincontent div.propmain { + margin: 0px 7px; +} + +div.maincontent div.propmain div.formfields { + font-size: 0.9em; +} + +div.maincontent div.propmain div.formfields div.formaccordionwrap.ui-accordion { + margin-bottom: 7px; +} + +div.maincontent div.propmain div.formfields div.formaccordionwrap.ui-accordion h3.ui-accordion-header { + margin-top: 7px; +} + +div.maincontent div.propmain div.formfields div.formaccordionwrap.ui-accordion h3.ui-state-active { + background: url("jquery_ui_themes/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png") repeat-x scroll 50% 50% #E6E6E6; +} + +div.maincontent div.propmain div.formfields div.formaccordionwrap.ui-accordion div.formaccordionitems.ui-accordion-content { + padding: 0 14px; + background: none; + background-color: #FEFEFE; +} + +div.maincontent div.propmain div.formfields div.formaccordionwrap.ui-accordion div.formaccordionitems.ui-widget-content a { + color: #035488; +} + +div.maincontent div.propmain div.formfields div.formaccordionwrap.ui-accordion div.formaccordionitems.ui-accordion-content div.formitem { + margin-top: 6px; + border-top: 1px dotted #CCCCCC; + padding-top: 6px; +} + +div.maincontent div.propmain div.formfields div.formaccordionwrap.ui-accordion div.formaccordionitems.ui-accordion-content div.formitem.firstitem { + border-top: 0; +} + +div.maincontent div.propmain div.formfields div.formitem { + margin: 0.9em 0 0.5em; +} + +div.maincontent div.propmain div.formfields div.formitem div.formitemtitle { + font-weight: bold; + margin-bottom: 2px; +} + +div.maincontent div.propmain div.formfields div.formitem div.staticwrap, div.maincontent div.propmain div.formfields div.formitem div.static { + margin-left: 7px; + width: 95%; + font-size: 0.95em; + color: #333333; +} + +div.maincontent div.propmain div.formfields div.formitem input.text { + padding: 0.5em; + width: 95%; + border: 1px solid #BBBBBB; +} + +div.maincontent div.propmain div.formfields div.formitem input.text:hover { + border: 1px solid #888888; +} + +div.maincontent div.propmain div.formfields div.formitem input.text:focus { + border: 1px solid #888888; +} + +div.maincontent div.propmain div.formfields div.formitem input.date { + padding: 0.5em; + max-width: 20em; + width: 95%; + border: 1px solid #BBBBBB; +} + +div.maincontent div.propmain div.formfields div.formitem input.date:hover { + border: 1px solid #888888; +} + +div.maincontent div.propmain div.formfields div.formitem input.date:focus { + border: 1px solid #888888; +} + +div.maincontent div.propmain div.formfields div.formitem div.textareawrap { + width: 95%; +} + +div.maincontent div.propmain div.formfields div.formitem textarea.text { + padding: 0.3em; + width: 100%; + border: 1px solid #BBBBBB; +} + +div.maincontent div.propmain div.formfields div.formitem textarea.text:hover { + border: 1px solid #888888; +} + +div.maincontent div.propmain div.formfields div.formitem textarea.text:focus { + border: 1px solid #888888; +} + +div.maincontent div.propmain div.formfields div.formitem input.checkbox { + border: 1px solid #BBBBBB; +} + +div.maincontent div.propmain div.formfields div.formitem select { + padding: 0.3em; + width: 95%; + max-width: 750px; + border: 1px solid #BBBBBB; +} + +div.maincontent div.propmain div.formfields div.formitem select:hover { + border: 1px solid #888888; +} + +div.maincontent div.propmain div.formfields div.formitem select:focus { + border: 1px solid #888888; +} + +div.maincontent div.propmain div.formfields div.formitem table.viewtable td { + vertical-align: top; +} + +div.maincontent div.propmain div.formfields div.formitem table.viewtable td.datakey { + padding-right: 2.0em; + font-weight: bold; + white-space: nowrap; +} + +div.maincontent div.propmain div.formfields div.formitem div.formitemdesc { + margin-left: 7px; + font-size: 0.9em; + color: #333333; +} + +div.maincontent div.propmain div.formsubmit { + margin: 15px auto; + text-align: center; +} + +div.maincontent div.propmain div.formsubmit input.submit { + color: #1F1F1F; + font-size: 0.9em; + font-weight: bold; + padding: 0.2em 0.5em; +} + +div.maincontent div.propmain hr { + margin-top: 0.7em; + border: none 0; + border-top: 1px dashed #AAAAAA; + height: 1px; +} + +div.maincontent div.propmain div.nontablewrap { + border-left: 1px solid #CCCCCC; + border-right: 1px solid #CCCCCC; + border-bottom: 1px solid #CCCCCC; + color: #333333; +} + +div.maincontent div.propmain div.nontable_row { + background-color: #F5F5F5; + border-top: 1px dashed #CCCCCC; + padding: 0.5em; +} + +div.maincontent div.propmain div.nontable_row.firstrow { + border-top: 1px solid #CCCCCC; +} + +div.maincontent div.propmain div.nontable_row.altrow { + background-color: #FEFEFE; +} + +div.maincontent div.propmain div.nontable_th { + margin-top: 0.4em; + font-weight: bold; +} + +div.maincontent div.propmain div.nontable_th.firstcol { + margin-top: 0; +} + +div.maincontent div.propmain div.nontable_td { + margin-left: 7px; + color: #444444; +} + +div.maincontent div.propmain div.mediawrap { + float: right; + margin-left: 2.0em; + max-width: 45%; + margin-bottom: 0.5em; +} + +@media (max-width: 600px) { + div.maincontent div.propmain div.mediawrap { + float: none; + margin-left: 0; + max-width: none; + } +} + +div.maincontent div.propmain div.mediawrap div.mediaitemtitle { + font-weight: bold; + margin-bottom: 0.3em; +} + +div.maincontent div.propmain div.mediawrap .mediaitem { + max-width: 100%; +} + +div.maincontent div.propmain div.mediawrap div.mediaitemdesc { + font-size: 0.9em; + color: #333333; +} + +/* Optional menu navigation styles. */ +div.menuwrap { +} + +div.menuwrap div.menu { + font-size: 0.7em; + border: 1px solid #BBBBBB; + background-color: #F5F5F5; + margin-top: 7px; + padding: 5px; +} + +div.menuwrap div.menu div.title { + font-weight: bold; + padding-bottom: 6px; +} + +div.menuwrap div.menu a { + display: block; + border-top: 1px solid #CCCCCC; + padding: 12px 15px; +} + +div.menuwrap div.menu a:hover { + background-color: #EAEAEA; + text-decoration: none; +} + +/* Floating navigation menu styles. */ +div.maincontent div.proptitle div#navbutton { + display: none; +} + +div.maincontent div.proptitle div#navdropdown { + display: none; +} diff --git a/support/admin_view_print.css b/support/admin_view_print.css new file mode 100644 index 0000000..bcd9b8b --- /dev/null +++ b/support/admin_view_print.css @@ -0,0 +1,3 @@ +div.pagewrap div.menuwrap { + display: none; +} diff --git a/support/flex_forms.php b/support/flex_forms.php new file mode 100644 index 0000000..ec5a904 --- /dev/null +++ b/support/flex_forms.php @@ -0,0 +1,1228 @@ +state = array( + "formnum" => 0, + "formtables" => true, + "formwidths" => true, + "autofocused" => false, + "jqueryuiused" => false, + "jqueryuitheme" => "smoothness", + "supporturl" => "support", + "customfieldtypes" => array(), + "ajax" => false, + "action" => self::GetRequestURLBase(), + "js" => array(), + "jsoutput" => array(), + "css" => array(), + "cssoutput" => array() + ); + + $this->secretkey = false; + $this->extrainfo = ""; + $this->autononce = false; + } + + public function GetState() + { + return $this->state; + } + + public function SetState($newstate) + { + $this->state = array_merge($this->state, $newstate); + } + + public function SetAjax($enable) + { + if ($enable) + { + $this->state["ajax"] = true; + $this->state["action"] = self::GetFullRequestURLBase(); + } + else + { + $this->state["ajax"] = false; + $this->state["action"] = self::GetRequestURLBase(); + } + } + + public function AddJS($name, $info) + { + $this->state["js"][$name] = $info; + } + + public function SetJSOutput($name) + { + $this->state["jsoutput"][$name] = true; + } + + public function AddCSS($name, $info) + { + $this->state["css"][$name] = $info; + } + + public function SetCSSOutput($name) + { + $this->state["cssoutput"][$name] = true; + } + + public function SetSecretKey($secretkey) + { + $this->secretkey = (string)$secretkey; + } + + public function SetTokenExtraInfo($extrainfo) + { + $this->extrainfo = (string)$extrainfo; + } + + public function CreateSecurityToken($action, $extra = "") + { + if ($this->secretkey === false) + { + echo self::FFTranslate("Secret key not set for form."); + exit(); + } + + $str = $action . ":" . $this->extrainfo; + if (is_string($extra) && $extra !== "") + { + $extra = explode(",", $extra); + foreach ($extra as $key) + { + $key = trim($key); + if ($key !== "" && isset($_REQUEST[$key])) $str .= ":" . (string)$_REQUEST[$key]; + } + } + else if (is_array($extra)) + { + foreach ($extra as $val) $str .= ":" . $val; + } + + return hash_hmac("sha1", $str, $this->secretkey); + } + + public static function IsSecExtraOpt($opt) + { + return (isset($_REQUEST["sec_extra"]) && strpos("," . $_REQUEST["sec_extra"] . ",", "," . $opt . ",") !== false); + } + + public function CheckSecurityToken($action) + { + if (isset($_REQUEST[$action]) && (!isset($_REQUEST["sec_t"]) || $_REQUEST["sec_t"] != $this->CreateSecurityToken($_REQUEST[$action], (isset($_REQUEST["sec_extra"]) ? $_REQUEST["sec_extra"] : "")))) + { + echo self::FFTranslate("Invalid security token. Cross-site scripting (XSRF attack) attempt averted."); + exit(); + } + else if (isset($_REQUEST[$action])) + { + $this->autononce = array("action" => $action, "value" => $_REQUEST[$action]); + } + } + + public function OutputFormCSS($delaycss = false) + { + if (!isset($this->state["cssoutput"]["formcss"])) + { + if ($delaycss) $this->state["css"]["formcss"] = array("mode" => "link", "dependency" => false, "src" => $this->state["supporturl"] . "/flex_forms.css"); + else + { +?> + " type="text/css" media="all" /> +state["cssoutput"]["formcss"] = true; + } + } + } + + public function OutputMessage($type, $message) + { + $type = strtolower((string)$type); + if ($type === "warn") $type = "warning"; + + $this->OutputFormCSS(); + +?> +
+
+
+ +
+
+
+CreateSecurityToken("forms__message", array($type, $message)); + } + + public function OutputSignedMessage($prefix = "") + { + if (isset($_REQUEST[$prefix . "msgtype"]) && isset($_REQUEST[$prefix . "msg"]) && isset($_REQUEST[$prefix . "msg_t"]) && $_REQUEST[$prefix . "msg_t"] === $this->CreateSecurityToken("forms__message", array($_REQUEST[$prefix . "msgtype"], $_REQUEST[$prefix . "msg"]))) + { + $this->OutputMessage($_REQUEST[$prefix . "msgtype"], htmlspecialchars($_REQUEST[$prefix . "msg"])); + } + } + + public function OutputJQuery($delayjs = false) + { + if (!isset($this->state["jsoutput"]["jquery"])) + { + if ($delayjs) $this->state["js"]["jquery"] = array("mode" => "src", "dependency" => false, "src" => $this->state["supporturl"] . "/jquery-3.1.1.min.js", "detect" => "jQuery"); + else + { +?> + +state["jsoutput"]["jquery"] = true; + } + } + } + + public function OutputJQueryUI($delayjs = false) + { + $this->OutputJQuery($delayjs); + + if (!isset($this->state["jsoutput"]["jqueryui"])) + { + if ($delayjs) + { + $this->state["css"]["jqueryui"] = array("mode" => "link", "dependency" => false, "src" => $this->state["supporturl"] . "/jquery_ui_themes/" . $this->state["jqueryuitheme"] . "/jquery-ui-1.12.1.css"); + $this->state["js"]["jqueryui"] = array("mode" => "src", "dependency" => "jquery", "src" => $this->state["supporturl"] . "/jquery-ui-1.12.1.min.js", "detect" => "jQuery.ui"); + } + else + { +?> + state["jqueryuitheme"] . "/jquery-ui-1.12.1.css"); ?>" type="text/css" media="all" /> + +state["cssoutput"]["jqueryui"] = true; + $this->state["jsoutput"]["jqueryui"] = true; + } + } + } + + public static function GetValue($key, $default) + { + return (isset($_REQUEST[$key]) ? $_REQUEST[$key] : $default); + } + + public static function GetSelectValues($data) + { + $result = array(); + foreach ($data as $val) $result[$val] = true; + + return $result; + } + + public static function ProcessInfoDefaults($info, $defaults) + { + foreach ($defaults as $key => $val) + { + if (!isset($info[$key])) $info[$key] = $val; + } + + return $info; + } + + public static function SetNestedPathValue(&$data, $pathparts, $val) + { + $curr = &$data; + foreach ($pathparts as $key) + { + if (!isset($curr[$key])) $curr[$key] = array(); + + $curr = &$curr[$key]; + } + + $curr = $val; + } + + public static function GetIDDiff($origids, $newids) + { + $result = array("remove" => array(), "add" => array()); + foreach ($origids as $id => $val) + { + if (!isset($newids[$id])) $result["remove"][$id] = $val; + } + + foreach ($newids as $id => $val) + { + if (!isset($origids[$id])) $result["add"][$id] = $val; + } + + return $result; + } + + public function GetHashedFieldName($name) + { + if ($this->secretkey === false) + { + echo self::FFTranslate("Secret key not set."); + exit(); + } + + return "f_" . hash_hmac("md5", $name, $this->secretkey); + } + + public function GetHashedFieldValues($nameswithdefaults) + { + if ($this->secretkey === false) + { + echo self::FFTranslate("Secret key not set."); + exit(); + } + + $result = array(); + foreach ($nameswithdefaults as $name => $default) + { + $name2 = "f_" . hash_hmac("md5", $name, $this->secretkey); + + $result[$name] = (isset($_REQUEST[$name2]) ? $_REQUEST[$name2] : $default); + } + + return $result; + } + + public function Generate($options, $errors = array(), $lastform = true) + { + $this->InitFormVars($options); + + $this->OutputFormCSS(); + +?> +
+
+state["formnum"]++; +?> +
" method="get" method="post" enctype="multipart/form-data" action="state["action"]); ?>"> +autononce !== false) + { + $options["nonce"] = $this->autononce["action"]; + + if (!isset($options["hidden"])) $options["hidden"] = array(); + if (!isset($options["hidden"][$options["nonce"]])) $options["hidden"][$options["nonce"]] = $this->autononce["value"]; + } + if (isset($options["hidden"])) + { + foreach ($options["hidden"] as $name => $value) + { +?> + + + " /> + " /> + +
"> + $field) + { + $id = "f" . $this->state["formnum"] . "_" . $num; + if (!is_string($field) && isset($field["name"])) + { + if (isset($errors[$field["name"]])) $field["error"] = $errors[$field["name"]]; + + if (isset($options["hashnames"]) && $options["hashnames"]) + { + $field["origname"] = $field["name"]; + $field["name"] = $this->GetHashedFieldName($field["name"]); + } + + $id .= "_" . $field["name"]; + } + + $this->ProcessField($num, $field, $id); + } + + $this->CleanupFields(); +?> +
+ProcessSubmit($options); + + if (isset($options["submit"]) || (isset($options["useform"]) && $options["useform"])) + { +?> +
+ +
+
+Finalize(); + } + + public static function FFTranslate() + { + $args = func_get_args(); + if (!count($args)) return ""; + + return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); + } + + public static function JSSafe($data) + { + return str_replace(array("'", "\r", "\n"), array("\\'", "\\r", "\\n"), $data); + } + + public static function IsSSLRequest() + { + return ((isset($_SERVER["HTTPS"]) && ($_SERVER["HTTPS"] == "on" || $_SERVER["HTTPS"] == "1")) || (isset($_SERVER["SERVER_PORT"]) && $_SERVER["SERVER_PORT"] == "443") || (str_replace("\\", "/", strtolower(substr($_SERVER["REQUEST_URI"], 0, 8))) == "https://")); + } + + // Returns 'http[s]://www.something.com[:port]' based on the current page request. + public static function GetRequestHost($protocol = "") + { + $protocol = strtolower($protocol); + $ssl = ($protocol == "https" || ($protocol == "" && self::IsSSLRequest())); + if ($protocol == "") $type = "def"; + else if ($ssl) $type = "https"; + else $type = "http"; + + if (!isset(self::$requesthostcache)) self::$requesthostcache = array(); + if (isset(self::$requesthostcache[$type])) return self::$requesthostcache[$type]; + + $url = "http" . ($ssl ? "s" : "") . "://"; + + $str = str_replace("\\", "/", $_SERVER["REQUEST_URI"]); + $pos = strpos($str, "?"); + if ($pos !== false) $str = substr($str, 0, $pos); + $str2 = strtolower($str); + if (substr($str2, 0, 7) == "http://") + { + $pos = strpos($str, "/", 7); + if ($pos === false) $str = ""; + else $str = substr($str, 7, $pos); + } + else if (substr($str2, 0, 8) == "https://") + { + $pos = strpos($str, "/", 8); + if ($pos === false) $str = ""; + else $str = substr($str, 8, $pos); + } + else $str = ""; + + if ($str != "") $host = $str; + else if (isset($_SERVER["HTTP_HOST"])) $host = $_SERVER["HTTP_HOST"]; + else $host = $_SERVER["SERVER_NAME"] . ":" . (int)$_SERVER["SERVER_PORT"]; + + $pos = strpos($host, ":"); + if ($pos === false) $port = 0; + else + { + $port = (int)substr($host, $pos + 1); + $host = substr($host, 0, $pos); + } + if ($port < 1 || $port > 65535) $port = ($ssl ? 443 : 80); + $url .= preg_replace('/[^a-z0-9.\-]/', "", strtolower($host)); + if ($protocol == "" && ((!$ssl && $port != 80) || ($ssl && $port != 443))) $url .= ":" . $port; + else if ($protocol == "http" && !$ssl && $port != 80) $url .= ":" . $port; + else if ($protocol == "https" && $ssl && $port != 443) $url .= ":" . $port; + + self::$requesthostcache[$type] = $url; + + return $url; + } + + public static function GetRequestURLBase() + { + $str = str_replace("\\", "/", $_SERVER["REQUEST_URI"]); + $pos = strpos($str, "?"); + if ($pos !== false) $str = substr($str, 0, $pos); + $str2 = strtolower($str); + if (substr($str2, 0, 7) == "http://" || substr($str2, 0, 8) == "https://") + { + $pos = strpos($str, "/", 8); + if ($pos === false) $str = "/"; + else $str = substr($str, $pos); + } + + return $str; + } + + public static function GetFullRequestURLBase($protocol = "") + { + return self::GetRequestHost($protocol) . self::GetRequestURLBase(); + } + + public static function RegisterFormHandler($mode, $callback) + { + if (!isset(self::$formhandlers) || !is_array(self::$formhandlers)) self::$formhandlers = array("init" => array(), "field_string" => array(), "field_type" => array(), "table_row" => array(), "cleanup" => array(), "finalize" => array()); + + if (isset(self::$formhandlers[$mode])) self::$formhandlers[$mode][] = $callback; + } + + protected function InitFormVars(&$options) + { + if (!isset(self::$formhandlers) || !is_array(self::$formhandlers)) self::$formhandlers = array("init" => array(), "field_string" => array(), "field_type" => array(), "table_row" => array(), "cleanup" => array(), "finalize" => array()); + + $this->state["insiderow"] = false; + $this->state["firstitem"] = false; + + // Let form handlers modify the options and state arrays. + foreach (self::$formhandlers["init"] as $callback) + { + if (is_callable($callback)) call_user_func_array($callback, array(&$this->state, &$options)); + } + } + + protected function AlterField($num, &$field, $id) + { + // Let form handlers process custom, modified, and other field types. + foreach (self::$formhandlers["field_type"] as $callback) + { + if (is_callable($callback)) call_user_func_array($callback, array(&$this->state, $num, &$field, $id)); + } + } + + protected function ProcessField($num, &$field, $id) + { + if (is_string($field)) + { + if ($field == "split" && !$this->state["insiderow"]) echo "
"; + else if ($field == "startrow") + { + if ($this->state["insiderow"]) echo ""; + else if ($this->state["formtables"]) + { + $this->state["insiderow"] = true; +?> +
"> +state["firstitem"] = false; + } + } + else if ($field == "endrow" && $this->state["formtables"]) + { +?> +
+state["insiderow"] = false; + } + else if (substr($field, 0, 5) == "html:") + { + echo substr($field, 5); + } + + // Let form handlers process strings. + foreach (self::$formhandlers["field_string"] as $callback) + { + if (is_callable($callback)) call_user_func_array($callback, array(&$this->state, $num, &$field, $id)); + } + } + else if (!isset($field["type"]) || (isset($field["use"]) && !$field["use"])) + { + // Do nothing if type is not specified. + } + else if (isset($this->state["customfieldtypes"][$field["type"]])) + { + // Output custom fields. + $this->AlterField($num, $field, $id); + } + else + { + if ($this->state["insiderow"]) echo ""; +?> +
state["firstitem"] ? " firstitem" : ""); ?>"> +state["firstitem"] = false; + if (isset($field["title"])) + { + if (is_string($field["title"])) + { +?> +
+ +
+state["insiderow"]) + { +?> +
 
+state["formwidths"]) unset($field["width"]); + + if (isset($field["name"]) && isset($field["default"])) + { + if ($field["type"] == "select") + { + if (!isset($field["select"])) + { + $field["select"] = self::GetValue($field["name"], $field["default"]); + if (is_array($field["select"])) $field["select"] = self::GetSelectValues($field["select"]); + } + } + else if ($field["type"] == "checkbox") + { + if (!isset($field["check"]) && isset($field["value"])) $field["check"] = (isset($_REQUEST[$field["name"]]) && $_REQUEST[$field["name"]] === $field["value"] ? true : ($_SERVER["REQUEST_METHOD"] === "GET" ? $field["default"] : false)); + } + else + { + if (!isset($field["value"])) $field["value"] = self::GetValue($field["name"], $field["default"]); + } + } + + if (isset($field["focus"]) && $field["focus"] && $this->state["autofocused"] === false) $this->state["autofocused"] = $id; + if ($field["type"] == "select" && isset($field["mode"]) && $field["mode"] == "formhandler") unset($field["mode"]); + + $this->AlterField($num, $field, $id); + + switch ($field["type"]) + { + case "static": + { +?> +
+
>
+
+ +
+
type="text" id="" name="" value=""state["autofocused"] === $id) echo " autofocus"; ?> />
+
+ +
+
type="password" id="" name="" value=""state["autofocused"] === $id) echo " autofocus"; ?> />
+
+ +
+
+ " value=""state["autofocused"] === $id) echo " autofocus"; ?> /> + +
+
+ true); + +?> +
+ $value) + { + if (is_array($value)) + { + foreach ($value as $name2 => $value2) + { + $id2 = $idbase . ($idnum ? "_" . $idnum : ""); +?> +
+ " value=""state["autofocused"] === $id) { echo " autofocus"; $this->state["autofocused"] = true; } ?> /> + +
+ +
+ " value=""state["autofocused"] === $id) { echo " autofocus"; $this->state["autofocused"] = true; } ?> /> + +
+ +
+ +
+ +
+ +
+
+
+ +
+state["formtables"]) + { +?> + > + + "head"); + $colattrs = array(); + if (!isset($field["cols"])) $field["cols"] = array(); + foreach ($field["cols"] as $col) $colattrs[] = array(); + foreach (self::$formhandlers["table_row"] as $callback) + { + if (is_callable($callback)) call_user_func_array($callback, array(&$this->state, $num, &$field, $idbase, "head", -1, &$trattrs, &$colattrs, &$field["cols"])); + } +?> + $val) echo " " . $key . "=\"" . htmlspecialchars($val) . "\""; ?>> + $col) + { +?> + $val) echo " " . $key . "=\"" . htmlspecialchars($val) . "\""; ?>> + + + + + "row" . ($altrow ? " altrow" : "")); + $colattrs2 = $colattrs; + foreach (self::$formhandlers["table_row"] as $callback) + { + if (is_callable($callback)) call_user_func_array($callback, array(&$this->state, $num, &$field, $idbase, "body", $rownum, &$trattrs, &$colattrs2, &$row)); + } + + if (count($row) < count($colattrs2)) $colattrs2[count($colattrs2) - 1]["colspan"] = (count($colattrs2) - count($row) + 1); +?> + $val) echo " " . $key . "=\"" . htmlspecialchars($val) . "\""; ?>> + + $val) echo " " . $key . "=\"" . htmlspecialchars($val) . "\""; } ?>> + + + + +
+ +
+ $col) + { + $headcolattrs[] = array("class" => "nontable_th" . ($num2 ? "" : " firstcol")); + } + foreach (self::$formhandlers["table_row"] as $callback) + { + if (is_callable($callback)) call_user_func_array($callback, array(&$this->state, $num, &$field, $idbase, "head", -1, &$trattrs, &$headcolattrs, &$field["cols"])); + } + + $colattrs = array(); + foreach ($field["cols"] as $col) $colattrs[] = array("class" => "nontable_td"); + + $rownum = 0; + $altrow = false; + if (isset($field["callback"]) && is_callable($field["callback"])) $field["rows"] = call_user_func($field["callback"]); + while (count($field["rows"])) + { + foreach ($field["rows"] as $row) + { + // Let form handlers process the current row. + $trattrs = array("class" => "nontable_row" . ($altrow ? " altrow" : "") . ($rownum ? "" : " firstrow")); + $colattrs2 = $colattrs; + foreach (self::$formhandlers["table_row"] as $callback) + { + if (is_callable($callback)) call_user_func_array($callback, array(&$this->state, $num, &$field, $idbase, "body", $rownum, &$trattrs, &$colattrs2, &$row)); + } + +?> + $val) echo " " . $key . "=\"" . htmlspecialchars($val) . "\""; ?>> + + $val) echo " " . $key . "=\"" . htmlspecialchars($val) . "\""; ?>>
+ $val) echo " " . $key . "=\"" . htmlspecialchars($val) . "\""; } ?>>
+ +
+ + + + + +
+
"state["autofocused"] === $id) echo " autofocus"; ?> />
+
+ +
+ +
+ +
+
+
+ + +state["insiderow"]) echo ""; + } + } + + protected function CleanupFields() + { + if ($this->state["insiderow"]) + { +?> + +state)); + } + } + + protected function ProcessSubmit(&$options) + { + if (is_string($options["submit"])) + { + $name = (isset($options["submitname"]) ? $options["submitname"] : ""); + $options["submit"] = array($name => $options["submit"]); + } +?> +
+ $val) + { + if (is_int($name) && isset($options["submitname"])) $name = $options["submitname"]; +?> + GetHashedFieldName($name) : $name) . "\""; ?> value="" /> + +
+ + +OutputJQuery(true); + + if ($this->state["jqueryuiused"]) $this->OutputJQueryUI(true); + + // Output CSS. + $output = $this->state["cssoutput"]; + foreach ($output as $name => $val) unset($this->state["css"][$name]); + if ($this->state["ajax"]) echo "\n"; + foreach ($this->state["css"] as $name => $info) + { + if ($info["mode"] === "inline") echo $info["src"]; + } + $this->state["css"] = array(); + $this->state["cssoutput"] = $output; + + // Output Javascript. + $output = $this->state["jsoutput"]; + foreach ($output as $name => $val) unset($this->state["js"][$name]); + do + { + $found = false; + + foreach ($this->state["js"] as $name => $info) + { + if ($info["dependency"] === false || !isset($this->state["js"][$info["dependency"]])) + { + if ($info["mode"] === "src") + { + if ($this->state["ajax"]) echo "FlexForms.jsqueue['" . self::JSSafe($name) . "'] = " . json_encode($info, JSON_UNESCAPED_SLASHES) . ";\n"; + else echo "\n"; + } + else if ($info["mode"] === "inline") + { + if (!$this->state["ajax"]) echo "\n"; + else + { + $src = $info["src"]; + unset($info["src"]); + echo "FlexForms.jsqueue['" . self::JSSafe($name) . "'] = " . json_encode($info, JSON_UNESCAPED_SLASHES) . ";\n"; + echo "FlexForms.jsqueue['" . self::JSSafe($name) . "'].src = function() {\n" . $src . "};\n"; + } + } + + unset($this->state["js"][$name]); + $output[$name] = true; + + $found = true; + } + } + } while ($found); + $this->state["js"] = array(); + $this->state["jsoutput"] = $output; + + // Let form handlers finalize other field types. + foreach (self::$formhandlers["finalize"] as $callback) + { + if (is_callable($callback)) call_user_func_array($callback, array(&$this->state)); + } + + // Initialize FlexForms (only needed for any AJAX bits). +?> + + \ No newline at end of file diff --git a/support/page_basics.php b/support/page_basics.php index 9764e48..b188663 100644 --- a/support/page_basics.php +++ b/support/page_basics.php @@ -2,96 +2,50 @@ // Admin Pack server-side page manipulation functions. // (C) 2017 CubicleSoft. All Rights Reserved. + // Most functionality has been moved into FlexForms. Much of this is legacy interface code for convenience and to avoid breaking things badly. + require_once "flex_forms.php"; + if (file_exists(str_replace("\\", "/", dirname(__FILE__)) . "/flex_forms_extras.php")) require_once str_replace("\\", "/", dirname(__FILE__)) . "/flex_forms_extras.php"; + + class BB_FlexForms extends FlexForms + { + public function CreateSecurityToken($action, $extra = "") + { + global $bb_randpage, $bb_usertoken; + + $this->SetSecretKey($bb_randpage . ":" . $bb_usertoken); + + return parent::CreateSecurityToken($action, $extra); + } + } + + // Can be used to override default functionality with an extended class. However, form handlers are generally a better solution. + $bb_flexforms = new BB_FlexForms(); + // Code swiped from Barebones CMS support functions. function BB_JSSafe($data) { - return str_replace(array("'", "\r", "\n"), array("\\'", "\\r", "\\n"), $data); + return FlexForms::JSSafe($data); } function BB_IsSSLRequest() { - return ((isset($_SERVER["HTTPS"]) && ($_SERVER["HTTPS"] == "on" || $_SERVER["HTTPS"] == "1")) || (isset($_SERVER["SERVER_PORT"]) && $_SERVER["SERVER_PORT"] == "443") || (str_replace("\\", "/", strtolower(substr($_SERVER["REQUEST_URI"], 0, 8))) == "https://")); + return FlexForms::IsSSLRequest(); } // Returns 'http[s]://www.something.com[:port]' based on the current page request. function BB_GetRequestHost($protocol = "") { - global $bb_getrequesthost_cache; - - $protocol = strtolower($protocol); - $ssl = ($protocol == "https" || ($protocol == "" && BB_IsSSLRequest())); - if ($protocol == "") $type = "def"; - else if ($ssl) $type = "https"; - else $type = "http"; - - if (!isset($bb_getrequesthost_cache)) $bb_getrequesthost_cache = array(); - if (isset($bb_getrequesthost_cache[$type])) return $bb_getrequesthost_cache[$type]; - - $url = "http" . ($ssl ? "s" : "") . "://"; - if ($ssl && defined("HTTPS_SERVER") && HTTPS_SERVER != "") $url .= HTTPS_SERVER; - else if (!$ssl && defined("HTTP_SERVER") && HTTP_SERVER != "") $url .= HTTP_SERVER; - else - { - $str = str_replace("\\", "/", $_SERVER["REQUEST_URI"]); - $pos = strpos($str, "?"); - if ($pos !== false) $str = substr($str, 0, $pos); - $str2 = strtolower($str); - if (substr($str2, 0, 7) == "http://") - { - $pos = strpos($str, "/", 7); - if ($pos === false) $str = ""; - else $str = substr($str, 7, $pos); - } - else if (substr($str2, 0, 8) == "https://") - { - $pos = strpos($str, "/", 8); - if ($pos === false) $str = ""; - else $str = substr($str, 8, $pos); - } - else $str = ""; - - if ($str != "") $host = $str; - else if (isset($_SERVER["HTTP_HOST"])) $host = $_SERVER["HTTP_HOST"]; - else $host = $_SERVER["SERVER_NAME"] . ":" . (int)$_SERVER["SERVER_PORT"]; - - $pos = strpos($host, ":"); - if ($pos === false) $port = 0; - else - { - $port = (int)substr($host, $pos + 1); - $host = substr($host, 0, $pos); - } - if ($port < 1 || $port > 65535) $port = ($ssl ? 443 : 80); - $url .= preg_replace('/[^a-z0-9.\-]/', "", strtolower($host)); - if ($protocol == "" && ((!$ssl && $port != 80) || ($ssl && $port != 443))) $url .= ":" . $port; - else if ($protocol == "http" && !$ssl && $port != 80) $url .= ":" . $port; - else if ($protocol == "https" && $ssl && $port != 443) $url .= ":" . $port; - } - - $bb_getrequesthost_cache[$type] = $url; - - return $url; + return FlexForms::GetRequestHost($protocol); } function BB_GetRequestURLBase() { - $str = str_replace("\\", "/", $_SERVER["REQUEST_URI"]); - $pos = strpos($str, "?"); - if ($pos !== false) $str = substr($str, 0, $pos); - $str2 = strtolower($str); - if (substr($str2, 0, 7) == "http://" || substr($str2, 0, 8) == "https://") - { - $pos = strpos($str, "/", 8); - if ($pos === false) $str = "/"; - else $str = substr($str, $pos); - } - - return $str; + return FlexForms::GetRequestURLBase(); } function BB_GetFullRequestURLBase($protocol = "") { - return BB_GetRequestHost($protocol) . BB_GetRequestURLBase(); + return FlexForms::GetFullRequestURLBase($protocol); } // Multilingual admin functions. @@ -198,6 +152,9 @@ function BB_InitLangmap($path, $default = "") } } + // Route all translation requests through BB_Translate(). + if (!defined("CS_TRANSLATE_FUNC")) define("CS_TRANSLATE_FUNC", "BB_Translate"); + // Code swiped from CubicleSoft browser cookie support functions. function SetCookieFixDomain($name, $value = "", $expires = 0, $path = "", $domain = "", $secure = false, $httponly = false) @@ -228,31 +185,18 @@ function SetCookieFixDomain($name, $value = "", $expires = 0, $path = "", $domai } - function BB_OutputJQueryUI($rooturl, $supportpath) - { -?> - " type="text/css" media="all" /> - - array(), "field_string" => array(), "field_type" => array(), "table_row" => array(), "finalize" => array()); - - $bb_propformhandlers[$mode][] = $callback; + FlexForms::RegisterFormHandler($mode, $callback); } - // Slightly modified code swiped from Barebone CMS editing routines. + // Originally from Barebone CMS editing routines. Most functionality has now transitioned to FlexForms. function BB_PropertyForm($options) { - global $bb_formtables, $bb_formwidths, $bb_propformhandlers; + global $bb_formtables, $bb_formwidths, $bb_flexforms; if (!isset($bb_formtables) || !is_bool($bb_formtables)) $bb_formtables = true; if (!isset($bb_formwidths) || !is_bool($bb_formwidths)) $bb_formwidths = true; - if (!isset($bb_propformhandlers) || !is_array($bb_propformhandlers)) $bb_propformhandlers = array("init" => array(), "field_string" => array(), "field_type" => array(), "table_row" => array(), "finalize" => array()); // Certain types of fields require the Admin Pack extras package. if (defined("BB_ROOT_URL")) $rooturl = BB_ROOT_URL; @@ -268,21 +212,13 @@ function BB_PropertyForm($options) else if (defined("SUPPORT_PATH")) $supportpath = SUPPORT_PATH; else $supportpath = "support"; - $state = array( - "autofocus" => false, - "jqueryuiused" => false, - "rooturl" => $rooturl, - "supportpath" => $supportpath, - "insiderow" => false, - "firstitem" => false, - "customfieldtypes" => array() - ); - - // Let property form handlers modify the options and state arrays. - foreach ($bb_propformhandlers["init"] as $callback) - { - if (is_callable($callback)) call_user_func_array($callback, array(&$state, &$options)); - } + // Initialize the FlexForms class instance. Override a few defaults for AdminPack template integration. + $bb_flexforms->SetState(array("supporturl" => $rooturl . "/" . $supportpath, "formtables" => $bb_formtables, "formwidths" => $bb_formwidths)); + $bb_flexforms->SetJSOutput("jquery"); + $bb_flexforms->SetCSSOutput("formcss"); + + // AdminPack provides its own security token logic and doesn't need stringent randomized field support. + unset($options["hashnames"]); ?>