15 Commits
1.0.4 ... 2.0.1

Author SHA1 Message Date
b03fc3b7dd composer version changes
composer version changes
2025-01-21 20:59:01 -05:00
61589b35ff Add APP_NAME constant
Improved issue display
Add component::prepend
Add switches to form html
Updated form html to Bootstrap 5.
Updated navigation html to Bootstrap 5.
2025-01-21 20:46:49 -05:00
d9e61d3f8f Merge remote-tracking branch 'origin/update_PHP_deps_20240820103006' 2024-08-20 06:30:48 -04:00
8e377d0882 Update PHP dependencies [20240820103006] 2024-08-20 10:30:08 +00:00
ea7ea1286c remove custom DTC setup and fixes for customexception 2024-08-20 06:29:45 -04:00
1d71b4fd13 Add support for preg_replace_callback filters
to support changes needed for DTC to be moved
2024-08-20 06:29:08 -04:00
de930d00c9 add function for only setting non-existent components 2024-08-20 06:28:27 -04:00
4d2ccfb1c5 prevent runaway branching 2024-08-12 23:43:46 -04:00
eb0644feb5 Merge remote-tracking branch 'origin/update_PHP_deps_20240813032014' 2024-08-12 23:20:35 -04:00
9eca4c9e6a Update PHP dependencies [20240813032014] 2024-08-13 03:20:15 +00:00
bb30be33b2 pipeline update 2024-08-12 23:19:59 -04:00
168a9ee805 Merge remote-tracking branch 'origin/update_PHP_deps_20240813023647' 2024-08-12 22:38:33 -04:00
42dbedf9ee Update PHP dependencies [20240813023647] 2024-08-13 02:36:49 +00:00
65d61db539 add gitlab pipeline 2024-08-12 22:36:39 -04:00
cbaba96d5e canary update 2024-08-09 17:16:21 -04:00
12 changed files with 259 additions and 71 deletions

64
.gitignore vendored Normal file
View File

