BAsic functionality
This commit is contained in:
parent
b772c26822
commit
fd9a0311a6
6 changed files with 3304 additions and 0 deletions
35
docker-compose.yaml
Normal file
35
docker-compose.yaml
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: wp_test_db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: rootpassword
|
||||||
|
MYSQL_DATABASE: wordpress
|
||||||
|
MYSQL_USER: wordpress
|
||||||
|
MYSQL_PASSWORD: wordpress
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/mysql
|
||||||
|
|
||||||
|
wordpress:
|
||||||
|
image: wordpress:latest
|
||||||
|
container_name: wp_test_app
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
ports:
|
||||||
|
- "8080:80"
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
WORDPRESS_DB_HOST: db:3306
|
||||||
|
WORDPRESS_DB_USER: wordpress
|
||||||
|
WORDPRESS_DB_PASSWORD: wordpress
|
||||||
|
WORDPRESS_DB_NAME: wordpress
|
||||||
|
WORDPRESS_DEBUG: 'true'
|
||||||
|
WORDPRESS_DEBUG_LOG: 'true'
|
||||||
|
WORDPRESS_DEBUG_DISPLAY: 'false'
|
||||||
|
volumes:
|
||||||
|
- ./src:/var/www/html/wp-content/plugins/fwp-calendar
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data:
|
||||||
|
|
56
src/calendar.js
Normal file
56
src/calendar.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const el = document.getElementById('event-calendar');
|
||||||
|
if (!el || typeof FullCalendar === 'undefined' || typeof EventCalendar === 'undefined') return;
|
||||||
|
|
||||||
|
const calendar = new FullCalendar.Calendar(el, {
|
||||||
|
initialView: 'dayGridMonth',
|
||||||
|
events: EventCalendar.events
|
||||||
|
});
|
||||||
|
|
||||||
|
calendar.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.event-item').forEach(el => {
|
||||||
|
const startStr = el.getAttribute('data-start'); // e.g. "2025-06-17T00:00:00+00:00"
|
||||||
|
const endStr = el.getAttribute('data-end'); // e.g. "2025-06-18T00:00:00+00:00"
|
||||||
|
const eventTitle = el.getAttribute('data-title');
|
||||||
|
|
||||||
|
if (!startStr) return; // skip if no start time
|
||||||
|
|
||||||
|
const startDate = new Date(startStr);
|
||||||
|
const endDate = endStr ? new Date(endStr) : null;
|
||||||
|
|
||||||
|
// Helper function to format date/time nicely
|
||||||
|
function formatTimeRange(start, end) {
|
||||||
|
const options = {
|
||||||
|
hour: 'numeric', minute: 'numeric', hour12: true,
|
||||||
|
month: 'short', day: 'numeric',
|
||||||
|
};
|
||||||
|
if (!end) return start.toLocaleString(undefined, options);
|
||||||
|
|
||||||
|
return `${start.toLocaleString(undefined, options)} - ${end.toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric', hour12: true })}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect all-day event:
|
||||||
|
// Start and end times are both at midnight,
|
||||||
|
// and end is exactly one day after start.
|
||||||
|
const isAllDay = () => {
|
||||||
|
if (!endDate) return false;
|
||||||
|
const startMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0 && startDate.getSeconds() === 0;
|
||||||
|
const endMidnight = endDate.getHours() === 0 && endDate.getMinutes() === 0 && endDate.getSeconds() === 0;
|
||||||
|
const oneDayMs = 24 * 60 * 60 * 1000;
|
||||||
|
const diffDays = (endDate - startDate) / oneDayMs;
|
||||||
|
console.log(diffDays);
|
||||||
|
return startMidnight && endMidnight && diffDays === 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isAllDay()) {
|
||||||
|
// Format as e.g. "June 17, 2025 - All Day"
|
||||||
|
const options = { year: 'numeric', month: 'long', day: 'numeric' };
|
||||||
|
el.textContent = `${eventTitle} — ${startDate.toLocaleDateString(undefined, options)} - All Day`;
|
||||||
|
} else {
|
||||||
|
el.textContent = `${eventTitle} — ${formatTimeRange(startDate, endDate)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
6
src/fullcalendar.min.js
vendored
Normal file
6
src/fullcalendar.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
213
src/fwp-calendar.php
Normal file
213
src/fwp-calendar.php
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Plugin Name: Free WordPress Calendar
|
||||||
|
* Plugin URI: https://example.com/
|
||||||
|
* Description: Display multiple iCal feeds in a calendar and show upcoming events with a shortcode.
|
||||||
|
* Version: 1.0
|
||||||
|
* Author: Your Name
|
||||||
|
* License: GPL2
|
||||||
|
* Text Domain: fwp-calendar
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once plugin_dir_path(__FILE__) . 'ical/ical.php';
|
||||||
|
require_once plugin_dir_path(__FILE__) . 'ical/event.php';
|
||||||
|
|
||||||
|
use ICal\ICal;
|
||||||
|
|
||||||
|
class FWP_Calendar {
|
||||||
|
|
||||||
|
private $option_name = 'fwp_calendar_urls';
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
add_action('admin_menu', [$this, 'admin_menu']);
|
||||||
|
add_action('admin_init', [$this, 'register_settings']);
|
||||||
|
add_action('wp_enqueue_scripts', [$this, 'enqueue_assets']);
|
||||||
|
add_shortcode('event-calendar', [$this, 'shortcode_handler']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function admin_menu() {
|
||||||
|
add_options_page('FWP Calendar', 'FWP Calendar', 'manage_options', 'fwp-calendar', [$this, 'settings_page']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register_settings() {
|
||||||
|
register_setting('fwp-calendar-settings-group', $this->option_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function enqueue_assets() {
|
||||||
|
// Enqueue FullCalendar JS and CSS from plugin folder (ensure these files exist)
|
||||||
|
wp_enqueue_script(
|
||||||
|
'fwp-calendar-fullcalendar-js',
|
||||||
|
plugins_url('fullcalendar.min.js', __FILE__),
|
||||||
|
[],
|
||||||
|
'6.1.8',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_enqueue_script(
|
||||||
|
'fwp-calendar-js',
|
||||||
|
plugins_url('calendar.js', __FILE__),
|
||||||
|
['fwp-calendar-fullcalendar-js'],
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function settings_page() {
|
||||||
|
?>
|
||||||
|
<div class="wrap">
|
||||||
|
<h1>Free WordPress Calendar Settings</h1>
|
||||||
|
<form method="post" action="options.php">
|
||||||
|
<?php
|
||||||
|
settings_fields('fwp-calendar-settings-group');
|
||||||
|
$urls = get_option($this->option_name, []);
|
||||||
|
if (!is_array($urls)) {
|
||||||
|
$urls = array_filter(array_map('trim', explode("\n", $urls)));
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<table class="form-table">
|
||||||
|
<tr valign="top">
|
||||||
|
<th scope="row">iCal URLs</th>
|
||||||
|
<td>
|
||||||
|
<div id="fwp-calendar-url-list">
|
||||||
|
<?php foreach ($urls as $i => $url): ?>
|
||||||
|
<div class="fwp-url-entry" style="margin-bottom: 5px;">
|
||||||
|
<input type="text" name="<?php echo esc_attr($this->option_name); ?>[]" value="<?php echo esc_attr($url); ?>" style="width: 90%;" />
|
||||||
|
<button type="button" class="button button-small" onclick="this.parentNode.remove()">Remove</button>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="button" onclick="fwpAddUrlField()">Add URL</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<?php submit_button(); ?>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function fwpAddUrlField() {
|
||||||
|
const container = document.getElementById('fwp-calendar-url-list');
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'fwp-url-entry';
|
||||||
|
wrapper.style = 'margin-bottom: 5px;';
|
||||||
|
wrapper.innerHTML = `
|
||||||
|
<input type="text" name="<?php echo esc_attr($this->option_name); ?>[]" style="width: 90%;" />
|
||||||
|
<button type="button" class="button button-small" onclick="this.parentNode.remove()">Remove</button>
|
||||||
|
`;
|
||||||
|
container.appendChild(wrapper);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function shortcode_handler($atts) {
|
||||||
|
$atts = shortcode_atts([
|
||||||
|
'id' => '',
|
||||||
|
'show_upcoming' => 'true',
|
||||||
|
], $atts);
|
||||||
|
|
||||||
|
$show_upcoming = filter_var($atts['show_upcoming'], FILTER_VALIDATE_BOOLEAN);
|
||||||
|
$urls = get_option($this->option_name, []);
|
||||||
|
if (!is_array($urls)) $urls = [];
|
||||||
|
|
||||||
|
if ($atts['id'] !== '') {
|
||||||
|
$key = intval($atts['id']) - 1;
|
||||||
|
if (isset($urls[$key])) {
|
||||||
|
$urls = [$urls[$key]];
|
||||||
|
} else {
|
||||||
|
return '<p>Invalid calendar ID.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$events = [];
|
||||||
|
foreach ($urls as $url) {
|
||||||
|
$url = trim($url);
|
||||||
|
$ics_content = @file_get_contents(trim($url));
|
||||||
|
|
||||||
|
if (!$ics_content) {
|
||||||
|
error_log("FWP Calendar: Failed to fetch ICS from $url");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$parser = new ICal();
|
||||||
|
$parser->initString($ics_content); // <-- This is the fix
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('FWP Calendar: Failed to parse ICS: ' . $e->getMessage());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($parser->events() as $event) {
|
||||||
|
$start_ts = method_exists($parser, 'iCalDateToTimestamp')
|
||||||
|
? $parser->iCalDateToTimestamp($event->dtstart)
|
||||||
|
: strtotime($event->dtstart);
|
||||||
|
$end_ts = isset($event->dtend)
|
||||||
|
? (method_exists($parser, 'iCalDateToTimestamp') ? $parser->iCalDateToTimestamp($event->dtend) : strtotime($event->dtend))
|
||||||
|
: $start_ts;
|
||||||
|
|
||||||
|
$events[] = [
|
||||||
|
'title' => $event->summary ?? 'No Title',
|
||||||
|
'start' => date('c', $start_ts),
|
||||||
|
'end' => date('c', $end_ts),
|
||||||
|
'url' => $event->url ?? '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
?>
|
||||||
|
<div id="event-calendar" style="max-width: 900px; margin: auto; height: 600px;"></div>
|
||||||
|
<script>
|
||||||
|
var EventCalendar = {
|
||||||
|
events: <?php echo json_encode($events); ?>
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if ($show_upcoming && !empty($events)) {
|
||||||
|
usort($events, function ($a, $b) {
|
||||||
|
return strtotime($a['start']) - strtotime($b['start']);
|
||||||
|
});
|
||||||
|
|
||||||
|
$now = time();
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
echo '<div class="upcoming-events" style="max-width: 900px; margin: 2rem auto;">';
|
||||||
|
echo '<h2>Upcoming Events</h2><ul>';
|
||||||
|
|
||||||
|
foreach ($events as $event) {
|
||||||
|
$start_ts = strtotime($event['start']);
|
||||||
|
if ($start_ts >= $now) {
|
||||||
|
$start_attr = esc_attr($event['start']);
|
||||||
|
$end_attr = isset($event['end']) ? esc_attr($event['end']) : '';
|
||||||
|
|
||||||
|
echo '<li class="event-item" data-title="'.$event['title'].'" data-start="' . $start_attr . '"';
|
||||||
|
if ($end_attr !== '') {
|
||||||
|
echo ' data-end="' . $end_attr . '"';
|
||||||
|
}
|
||||||
|
echo '>';
|
||||||
|
|
||||||
|
if (!empty($event['url'])) {
|
||||||
|
echo '<a href="' . esc_url($event['url']) . '" target="_blank" rel="noopener noreferrer">' . esc_html($event['title']) . '</a>';
|
||||||
|
} else {
|
||||||
|
echo esc_html($event['title']);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</li>';
|
||||||
|
|
||||||
|
$count++;
|
||||||
|
if ($count >= 5) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</ul></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new FWP_Calendar();
|
||||||
|
|
264
src/ical/event.php
Normal file
264
src/ical/event.php
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace ICal;
|
||||||
|
|
||||||
|
class Event
|
||||||
|
{
|
||||||
|
// phpcs:disable Generic.Arrays.DisallowLongArraySyntax
|
||||||
|
|
||||||
|
const HTML_TEMPLATE = '<p>%s: %s</p>';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/summary.html
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $summary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/dtstart.html
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $dtstart;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/dtend.html
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $dtend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/duration.html
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
public $duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/dtstamp.html
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $dtstamp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the event starts, represented as a timezone-adjusted string
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $dtstart_tz;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the event ends, represented as a timezone-adjusted string
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $dtend_tz;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/uid.html
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $uid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/created.html
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $created;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/lastModified.html
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $last_modified;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/description.html
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
public $description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/location.html
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
public $location;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/sequence.html
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $sequence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/status.html
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/transp.html
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $transp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/organizer.html
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $organizer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://www.kanzaki.com/docs/ical/attendee.html
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $attendee;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage additional properties
|
||||||
|
*
|
||||||
|
* @var array<string, mixed>
|
||||||
|
*/
|
||||||
|
public $additionalProperties = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the Event object
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct(array $data = array())
|
||||||
|
{
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
$variable = self::snakeCase($key);
|
||||||
|
if (property_exists($this, $variable)) {
|
||||||
|
$this->{$variable} = $this->prepareData($value);
|
||||||
|
} else {
|
||||||
|
$this->additionalProperties[$variable] = $this->prepareData($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic getter method
|
||||||
|
*
|
||||||
|
* @param string $additionalPropertyName
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __get($additionalPropertyName)
|
||||||
|
{
|
||||||
|
if (array_key_exists($additionalPropertyName, $this->additionalProperties)) {
|
||||||
|
return $this->additionalProperties[$additionalPropertyName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic isset method
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function __isset($name)
|
||||||
|
{
|
||||||
|
return is_null($this->$name) === false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares the data for output
|
||||||
|
*
|
||||||
|
* @param mixed $value
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
protected function prepareData($value)
|
||||||
|
{
|
||||||
|
if (is_string($value)) {
|
||||||
|
return stripslashes(trim(str_replace('\n', "\n", $value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($value)) {
|
||||||
|
return array_map(function ($value) {
|
||||||
|
return $this->prepareData($value);
|
||||||
|
}, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Event data excluding anything blank
|
||||||
|
* within an HTML template
|
||||||
|
*
|
||||||
|
* @param string $html HTML template to use
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function printData($html = self::HTML_TEMPLATE)
|
||||||
|
{
|
||||||
|
$data = array(
|
||||||
|
'SUMMARY' => $this->summary,
|
||||||
|
'DTSTART' => $this->dtstart,
|
||||||
|
'DTEND' => $this->dtend,
|
||||||
|
'DTSTART_TZ' => $this->dtstart_tz,
|
||||||
|
'DTEND_TZ' => $this->dtend_tz,
|
||||||
|
'DURATION' => $this->duration,
|
||||||
|
'DTSTAMP' => $this->dtstamp,
|
||||||
|
'UID' => $this->uid,
|
||||||
|
'CREATED' => $this->created,
|
||||||
|
'LAST-MODIFIED' => $this->last_modified,
|
||||||
|
'DESCRIPTION' => $this->description,
|
||||||
|
'LOCATION' => $this->location,
|
||||||
|
'SEQUENCE' => $this->sequence,
|
||||||
|
'STATUS' => $this->status,
|
||||||
|
'TRANSP' => $this->transp,
|
||||||
|
'ORGANISER' => $this->organizer,
|
||||||
|
'ATTENDEE(S)' => $this->attendee,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove any blank values
|
||||||
|
$data = array_filter($data);
|
||||||
|
|
||||||
|
$output = '';
|
||||||
|
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
$output .= sprintf($html, $key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given input to snake_case
|
||||||
|
*
|
||||||
|
* @param string $input
|
||||||
|
* @param string $glue
|
||||||
|
* @param string $separator
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function snakeCase($input, $glue = '_', $separator = '-')
|
||||||
|
{
|
||||||
|
$inputSplit = preg_split('/(?<=[a-z])(?=[A-Z])/x', $input);
|
||||||
|
|
||||||
|
if ($inputSplit === false) {
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
$inputSplit = implode($glue, $inputSplit);
|
||||||
|
$inputSplit = str_replace($separator, $glue, $inputSplit);
|
||||||
|
|
||||||
|
return strtolower($inputSplit);
|
||||||
|
}
|
||||||
|
}
|
2730
src/ical/ical.php
Normal file
2730
src/ical/ical.php
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue