commit 64f6de30fb546c63b758a2a3cc834c2e12155683 Author: gustavo Date: Mon Jul 14 07:33:32 2025 +0000 first commit diff --git a/404.php b/404.php new file mode 100644 index 0000000..a51168d --- /dev/null +++ b/404.php @@ -0,0 +1,11 @@ + $title, + ] +); + +Timber::render( $templates, $context ); diff --git a/assets/fonts/.gitkeep b/assets/fonts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/images/.gitkeep b/assets/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/scripts/site.js b/assets/scripts/site.js new file mode 100644 index 0000000..6655bd0 --- /dev/null +++ b/assets/scripts/site.js @@ -0,0 +1,5 @@ +jQuery( document ).ready( function( $ ) { + + // Your JavaScript goes here + +}); \ No newline at end of file diff --git a/assets/styles/.gitkeep b/assets/styles/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/author.php b/author.php new file mode 100644 index 0000000..dae48bc --- /dev/null +++ b/author.php @@ -0,0 +1,18 @@ +name() ); +} + +Timber::render( [ 'templates/author.twig', 'templates/archive.twig' ], $context ); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..1e1dcd5 --- /dev/null +++ b/composer.json @@ -0,0 +1,75 @@ +{ + "name": "timber/starter-theme", + "description": "Starter theme to build a Timber theme", + "type":"wordpress-theme", + "license": "MIT", + "authors": [ + { + "name": "Erik van der Bas", + "email": "erik@basedonline.nl", + "homepage": "https://basedonline.nl" + }, + { + "name": "Lukas Gächter", + "email": "lukas.gaechter@mind.ch", + "homepage": "https://www.mind.ch" + }, + { + "name": "Nicolas Lemoine", + "email": "nico@n5s.dev", + "homepage": "https://n5s.dev" + }, + { + "name": "Jared Novack", + "email": "jared@upstatement.com", + "homepage": "https://upstatement.com" + }, + { + "name": "Timber Community", + "homepage": "https://github.com/timber/timber" + } + ], + "repositories": [ + { + "type": "composer", + "url": "https://wpackagist.org" + } + ], + "require": { + "timber/timber": "^2.1" + }, + "require-dev": { + "automattic/wordbless": "^0.4.2", + "yoast/wp-test-utils": "^1.0", + "wp-coding-standards/wpcs": "^3.1", + "phpcompatibility/php-compatibility": "^9", + "szepeviktor/phpstan-wordpress": "^1.3", + "10up/phpcs-composer": "^3.0" + }, + "extra": { + "installer-paths": { + "vendor/automattic/wordbless/": [ + "automattic/wordbless" + ] + }, + "wordpress-install-dir": "wordpress" + }, + "config": { + "allow-plugins": { + "roots/wordpress-core-installer": true, + "composer/installers": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "autoload": { + "psr-4": { + "App\\": "src/" + } + }, + "scripts": { + "test": "phpunit", + "cs": "@php ./vendor/bin/phpcs --colors -s -p -v ./", + "cs:fix": "@php ./vendor/bin/phpcbf --colors -s -p -v ./", + "phpstan": "@php ./vendor/bin/phpstan analyse" + } +} diff --git a/functions.php b/functions.php new file mode 100644 index 0000000..76890b8 --- /dev/null +++ b/functions.php @@ -0,0 +1,19 @@ + 'bar', + ] +); + +Timber::render( $templates, $context ); diff --git a/page.php b/page.php new file mode 100644 index 0000000..34c142d --- /dev/null +++ b/page.php @@ -0,0 +1,17 @@ + + + + . + + */node_modules/* + */wordpress/* + */vendor/* + */resources/* + */dist/* + */tests/* + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..5ce8f6d --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,15 @@ +parameters: + editorUrl: 'vscode://file/%%file%%:%%line%%' + level: 5 # Increase until "max" + paths: + - src/ + - %currentWorkingDirectory%/ + excludePaths: + - tests/* + - docs/* + - vendor/* + - wordpress/* + ignoreErrors: + +includes: + - vendor/szepeviktor/phpstan-wordpress/extension.neon \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..c0f40d4 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,14 @@ + + + + ./tests/ + + + diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..f37bb02 Binary files /dev/null and b/screenshot.png differ diff --git a/search.php b/search.php new file mode 100644 index 0000000..9e1ac5f --- /dev/null +++ b/search.php @@ -0,0 +1,18 @@ + 'Search results for ' . get_search_query(), + ] +); + +Timber::render( $templates, $context ); diff --git a/single.php b/single.php new file mode 100644 index 0000000..df070cf --- /dev/null +++ b/single.php @@ -0,0 +1,20 @@ +post_type . '.twig', 'templates/single.twig' ]; + +if ( post_password_required( $post->ID ) ) { + $templates = 'templates/single-password.twig'; +} + +Timber::render( $templates, $context ); diff --git a/src/StarterSite.php b/src/StarterSite.php new file mode 100644 index 0000000..834eaad --- /dev/null +++ b/src/StarterSite.php @@ -0,0 +1,186 @@ + _x( 'Main menu', 'Backend - menu name', 'timber-starter' ), + ] + ); + + // Add default posts and comments RSS feed links to head. + add_theme_support( 'automatic-feed-links' ); + + /* + * Let WordPress manage the document title. + * By adding theme support, we declare that this theme does not use a + * hard-coded tag in the document head, and expect WordPress to + * provide it for us. + */ + add_theme_support( 'title-tag' ); + + /* + * Enable support for Post Thumbnails on posts and pages. + * + * @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/ + */ + add_theme_support( 'post-thumbnails' ); + + /* + * Switch default core markup for search form, comment form, and comments + * to output valid HTML5. + */ + add_theme_support( + 'html5', + [ + 'comment-form', + 'comment-list', + 'gallery', + 'caption', + ] + ); + + /* + * Enable support for Post Formats. + * + * See: https://codex.wordpress.org/Post_Formats + */ + add_theme_support( + 'post-formats', + [ + 'aside', + 'image', + 'video', + 'quote', + 'link', + 'gallery', + 'audio', + ] + ); + + add_theme_support( 'menus' ); + } + + /** + * This would return 'foo bar!'. + * + * @param string $text being 'foo', then returned 'foo bar!' + */ + public function myfoo( $text ) { + $text .= ' bar!'; + + return $text; + } + + /** + * This is where you can add your own functions to twig. + * + * @link https://timber.github.io/docs/v2/hooks/filters/#timber/twig/filters + * @param array $filters an array of Twig filters. + */ + public function add_filters_to_twig( $filters ) { + + $additional_filters = [ + 'myfoo' => [ + 'callable' => [ $this, 'myfoo' ], + ], + ]; + + return array_merge( $filters, $additional_filters ); + } + + + /** + * This is where you can add your own functions to twig. + * + * @link https://timber.github.io/docs/v2/hooks/filters/#timber/twig/functions + * @param array $functions an array of existing Twig functions. + */ + public function add_functions_to_twig( $functions ) { + $additional_functions = [ + 'get_theme_mod' => [ + 'callable' => 'get_theme_mod', + ], + ]; + + return array_merge( $functions, $additional_functions ); + } + + /** + * Updates Twig environment options. + * + * @see https://twig.symfony.com/doc/2.x/api.html#environment-options + * + * @param array $options an array of environment options + * + * @return array + */ + public function update_twig_environment_options( $options ) { + // $options['autoescape'] = true; + + return $options; + } +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..90afb39 --- /dev/null +++ b/style.css @@ -0,0 +1,5 @@ +/* + * Theme Name: My Timber 2.x Starter Theme + * Description: Starter Theme to use with Timber + * Author: Timber Team and You! +*/ diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..fb3f982 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,28 @@ +<?php + +use WorDBless\Load; + +if (! file_exists(dirname(__DIR__) . '/wordpress/wp-content')) { + mkdir(dirname(__DIR__) . '/wordpress/wp-content'); +} + +if (! file_exists(dirname(__DIR__) . '/wordpress/wp-content/themes')) { + mkdir(dirname(__DIR__) . '/wordpress/wp-content/themes'); +} + +copy( + dirname(__DIR__) . '/vendor/automattic/wordbless/src/dbless-wpdb.php', + dirname(__DIR__) . '/wordpress/wp-content/db.php' +); + +$theme_base_name = basename(dirname(__DIR__)); +$src = realpath(dirname(dirname(__DIR__)) . '/' . $theme_base_name); +$dest = dirname(__DIR__) . '/wordpress/wp-content/themes/' . $theme_base_name; + +if ( is_dir($src) && ! file_exists($dest) ) { + symlink($src, $dest); +} + +require_once dirname(__DIR__) . '/vendor/autoload.php'; + +Load::load(); diff --git a/tests/test-timber-starter-theme.php b/tests/test-timber-starter-theme.php new file mode 100644 index 0000000..6f02aa4 --- /dev/null +++ b/tests/test-timber-starter-theme.php @@ -0,0 +1,64 @@ +<?php + +use Timber\Timber; +use WorDBless\BaseTestCase; + +class TestTimberStarterTheme extends BaseTestCase +{ + + public function set_up() + { + switch_theme(basename(dirname(__DIR__)) . '/theme'); + + require dirname(__DIR__) . '/functions.php'; + + Timber::$dirname = array_merge((array) Timber::$dirname, ['../views']); + Timber::$dirname = array_unique(Timber::$dirname); + + // WorDBless includes wp-settings.php + do_action('after_setup_theme'); + + parent::set_up(); + } + + function tear_down() + { + parent::tear_down(); + switch_theme('twentytwenty'); + } + + function testTimberExists() + { + $context = Timber::context(); + $this->assertTrue(is_array($context)); + } + + function testFunctionsPHP() + { + $context = Timber::context(); + $this->assertEquals('App\StarterSite', get_class($context['site'])); + $this->assertTrue(current_theme_supports('post-thumbnails')); + $this->assertEquals('bar', $context['foo']); + } + + function testLoading() + { + $str = Timber::compile('partials/tease.twig'); + $this->assertStringStartsWith('<article class="tease tease-" id="tease-">', $str); + $this->assertStringEndsWith('</article>', $str); + } + + /** + * Helper test to output current twig version + */ + function testTwigVersion() + { + $version = Timber::compile_string("{{ version }}", ['version' => Twig\Environment::VERSION]); + $this->assertEquals(Twig\Environment::VERSION, $version); + } + + // function testTwigFilter() { + // $str = Timber::compile_string('{{ "foo"|myfoo }}'); + // $this->assertEquals('foo bar!', $str); + // } +} diff --git a/views/layouts/base.twig b/views/layouts/base.twig new file mode 100644 index 0000000..537c08d --- /dev/null +++ b/views/layouts/base.twig @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html {{ site.language_attributes }}> + {% block head %} + {% include 'partials/head.twig' %} + {% endblock %} + + <body class="{{ body_class }}"> + {{ function('wp_body_open') }} + <a class="skip-link screen-reader-text" href="#content">{{ _e('Skip to content') }}</a> + <header class="header"> + {% block header %} + <div class="wrapper"> + <h1 class="hdr-logo"> + <a class="hdr-logo-link" href="{{ site.url }}">{{ site.name }}</a> + </h1> + <nav id="nav-main" class="nav-main"> + {% include 'partials/menu.twig' with { + items: menu.get_items + } %} + </nav> + <!-- #nav --> + </div> + {% endblock %} + </header> + + <section id="content" class="content-wrapper"> + {% if title %} + <h1>{{ title }}</h1> + {% endif %} + <div class="wrapper"> + {% block content %} + Sorry, no content + {% endblock %} + </div> + </section> + + {% block footer %} + {% include 'partials/footer.twig' %} + {% endblock %} + {{ function('wp_footer') }} + {% do action('get_footer') %} + </body> +</html> diff --git a/views/partials/comment-form.twig b/views/partials/comment-form.twig new file mode 100644 index 0000000..565f4b6 --- /dev/null +++ b/views/partials/comment-form.twig @@ -0,0 +1,32 @@ +<div class="comment-form"> + <h3>Add comment</h3> + <form class="comment-form" method="post" action="{{ site.link ~ '/wp-comments-post.php' }}"> + {% if user %} + <input type="hidden" name="email" value="{{ user.email }}" /> + <input type="hidden" name="author" value="{{ user.name }}" /> + <input type="hidden" name="url" value="{{ user.link }}" /> + {% else %} + <label> + Email<br /> + <input required name="email" type="email" id="email" /> + </label> + <label> + Name<br /> + <input required name="author" type="text" /> + </label> + <label> + Website<br /> + <input name="url" type="url" /> + </label> + {% endif %} + <label> + Comment<br /> + <textarea placeholder="Leave a comment..." name="comment" cols="60" rows="3"></textarea> + </label> + <input name="comment_post_ID" value="{{ post.id }}" type="hidden" /> + <input name="comment_parent" value="{{ comment.id|default('0') }}" type="hidden" /> + <button type="submit" name="Submit" class="btn">Send</button> + <button type="reset">Cancel</button> + <p>Your comment will be revised by the site if needed.</p> + </form> +</div> diff --git a/views/partials/comment.twig b/views/partials/comment.twig new file mode 100644 index 0000000..710f979 --- /dev/null +++ b/views/partials/comment.twig @@ -0,0 +1,19 @@ +<div class="blog-comment" id="blog-comment-{{ comment.id }}"> + <h5 class="comment-author">{{ comment.author.name }} says</h5> + <div class="comment-content">{{ comment.content|wpautop }}</div> + + <section class="comment-box"> + {% include 'partials/comment-form.twig' %} + + {% if post.comments %} + <h4>replies</h4> + <div class="comments"> + {% for cmt in comment.children %} + {% include 'partials/comment.twig' with { + comment: cmt + } %} + {% endfor %} + </div> + {% endif %} + </section> +</div> diff --git a/views/partials/footer.twig b/views/partials/footer.twig new file mode 100644 index 0000000..606165c --- /dev/null +++ b/views/partials/footer.twig @@ -0,0 +1 @@ +<footer id="footer">Copyright {{ 'now'|date('Y') }}</footer> diff --git a/views/partials/head.twig b/views/partials/head.twig new file mode 100644 index 0000000..ea68c2a --- /dev/null +++ b/views/partials/head.twig @@ -0,0 +1,9 @@ +<head> + <meta charset="{{ site.charset }}" /> + <link rel="stylesheet" href="{{ site.theme.link }}/style.css" type="text/css" media="screen" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="author" href="{{ site.theme.link }}/humans.txt" /> + <link rel="profile" href="http://gmpg.org/xfn/11" /> + {% do action('get_header') %} + {{ function('wp_head') }} +</head> diff --git a/views/partials/menu.twig b/views/partials/menu.twig new file mode 100644 index 0000000..377dc98 --- /dev/null +++ b/views/partials/menu.twig @@ -0,0 +1,12 @@ +{% if menu %} + <ul> + {% for item in items %} + <li class="{{ item.classes|join(' ') }}"> + <a target="{{ item.target }}" href="{{ item.link }}">{{ item.title }}</a> + {% include 'partials/menu.twig' with { + items: item.children + } %} + </li> + {% endfor %} + </ul> +{% endif %} diff --git a/views/partials/pagination.twig b/views/partials/pagination.twig new file mode 100644 index 0000000..e4d737b --- /dev/null +++ b/views/partials/pagination.twig @@ -0,0 +1,44 @@ +{% if posts.pagination.pages is not empty %} + <nav class="pagination-block"> + <ul class="pagination"> + {# First #} + {% if (posts.pagination.pages|first) and posts.pagination.pages|first.current != true %} + <li class="first btn"> + <a href="{{ posts.pagination.pages|first.link }}">First</a> + </li> + {% else %} + <li class="first btn disabled"><button disabled>First</button></li> + {% endif %} + + {# Previous #} + {% if posts.pagination.prev %} + <li class="prev btn"><a href="{{ posts.pagination.prev.link }}">Previous</a></li> + {% else %} + <li class="prev btn disabled"><button disabled>Previous</button></li> + {% endif %} + + {# Pages #} + {% for page in posts.pagination.pages %} + {% if page.link %} + <li><a href="{{ page.link }}" class="{{ page.class }}">{{ page.title }}</a></li> + {% else %} + <li class="current"><span class="{{ page.class }}">{{ page.title }}</span></li> + {% endif %} + {% endfor %} + + {# Next #} + {% if posts.pagination.next %} + <li class="next btn"><a href="{{ posts.pagination.next.link }}">Next</a></li> + {% else %} + <li class="next btn disabled"><button disabled>Next</button></li> + {% endif %} + + {# Last #} + {% if (posts.pagination.pages|last) and posts.pagination.pages|last.current != true %} + <li class="last btn"><a href="{{ posts.pagination.pages|last.link }}">Last</a></li> + {% else %} + <li class="last btn disabled"><button disabled>Last</button></li> + {% endif %} + </ul> + </nav> +{% endif %} diff --git a/views/partials/tease-post.twig b/views/partials/tease-post.twig new file mode 100644 index 0000000..5a08767 --- /dev/null +++ b/views/partials/tease-post.twig @@ -0,0 +1,16 @@ +{% extends 'partials/tease.twig' %} + +{% block content %} + <h2 class="h2"><a href="{{ post.link }}">{{ post.title }}</a></h2> + <p> + {{ + post.excerpt({ + words: 5, + read_more: 'Keep reading' + }) + }} + </p> + {% if post.thumbnail.src %} + <img src="{{ post.thumbnail.src }}" /> + {% endif %} +{% endblock %} diff --git a/views/partials/tease.twig b/views/partials/tease.twig new file mode 100644 index 0000000..1a0f7b0 --- /dev/null +++ b/views/partials/tease.twig @@ -0,0 +1,9 @@ +<article class="tease tease-{{ post.type }}" id="tease-{{ post.id }}"> + {% block content %} + <h2 class="h2"><a href="{{ post.link }}">{{ post.title }}</a></h2> + <p>{{ post.excerpt }}</p> + {% if post.thumbnail %} + <img src="{{ post.thumbnail.src }}" /> + {% endif %} + {% endblock %} +</article> \ No newline at end of file diff --git a/views/templates/404.twig b/views/templates/404.twig new file mode 100644 index 0000000..1b2073c --- /dev/null +++ b/views/templates/404.twig @@ -0,0 +1,5 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + Sorry, we couldn't find what you're looking for. +{% endblock %} diff --git a/views/templates/archive.twig b/views/templates/archive.twig new file mode 100644 index 0000000..ae021ea --- /dev/null +++ b/views/templates/archive.twig @@ -0,0 +1,15 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + {% for post in posts %} + {% include ['partials/tease-' ~ post.type ~ '.twig', 'partials/tease.twig'] %} + {% endfor %} + + {% include 'partials/pagination.twig' with { + pagination: posts.pagination({ + show_all: false, + mid_size: 3, + end_size: 2 + }) + } %} +{% endblock %} diff --git a/views/templates/author.twig b/views/templates/author.twig new file mode 100644 index 0000000..6d19761 --- /dev/null +++ b/views/templates/author.twig @@ -0,0 +1,7 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + {% for post in posts %} + {% include ['partials/tease-' ~ post.type ~ '.twig', 'partials/tease.twig'] %} + {% endfor %} +{% endblock %} diff --git a/views/templates/index.twig b/views/templates/index.twig new file mode 100644 index 0000000..b68fbd7 --- /dev/null +++ b/views/templates/index.twig @@ -0,0 +1,16 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + <h2>{{ foo }}</h2> + {% for post in posts %} + {% include ['partials/tease-' ~ post.type ~ '.twig', 'partials/tease.twig'] %} + {% endfor %} + + {% include 'partials/pagination.twig' with { + pagination: posts.pagination({ + show_all: false, + mid_size: 3, + end_size: 2 + }) + } %} +{% endblock %} diff --git a/views/templates/page.twig b/views/templates/page.twig new file mode 100644 index 0000000..7709c52 --- /dev/null +++ b/views/templates/page.twig @@ -0,0 +1,12 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + <div class="content-wrapper"> + <article class="post-type-{{ post.type }}" id="post-{{ post.id }}"> + <section class="article-content"> + <h1 class="article-h1">{{ post.title }}</h1> + <div class="article-body">{{ post.content }}</div> + </section> + </article> + </div> +{% endblock %} diff --git a/views/templates/search.twig b/views/templates/search.twig new file mode 100644 index 0000000..f8853a6 --- /dev/null +++ b/views/templates/search.twig @@ -0,0 +1,18 @@ +{# see `templates/archive.twig` for an alternative strategy of extending templates #} +{% extends 'layouts/base.twig' %} + +{% block content %} + <div class="content-wrapper"> + {% for post in posts %} + {% include ['partials/tease-' ~ post.type ~ '.twig', 'partials/tease.twig'] %} + {% endfor %} + + {% include 'partials/pagination.twig' with { + pagination: posts.pagination({ + show_all: false, + mid_size: 3, + end_size: 2 + }) + } %} + </div> +{% endblock %} diff --git a/views/templates/single-password.twig b/views/templates/single-password.twig new file mode 100644 index 0000000..fc62761 --- /dev/null +++ b/views/templates/single-password.twig @@ -0,0 +1,17 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + <form class="password-form" + action="{{ site.link }}/wp-login.php?action=postpass" + method="post"> + <label for="pwbox-{{ post.id }}">Password:</label> + <input class="password-box" + name="post_password" + id="pwbox-{{ post.id }}" + type="password" + placeholder="Password" + size="20" + maxlength="20" /> + <input class="password-btn" type="submit" name="Submit" value="Submit" /> + </form> +{% endblock %} diff --git a/views/templates/single.twig b/views/templates/single.twig new file mode 100644 index 0000000..584450d --- /dev/null +++ b/views/templates/single.twig @@ -0,0 +1,37 @@ +{% extends 'layouts/base.twig' %} + +{% block content %} + <div class="content-wrapper"> + <article class="post-type-{{ post.type }}" id="post-{{ post.id }}"> + <img src="{{ post.thumbnail.src|resize(1200, 300) }}" /> + <section class="article-content"> + <h1 class="article-h1">{{ post.title }}</h1> + <p class="blog-author"> + <span>By</span> + <a href="{{ post.author.path }}">{{ post.author.name }}</a> + <span>•</span> + <time datetime="{{ post.date|date('Y-m-d H:i:s') }}">{{ post.date }}</time> + </p> + <div class="article-body">{{ post.content }}</div> + </section> + <section class="comment-box"> + <div class="comments"> + {% if post.comments %} + <h3>comments</h3> + {% for cmt in post.comments %} + {% include 'partials/comment.twig' with { + comment: cmt + } %} + {% endfor %} + {% endif %} + </div> + + {% if post.comment_status == 'closed' %} + <p>comments for this post are closed</p> + {% else %} + {% include 'partials/comment-form.twig' %} + {% endif %} + </section> + </article> + </div> +{% endblock %}