upskill-event-manager/wordpress-dev/wordpress/wp-content/plugins/code-snippets/php/class-snippet.php
bengizmo d1509b3d60 feat(dev-env): implement backup-based development workflow
This commit introduces a more reliable and consistent approach to setting up
the development environment using backups:

- Add setup-from-backup.sh script for environment setup from existing backups
- Standardize script naming and organization
- Move obsolete scripts to bin/obsolete directory
- Update documentation with new workflow instructions
- Create migration guide for transitioning to new workflow
- Update Memory Bank with workflow improvements

The new workflow provides:
- More reliable environment setup
- Faster setup process
- Offline development capability
- Consistent development environments across team members

Breaking changes:
- setup-dev.sh is replaced by setup-from-backup.sh
- sync-and-setup.sh is replaced by separate scripts
- verify-with-wpcli.sh is no longer used

Migration path is documented in MIGRATION_GUIDE.md
2025-03-26 11:26:18 -03:00

456 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Code_Snippets;
use DateTime;
use DateTimeZone;
use Exception;
/**
* A snippet object.
*
* @since 2.4.0
* @package Code_Snippets
*
* @property int $id The database ID.
* @property string $name The snippet title.
* @property string $desc The formatted description.
* @property string $code The executable code.
* @property array<string> $tags An array of the tags.
* @property string $scope The scope name.
* @property int $priority Execution priority.
* @property bool $active The active status.
* @property bool $network true if is multisite-wide snippet, false if site-wide.
* @property bool $shared_network Whether the snippet is a shared network snippet.
* @property string $modified The date and time when the snippet data was most recently saved to the database.
* @property array{string,int}|null $code_error Code error encountered when last testing snippet code.
* @property object|null $conditions Snippet conditionals
* @property int $revision Revision or version number of snippet.
* @property string $cloud_id Cloud ID and ownership status of snippet.
*
* @property-read string $display_name The snippet name if it exists or a placeholder if it does not.
* @property-read string $tags_list The tags in string list format.
* @property-read string $scope_icon The dashicon used to represent the current scope.
* @property-read string $scope_name Human-readable description of the snippet type.
* @property-read string $type The type of snippet.
* @property-read string $lang The language that the snippet code is written in.
* @property-read int $modified_timestamp The last modification date in Unix timestamp format.
* @property-read DateTime $modified_local The last modification date in the local timezone.
* @property-read string $type_desc Human-readable description of the snippet type.
* @property-read boolean $is_pro Whether the snippet type is pro-only.
*/
class Snippet extends Data_Item {
/**
* MySQL datetime format (YYYY-MM-DD hh:mm:ss).
*/
const DATE_FORMAT = 'Y-m-d H:i:s';
/**
* Default value used for a datetime variable.
*/
const DEFAULT_DATE = '0000-00-00 00:00:00';
/**
* Constructor function.
*
* @param array<string, mixed>|object $initial_data Initial snippet data.
*/
public function __construct( $initial_data = null ) {
$default_values = array(
'id' => 0,
'name' => '',
'desc' => '',
'code' => '',
'tags' => array(),
'scope' => 'global',
'active' => false,
'priority' => 10,
'network' => null,
'shared_network' => null,
'modified' => null,
'code_error' => null,
'revision' => 1,
'cloud_id' => '',
);
$field_aliases = array(
'description' => 'desc',
'language' => 'lang',
);
parent::__construct( $default_values, $initial_data, $field_aliases );
}
/**
* Add a new tag
*
* @param string $tag Tag content to add to list.
*/
public function add_tag( string $tag ) {
$this->fields['tags'][] = $tag;
}
/**
* Prepare a value before it is stored.
*
* @param mixed $value Value to prepare.
* @param string $field Field name.
*
* @return mixed Value in the correct format.
*/
protected function prepare_field( $value, string $field ) {
switch ( $field ) {
case 'id':
case 'priority':
return absint( $value );
case 'tags':
return code_snippets_build_tags_array( $value );
case 'active':
return is_bool( $value ) ? $value : (bool) $value;
default:
return $value;
}
}
/**
* Prepare the scope by ensuring that it is a valid choice
*
* @param int|string $scope The field as provided.
*
* @return string The field in the correct format.
*/
protected function prepare_scope( $scope ) {
$scopes = self::get_all_scopes();
if ( in_array( $scope, $scopes, true ) ) {
return $scope;
}
if ( is_numeric( $scope ) && isset( $scopes[ $scope ] ) ) {
return $scopes[ $scope ];
}
return $this->fields['scope'];
}
/**
* If $network is anything other than true, set it to false
*
* @param bool $network The field as provided.
*
* @return bool The field in the correct format.
*/
protected function prepare_network( bool $network ): bool {
if ( null === $network && function_exists( 'is_network_admin' ) ) {
return is_network_admin();
}
return true === $network;
}
/**
* Determine the type of code this snippet is, based on its scope
*
* @return string The snippet type will be a filename extension.
*/
protected function get_type(): string {
if ( '-css' === substr( $this->scope, -4 ) ) {
return 'css';
} elseif ( '-js' === substr( $this->scope, -3 ) ) {
return 'js';
} elseif ( 'content' === substr( $this->scope, -7 ) ) {
return 'html';
} else {
return 'php';
}
}
/**
* Retrieve a list of all valid types.
*
* @return string[]
*/
public static function get_types(): array {
return [ 'php', 'html', 'css', 'js' ];
}
/**
* Retrieve description of snippet type.
*
* @return string
*/
protected function get_type_desc(): string {
$labels = [
'php' => __( 'Functions', 'code-snippets' ),
'html' => __( 'Content', 'code-snippets' ),
'css' => __( 'Styles', 'code-snippets' ),
'js' => __( 'Scripts', 'code-snippets' ),
];
return isset( $labels[ $this->type ] ) ? $labels[ $this->type ] : strtoupper( $this->type );
}
/**
* Determine the language that the snippet code is written in, based on the scope
*
* @return string The name of a language filename extension.
*/
protected function get_lang(): string {
return $this->type;
}
/**
* Prepare the modification field by ensuring it is in the correct format.
*
* @param DateTime|string $modified Snippet modification date.
*
* @return string
*/
protected function prepare_modified( $modified ): ?string {
// If the supplied value is a DateTime object, convert it to string representation.
if ( $modified instanceof DateTime ) {
return $modified->format( self::DATE_FORMAT );
}
// If the supplied value is probably a timestamp, attempt to convert it to a string.
if ( is_numeric( $modified ) ) {
return gmdate( self::DATE_FORMAT, $modified );
}
// If the supplied value is a string, check it is not just the default value.
if ( is_string( $modified ) && self::DEFAULT_DATE !== $modified ) {
return $modified;
}
// Otherwise, discard the supplied value.
return null;
}
/**
* Update the last modification date to the current date and time.
*
* @return void
*/
public function update_modified() {
$this->modified = gmdate( self::DATE_FORMAT );
}
/**
* Retrieve the snippet title if set or a placeholder title if not.
*
* @return string
*/
protected function get_display_name(): string {
// translators: %d: snippet ID.
return empty( $this->name ) ? sprintf( esc_html__( 'Untitled #%d', 'code-snippets' ), $this->id ) : $this->name;
}
/**
* Retrieve the tags in list format
*
* @return string The tags separated by a comma and a space.
*/
protected function get_tags_list(): string {
return implode( ', ', $this->tags );
}
/**
* Retrieve a list of all available scopes
*
* @return array<string> List of scope names.
*
* @phpcs:disable WordPress.Arrays.ArrayDeclarationSpacing.ArrayItemNoNewLine
*/
public static function get_all_scopes(): array {
return array(
'global', 'admin', 'front-end', 'single-use',
'content', 'head-content', 'footer-content',
'admin-css', 'site-css',
'site-head-js', 'site-footer-js',
);
}
/**
* Retrieve a list of all scope icons
*
* @return array<string, string> Scope name keyed to the class name of a dashicon.
*/
public static function get_scope_icons(): array {
return array(
'global' => 'admin-site',
'admin' => 'admin-tools',
'front-end' => 'admin-appearance',
'single-use' => 'clock',
'content' => 'shortcode',
'head-content' => 'editor-code',
'footer-content' => 'editor-code',
'admin-css' => 'dashboard',
'site-css' => 'admin-customizer',
'site-head-js' => 'media-code',
'site-footer-js' => 'media-code',
);
}
/**
* Retrieve the string representation of the scope
*
* @return string The name of the scope.
*/
protected function get_scope_name(): string {
switch ( $this->scope ) {
case 'global':
return __( 'Global function', 'code-snippets' );
case 'admin':
return __( 'Admin function', 'code-snippets' );
case 'front-end':
return __( 'Front-end function', 'code-snippets' );
case 'single-use':
return __( 'Single-use function', 'code-snippets' );
case 'content':
return __( 'Content', 'code-snippets' );
case 'head-content':
return __( 'Head content', 'code-snippets' );
case 'footer-content':
return __( 'Footer content', 'code-snippets' );
case 'admin-css':
return __( 'Admin styles', 'code-snippets' );
case 'site-css':
return __( 'Front-end styles', 'code-snippets' );
case 'site-head-js':
return __( 'Head styles', 'code-snippets' );
case 'site-footer-js':
return __( 'Footer styles', 'code-snippets' );
}
return '';
}
/**
* Retrieve the icon used for the current scope
*
* @return string A dashicon name.
*/
protected function get_scope_icon(): string {
$icons = self::get_scope_icons();
return $icons[ $this->scope ];
}
/**
* Determine if the snippet is a shared network snippet
*
* @return bool Whether the snippet is a shared network snippet.
*/
protected function get_shared_network(): bool {
if ( isset( $this->fields['shared_network'] ) ) {
return $this->fields['shared_network'];
}
if ( ! is_multisite() || ! $this->fields['network'] ) {
$this->fields['shared_network'] = false;
} else {
$shared_network_snippets = get_site_option( 'shared_network_snippets', array() );
$this->fields['shared_network'] = in_array( $this->fields['id'], $shared_network_snippets, true );
}
return $this->fields['shared_network'];
}
/**
* Retrieve the snippet modification date as a timestamp.
*
* @return integer Timestamp value.
*/
protected function get_modified_timestamp(): int {
$datetime = DateTime::createFromFormat( self::DATE_FORMAT, $this->modified, new DateTimeZone( 'UTC' ) );
return $datetime ? $datetime->getTimestamp() : 0;
}
/**
* Retrieve the modification time in the local timezone.
*
* @return DateTime
*/
protected function get_modified_local(): DateTime {
$datetime = DateTime::createFromFormat( self::DATE_FORMAT, $this->modified, new DateTimeZone( 'UTC' ) );
if ( function_exists( 'wp_timezone' ) ) {
$timezone = wp_timezone();
} else {
$timezone = get_option( 'timezone_string' );
// Calculate the timezone manually if it is not available.
if ( ! $timezone ) {
$offset = (float) get_option( 'gmt_offset' );
$hours = (int) $offset;
$minutes = ( $offset - $hours ) * 60;
$sign = ( $offset < 0 ) ? '-' : '+';
$timezone = sprintf( '%s%02d:%02d', $sign, abs( $hours ), abs( $minutes ) );
}
try {
$timezone = new DateTimeZone( $timezone );
} catch ( Exception $exception ) {
return $datetime;
}
}
$datetime->setTimezone( $timezone );
return $datetime;
}
/**
* Retrieve the last modified time, nicely formatted for readability.
*
* @param boolean $include_html Whether to include HTML in the output.
*
* @return string
*/
public function format_modified( bool $include_html = true ): string {
if ( ! $this->modified ) {
return '';
}
$timestamp = $this->modified_timestamp;
$time_diff = time() - $timestamp;
$local_time = $this->modified_local;
if ( $time_diff >= 0 && $time_diff < YEAR_IN_SECONDS ) {
// translators: %s: Human-readable time difference.
$human_time = sprintf( __( '%s ago', 'code-snippets' ), human_time_diff( $timestamp ) );
} else {
$human_time = $local_time->format( __( 'Y/m/d', 'code-snippets' ) );
}
if ( ! $include_html ) {
return $human_time;
}
// translators: 1: date format, 2: time format.
$date_format = _x( '%1$s at %2$s', 'date and time format', 'code-snippets' );
$date_format = sprintf( $date_format, get_option( 'date_format' ), get_option( 'time_format' ) );
return sprintf( '<span title="%s">%s</span>', $local_time->format( $date_format ), $human_time );
}
/**
* Determine whether the current snippet type is pro-only.
*/
private function get_is_pro(): bool {
return 'css' === $this->type || 'js' === $this->type;
}
/**
* Increment the revision number by one.
*/
public function increment_revision() {
++$this->revision;
}
}