@ -0,0 +1,64 @@
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# OSX
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# keep specific directories
!uploads/images/.gitignore
!bin/cli/.gitignore
# keep main directories
!css/.gitignore
!vendor/.gitignore
# SublimeText
*.sublime-project
*.sublime-workspace
# TheTempusProject Specific
.htaccess
app/config/*
!app/config/constants.php
uploads/images/*
logs/*
.vscode/
mail.log
vendor/canary/logs/*
docker/.env

49
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,49 @@
stages:
- update
variables:
TIMEZONE: "America/New_York" # For the system in general
DATE_TIMEZONE: ${TIMEZONE} # For PHP
GIT_DEPTH: 1
GITLAB_API_URL: ${CI_API_V4_URL}
TARGET_BRANCH: ${CI_COMMIT_REF_NAME} # This is the branch chosen in the `Pipeline Schedule`
TARGET_REMOTE: "https://${GITLAB_USERNAME}:${GITLAB_ACCESS_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}.git"
# These could/should be overridden in an extending job:
UPDATE_BRANCH_PREFIX: "update_PHP_deps_" # Used for the update branch name, it will be followed by the datetime
GIT_USER: "DependBot" # Used for the update commit
GIT_EMAIL: "webmaster@thetempusproject.com" # Used for the update commit
GITLAB_USERNAME: "root" # Used for pushing the new branch and opening the MR
GITLAB_ACCESS_TOKEN: "glpat-PKEmivGtBfbz4DVPdhzk" # Used for pushing the new branch and opening the MR
MERGE_IF_SUCCESSFUL: "true" # Set to true, to merge automatically if the pipeline succeeds
SECONDS_BETWEEN_POOLING: 10 # Nbr of seconds between checking if the MR pipeline is successful, so then it will merge
JOB_GIT_FLAGS: ""
JOB_CURL_FLAGS: ""
JOB_COMPOSER_FLAGS: ""
composer_update:
stage: update
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
image: composer:latest
interruptible: true # allows to stop the job if a newer pipeline starts, saving resources and allowing new jobs to start because job concurrency is limited
script:
- git ${JOB_GIT_FLAGS} fetch origin ${TARGET_BRANCH}
- git ${JOB_GIT_FLAGS} checkout ${TARGET_BRANCH}
- git reset --hard origin/main
- export DATE_TIME="$(date '+%Y%m%d%H%M%S')"
- export MR_BRANCH="${UPDATE_BRANCH_PREFIX}${DATE_TIME}"
- git ${JOB_GIT_FLAGS} checkout -b "${MR_BRANCH}"
- composer update ${JOB_COMPOSER_FLAGS}
- if [ "$(git diff)" == "" ]; then echo "No updates needed!"; exit 0; fi
- export TITLE="Update PHP dependencies [${DATE_TIME}]"
- git ${JOB_GIT_FLAGS} commit -a -m "${TITLE}"
- git ${JOB_GIT_FLAGS} push "${TARGET_REMOTE}" "${MR_BRANCH}"
artifacts:
paths:
- vendor/
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- vendor/

View File

@ -36,12 +36,24 @@ class Components {
self::$components[ $name ] = $value;
return true;
}
public static function setIfNull( $name, $value = '' ) {
if ( ! empty( self::$components[ $name ] ) ) {
return;
}
if ( null == $value ) {
$value = '';
}
self::$components[ $name ] = $value;
return true;
}
public static function unset( $name ) {
if ( isset( self::$components[ $name ] ) ) {
unset( self::$components[ $name ] );
}
return true;
}
public static function append( $name, $value ) {
if ( ! isset( self::$components[ $name ] ) ) {
return self::set( $name, $value );
@ -50,4 +62,13 @@ class Components {
self::$components[ $name ] = $curr . $value;
return true;
}
public static function prepend( $name, $value ) {
if ( ! isset( self::$components[ $name ] ) ) {
return self::set( $name, $value );
}
$curr = self::$components[ $name ];
self::$components[ $name ] = $value . $curr;
return true;
}
}

View File

@ -48,7 +48,20 @@ class Filters {
}
foreach ( self::$filters as $pattern ) {
if ( $pattern['enabled'] || false !== $force ) {
$data = trim( preg_replace( $pattern['match'], $pattern['replace'], $data ) );
if ( ! empty( $pattern['callback'] ) && is_array( $pattern['callback'] ) ) {
$result = preg_replace_callback(
$pattern['match'],
$pattern['callback'],
$data
);
} else {
$result = preg_replace(
$pattern['match'],
$pattern['replace'],
$data
);
}
$data = trim( $result );
}
}
return $data;
@ -59,13 +72,20 @@ class Filters {
return $data;
}
if ( self::$filters[$name]['enabled'] || false !== $force ) {
$data = trim(
preg_replace(
if ( ! empty( self::$filters[$name]['callback'] ) && is_array( self::$filters[$name]['callback'] ) ) {
$result = preg_replace_callback(
self::$filters[$name]['match'],
self::$filters[$name]['callback'],
$data
);
} else {
$result = preg_replace(
self::$filters[$name]['match'],
self::$filters[$name]['replace'],
$data
)
);
);
}
$data = trim( $result );
}
return $data;
}
@ -78,12 +98,21 @@ class Filters {
* @param {string} [$replace]
* @param {bool} [$enabled] - Whether the filter should be enabled or disabled.
*/
public static function add( $filterName, $match, $replace, $enabled = false ) {
public static function add( $filterName, $match, $replace, $enabled = false, $callback = false ) {
if ( isset( self::$filters[$filterName] ) ) {
Debug::error( "Filter already exists: $filterName" );
return;
}
self::$filters[$filterName] = [
if ( $callback === true ) {
self::$filters[ $filterName ] = [
'name' => $filterName,
'match' => $match,
'callback' => $replace,
'enabled' => $enabled,
];
return;
}
self::$filters[ $filterName ] = [
'name' => $filterName,
'match' => $match,
'replace' => $replace,

View File

@ -11,6 +11,7 @@
*/
namespace TheTempusProject\Houdini\Classes;
use TheTempusProject\Canary\Bin\Canary as Debug;
use DateTimeZone;
class Forms {
@ -66,6 +67,9 @@ class Forms {
case 'checkbox':
$fieldHtml = self::getCheckboxHtml( $fieldname, $defaultValue );
break;
case 'switch':
$fieldHtml = self::getSwitchHtml( $fieldname, $defaultValue );
break;
case 'timezone':
$fieldHtml = self::getTimezoneHtml( $defaultValue );
break;
@ -76,19 +80,18 @@ class Forms {
Debug::error( "unknown field type: $type" );
break;
}
$out .= '<div class="form-group">';
$out .= '<label for="' . $fieldname . '" class="col-lg-3 control-label">' . $fieldTitle . '</label>';
$out .= '<div class="col-lg-3">';
$out .= '<div class="mb-3">';
$out .= '<label for="' . $fieldname . '" class="form-label">' . $fieldTitle . '</label>';
$out .= $fieldHtml;
$out .= '</div>';
// @todo need to remove this or make it more generic (can't depend on bedrock anymore)
if ( 'file' === $type ) {
$out .= '<div class="col-lg-3 avatar-125" align="center">';
$out .= '<img alt="User Avatar" src="' . $defaultValue . '" class="img-circle img-responsive">';
$out .= '<img alt="User Avatar" src="{ROOT_URL}' . $defaultValue . '" class="img-circle img-fluid p-2">';
$out .= '</div>';
}
$out .= '</div>';
return $out;
return Template::parse( $out );
}
public static function getTimezoneHtml( $default ) {
@ -117,7 +120,8 @@ class Forms {
} else {
$checked = '';
}
return '<input type="checkbox" class="form-control" name="' . $name . '" id="' . $name . '" value="true"' . $checked . '>';
return '<div class="form-check"><input class="form-check-input" type="checkbox" value="true" name="' . $name . '" id="' . $name . '"' . $checked . '>
<label class="form-check-label" for="' . $name . '">' . ucfirst($name) . '</label></div>';
}
public static function getTextHtml( $name, $default = '' ) {
@ -133,7 +137,8 @@ class Forms {
public static function getSelectHtml( $name, $options, $default = null ) {
$out = '<select name="' . $name . '" id="' . $name . '" class="form-control">';
if ( !is_string( $options ) ) {
if ( is_iterable( $options ) ) {
$out .= self::getOptionsHtml( $options, $default );
} else {
$out .= $options;
@ -190,4 +195,17 @@ class Forms {
</fieldset>';
return $out;
}
public static function getSwitchHtml( $name, $default = null ) {
$checked = '';
if ( ! empty( $default ) ) {
$checked = ' checked="checked"';
}
$out = '<div class="mb-3 form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="' . $name . '" id="' . $name . '" value="true"' . $checked . '>
</div>';
return $out;
}
}

View File

@ -37,6 +37,9 @@ class Issues {
$child = [ $child ];
}
foreach ( $child as $children ) {
if ( ! is_string( $children ) ) {
$children = var_export( $children, true );
}
$out .= '<li>' . $children . '</li>';
}
$out .= '</ul>';

View File

@ -57,17 +57,36 @@ class Navigation extends Template {
self::$menus_array[ $menuName ][] = self::normalizeLinkArray( $link );
}
public static function getMenuLinks( $menuName ) {
if ( !isset( self::$menus_array[ $menuName ] ) ) {
Debug::debug( 'menu link mot found, creating new:' . $menuName);
return false;
}
$out = [];
foreach ( self::$menus_array[ $menuName ] as $key => $item) {
$filter = $item['filter'];
if ( ! empty( $filter ) ) {
$input = '{' . strtoupper($item['filter']) . '}testing{/' . strtoupper($item['filter']) . '}';
$output = Filters::apply( $input );
// 20 years later and a zero 'location' still gets me....
if ( ! stripos( $output, 'esting' ) ) {
continue;
}
}
$out[] = (object) $item;
}
return $out;
}
public static function getListItem( $link, $class = '' ) {
$link = self::normalizeLinkArray( $link );
if ( empty( $link ) ) {
return '';
}
if ( ! empty( $class ) ) {
$class = ' class="' . $class . '"';
}
// this is a good idea but it breaks active page select because the regex is not equipped to deal with the inclusion of a 'class' field in the list items field
// $list_item_class = ''; # the class for individual list items
$data = '<li'.$class.'><a href="' . $link['url'] . '">' . $link['text'] . '</a></li>';
$data = '<li class="'.$class.' nav-item"><a href="' . $link['url'] . '" class="nav-link">' . $link['text'] . '</a></li>';
if ( !empty( $link['filter'] ) ) {
$data = '{' . strtoupper($link['filter']) . '}'.$data.'{/' . strtoupper($link['filter']) . '}';
$data = Filters::apply( $data );
@ -88,8 +107,8 @@ class Navigation extends Template {
foreach ( $link['url'] as $key => $sub_link ) {
$sub_list .= self::getListItem( $sub_link, 'submenu' );
}
$list .= '<li>
<a href="javascript:;" data-toggle="collapse" data-target="#menu-collapse-' . self::$collapse_count . '">
$list .= '<li class="nav-item">
<a href="javascript:;" class="nav-link" data-toggle="collapse" data-target="#menu-collapse-' . self::$collapse_count . '">
' . $link['text'] . '<i class="fa fa-fw fa-caret-down"></i>
</a>
<ul id="menu-collapse-' . self::$collapse_count . '" class="collapse">' . $sub_list . '</ul>
@ -120,7 +139,6 @@ class Navigation extends Template {
}
if ( !empty( $previousCrumb ) ) {
$allCrumbs .= ' <b>></b> ';
$previousCrumb = trim( $previousCrumb ) . '/' . $crumb;
} else {
$previousCrumb = $crumb;
@ -132,25 +150,27 @@ class Navigation extends Template {
'view' === substr( $crumb, 0, 4 ) ||
$url === Routes::getRequestUrl() ||
'edit' === substr( $crumb, 0, 4 ) ) {
$allCrumbs .= '<b>' . ucfirst( $crumb ) . '</b>';
$allCrumbs .= '<li class="breadcrumb-item active" aria-current="page">' . ucfirst( $crumb ) . '</li>';
break;
}
$allCrumbs .= '<a href="' . $url . '">' . ucfirst( $crumb ) . '</a>';
$allCrumbs .= '<li class="breadcrumb-item"><a href="' . $url . '" class="text-decoration-none atb-green">' . ucfirst( $crumb ) . '</a></li>';
}
Components::set( $breadcrumb_component, $allCrumbs );
$nav = '';
$nav .= '<nav aria-label="breadcrumb">';
$nav .= ' <ol class="breadcrumb">';
$nav .= ' ' . $allCrumbs;
$nav .= ' </ol>';
$nav .= '</nav>';
Components::set( $breadcrumb_component, $nav );
}
/**
* This function parses either given html or the current page content and sets
* the current active page to selected within an html list.
*
* @param string $menu - The name of the view you wish to add. can be any arbitrary value if $view is
* provided.
* @param string $selectString - The string/url you are searching for, default model/controller is used if none is
* provided.
* @param string $menu - The name of the view you wish to add. can be any arbitrary value if $view is provided.
* @param string $selectString - The string/url you are searching for, default model/controller is used if none is provided.
* @param string $view - The html you want parsed, view is generated from menu name if $view is left blank
*
* @return string|bool - returns bool if the menu was added to the page content or
* returns the parsed view if one was provided with the
* function call.

View File

@ -15,9 +15,8 @@
namespace TheTempusProject\Houdini\Classes;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Canary\Classes\CustomException;
use TheTempusProject\Hermes\Functions\Route as Routes;
// use TheTempusProject\Bedrock\Functions\Date;
// use TheTempusProject\Bedrock\Classes\CustomException;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Houdini\Classes\Filters;
@ -37,7 +36,7 @@ class Template {
*/
public function __construct() {
Debug::group( 'Template Constructor', 1 );
Components::set( 'SITENAME', 'Houdini Site' );
Components::set( 'SITENAME', APP_NAME );
Components::set( 'ROOT_URL', Routes::getRoot() );
Components::set( 'ROOT_ADDRESS', Routes::getAddress() );
Components::set( 'TITLE', '' );
@ -50,25 +49,25 @@ class Template {
Filters::add( 'issues', '#{ISSUES}(.*?){/ISSUES}#is', ( Issues::hasIssues() ? '$1' : '' ), true );
$notices = implode( '<br>', Issues::getNoticeMessages() );
if ( !empty( $notices ) ) {
$notices = '<div class="alert alert-warning" role="alert">' . $notices . '</div>';
$notices = '<div class="alert alert-warning w-100" role="alert">' . $notices . '</div>';
}
Components::set( 'NOTICE', $notices );
$successes = implode( '<br>', Issues::getSuccessMessages() );
if ( !empty( $successes ) ) {
$successes = '<div class="alert alert-success" role="alert">' . $successes . '</div>';
$successes = '<div class="alert alert-success w-100" role="alert">' . $successes . '</div>';
}
Components::set( 'SUCCESS', $successes );
$errors = implode( '<br>', Issues::getErrorMessages() );
if ( !empty( $errors ) ) {
$errors = '<div class="alert alert-danger" role="alert">' . $errors . '</div>';
$errors = '<div class="alert alert-danger w-100" role="alert">' . $errors . '</div>';
}
Components::set( 'ERROR', $errors );
$infos = implode( '<br>', Issues::getInfoMessages() );
if ( !empty( $infos ) ) {
$infos = '<div class="alert alert-info" role="alert">' . $infos . '</div>';
$infos = '<div class="alert alert-info w-100" role="alert">' . $infos . '</div>';
}
Components::set( 'INFO', $infos );
}
@ -108,7 +107,7 @@ class Template {
return self::loadTemplate( $location, $name );
}
}
// new CustomException( 'template', $docLocation );
new CustomException( 'template', $docLocation );
}
/**
@ -126,7 +125,7 @@ class Template {
$fullPath = $path . $name . '.inc.php';
$className = APP_SPACE . '\\Templates\\' . ucfirst( $name ) . 'Loader';
if ( !file_exists( $fullPath ) ) {
// new CustomException( 'templateLoader', $fullPath );
new CustomException( 'templateLoader', $fullPath );
} else {
Debug::log( 'Requiring template loader: ' . $name );
require_once $fullPath;
@ -202,7 +201,7 @@ class Template {
self::buildRobot();
self::buildHeaders();
if ( empty( self::$templateLocation ) ) {
// throw an error here @todo
new CustomException( 'templateLocation', self::$templateLocation );
return;
}
if ( !Debug::status( 'render' ) ) {
@ -315,7 +314,6 @@ class Template {
* be used as components for the provided html.
* @return string - The fully parsed html output.
*/
public static function parse( $template, $data = null, $flags = null ) {
if ( empty( $template ) ) {
return $template;
@ -330,22 +328,6 @@ class Template {
$template = preg_replace( '#\{OPTION\=(.*?)\}#is', '', $template );
}
//Convert any dates into preferred Date/Time format. User preference will be applied her in the future.
// @todo - there must be something to migrate this ability from the top to the bottom when a user has a pref
$dtc = '#{DTC(.*?)}(.*?){/DTC}#is';
$template = preg_replace_callback(
$dtc,
function ( $data ) {
if ( empty( $data[2] ) ) {
return '';
}
return $data[2]; // @todo need a way to decouple this from houdini to bedrock
// return Date::formatTimestamp( $data[1], $data[2] );
},
$template
);
//Run through our full list of generated filters.
$template = Filters::apply( $template );

View File

@ -12,6 +12,7 @@
namespace TheTempusProject\Houdini\Classes;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Canary\Classes\CustomException;
class Views extends Template {
public static $additionalLocations = [];
@ -34,7 +35,7 @@ class Views extends Template {
if ( !empty( $out ) ) {
self::$content .= $out;
} else {
// new CustomException( 'view', $viewName );
new CustomException( 'view', $viewName );
}
}
@ -96,7 +97,7 @@ class Views extends Template {
}
}
// @todo - this would be awesome, if i actually caught the exception anywhere :/
// throw new CustomException('simpleView', $path);
new CustomException('simpleView', $path);
return false;
}

View File

@ -24,8 +24,8 @@
"require":
{
"php": ">=8.1.0",
"thetempusproject/canary": ">=1.0",
"thetempusproject/hermes": ">=1.0"
"thetempusproject/canary": "1.0.6",
"thetempusproject/hermes": "1.0.3"
},
"autoload":
{

16
composer.lock generated
View File

@ -4,20 +4,19 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a5f3c6aec6567c9b48b59eecc9588183",
"content-hash": "22473999e0fcf4a9a664d37a38a750e7",
"packages": [
{
"name": "thetempusproject/canary",
"version": "dev-main",
"version": "1.0.6",
"source": {
"type": "git",
"url": "https://git.thetempusproject.com/the-tempus-project/canary",
"reference": "8dff31b4eefa3efeb9f81d2e6b6ef3e9f8c9f27b"
"reference": "44b2ad688cff933964ec2ff50b408d94c7f51e40"
},
"require": {
"php": ">=8.1.0"
},
"default-branch": true,
"type": "library",
"autoload": {
"files": [
@ -48,20 +47,19 @@
"thetempusproject",
"tools"
],
"time": "2024-08-09T06:18:45+00:00"
"time": "2025-01-22T01:39:34+00:00"
},
{
"name": "thetempusproject/hermes",
"version": "dev-main",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://git.thetempusproject.com/the-tempus-project/hermes",
"reference": "9d6a79d80be98d0e598ce08c47a98d37814d1105"
"reference": "4b4e06a98f0f01695bda18de240bb3294d096ef4"
},
"require": {
"php": ">=8.1.0"
},
"default-branch": true,
"type": "library",
"autoload": {
"files": [
@ -92,7 +90,7 @@
"thetempusproject",
"tools"
],
"time": "2024-08-08T05:24:32+00:00"
"time": "2025-01-22T01:43:15+00:00"
}
],
"packages-dev": [],

View File

@ -6,6 +6,9 @@ if ( ! defined( 'HOUDINI_ROOT_DIRECTORY' ) ) {
if ( ! defined( 'HOUDINI_CONFIG_DIRECTORY' ) ) {
define( 'HOUDINI_CONFIG_DIRECTORY', HOUDINI_ROOT_DIRECTORY . 'config' . DIRECTORY_SEPARATOR );
}
if (!defined('APP_NAME')) {
define('APP_NAME', 'Houdini Site');
}
// # Tell the app all constants have been loaded.
if ( ! defined('HOUDINI_CONSTANTS_LOADED' ) ) {
define( 'HOUDINI_CONSTANTS_LOADED', true );