My dream is to include a php file in a theme which checks if a set of plugins are installed, and installs the ones which are not. Kind of like a set of dependencies for the theme, but also just a good way to package theme development to include a set of good plugins.
My questions...
Is there something like this in existence?
Is it possible to achieve from a single php file in a theme folder?
Are there any obvious pit-falls or problems with this approach?
How would I go about achieving this?
is it possible to enumerate installed plugins from within theme folder?
is it possible to download and place plugin files in the plugins folder?
is it possible to activate plugins from within theme directory?
07/06/2018 EDIT: If you are coming across this answer, the code highlighted below is extremely outdated and insecure and should not be used in any capacity outside of experimentation on a local server. If you are looking for a more modern solution for plugin management, consider installing Wordpress via Composer and Bedrock
I would advise NOT programatically checking for the existence of certain plugins, downloading, installing, and activating them from within any theme file. You have to consider that the check will be run every time the given page is loaded, and can result in a lot of superfluous code and unnecessary activity.
Instead, my advice would be to package any plugins on which your theme depends as a part of the theme itself, and NOT as a plugin. Plugins should be installed at the discretion of the user. If a theme depends on a plugin to function properly or efficiently, then it really should be packaged and downloaded with the theme itself.
But to answer your questions directly:
Probably. It is certainly possible to do.
Yes.
See the above. You potentially run into more issues by constantly checking for plugins and running a series of actions based on those conditions rather than just including everything needed.
Plenty of research
Probably
Yes
Yes
I cannot stress enough, however, that the purpose of a PLUGIN is to give the user the option to extend a given theme's capabilities. If your theme's capabilities DEPEND on existing plugins, then you really REALLY should include all the files when somebody downloads your theme.
Though if you feel that your approach benefits your theme in ways that I might be missing, feel free to write it however you like.
COMPLETE ANSWER: I decided to help create a proof of concept for you, because I got bored and curious. Much of this should be self explanatory. Add these functions:
function mm_get_plugins($plugins)
{
$args = array(
'path' => ABSPATH.'wp-content/plugins/',
'preserve_zip' => false
);
foreach($plugins as $plugin)
{
mm_plugin_download($plugin['path'], $args['path'].$plugin['name'].'.zip');
mm_plugin_unpack($args, $args['path'].$plugin['name'].'.zip');
mm_plugin_activate($plugin['install']);
}
}
function mm_plugin_download($url, $path)
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = curl_exec($ch);
curl_close($ch);
if(file_put_contents($path, $data))
return true;
else
return false;
}
function mm_plugin_unpack($args, $target)
{
if($zip = zip_open($target))
{
while($entry = zip_read($zip))
{
$is_file = substr(zip_entry_name($entry), -1) == '/' ? false : true;
$file_path = $args['path'].zip_entry_name($entry);
if($is_file)
{
if(zip_entry_open($zip,$entry,"r"))
{
$fstream = zip_entry_read($entry, zip_entry_filesize($entry));
file_put_contents($file_path, $fstream );
chmod($file_path, 0777);
//echo "save: ".$file_path."<br />";
}
zip_entry_close($entry);
}
else
{
if(zip_entry_name($entry))
{
mkdir($file_path);
chmod($file_path, 0777);
//echo "create: ".$file_path."<br />";
}
}
}
zip_close($zip);
}
if($args['preserve_zip'] === false)
{
unlink($target);
}
}
function mm_plugin_activate($installer)
{
$current = get_option('active_plugins');
$plugin = plugin_basename(trim($installer));
if(!in_array($plugin, $current))
{
$current[] = $plugin;
sort($current);
do_action('activate_plugin', trim($plugin));
update_option('active_plugins', $current);
do_action('activate_'.trim($plugin));
do_action('activated_plugin', trim($plugin));
return true;
}
else
return false;
}
... and then execute like so:
$plugins = array(
array('name' => 'jetpack', 'path' => 'http://downloads.wordpress.org/plugin/jetpack.1.3.zip', 'install' => 'jetpack/jetpack.php'),
array('name' => 'buddypress', 'path' => 'http://downloads.wordpress.org/plugin/buddypress.1.5.5.zip', 'install' => 'buddypress/bp-loader.php'),
array('name' => 'tumblr-importer', 'path' => 'http://downloads.wordpress.org/plugin/tumblr-importer.0.5.zip', 'install' => 'tumblr-importer/tumblr-importer.php')
);
mm_get_plugins($plugins);
'name' can be anything, as it serves to be more of a temporary value. 'path' is exactly what it looks like, and is the direct URL to the zip file on the Wordpress server. The 'install' value is simply the path to the main PHP script that has all of the plugin information. You will have to know the layout of that particular plugin directory in order to fill out this information, as this is also required for the activation hack to work.
Activation function was found here (credit to sorich87): https://wordpress.stackexchange.com/questions/4041/how-to-activate-plugins-via-code
WARNING: This is by no means a very safe way to do things. I actually think that this can be abused quite easily, so our best bet is to use this as our baseline and try and improve from there.
If you should decide to use this approach, all I ask is that I'm credited with the initial overall script, along with sorich87 for his activation process may God have mercy on your soul.
07/06/2018 EDIT: Seriously, don't do this. By today's standards, this code is hot garbage. Plugin management should be done through Composer and Bedrock.
Inspired by the comment from Jamie Dixon I inspected how Wordpress works.
The process can be seen in /wp-admin/update.php from line 93. A short version could be made like this:
include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' ); //for plugins_api..
$plugin = 'plugin-name';
$api = plugins_api( 'plugin_information', array(
'slug' => $plugin,
'fields' => array(
'short_description' => false,
'sections' => false,
'requires' => false,
'rating' => false,
'ratings' => false,
'downloaded' => false,
'last_updated' => false,
'added' => false,
'tags' => false,
'compatibility' => false,
'homepage' => false,
'donate_link' => false,
),
));
//includes necessary for Plugin_Upgrader and Plugin_Installer_Skin
include_once( ABSPATH . 'wp-admin/includes/file.php' );
include_once( ABSPATH . 'wp-admin/includes/misc.php' );
include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
$upgrader = new Plugin_Upgrader( new Plugin_Installer_Skin( compact('title', 'url', 'nonce', 'plugin', 'api') ) );
$upgrader->install($api->download_link);
If you don't want the feedback displayed you should create a custom Skin class. For example:
$upgrader = new \Plugin_Upgrader( new Quiet_Skin() );
class Quiet_Skin extends \WP_Upgrader_Skin {
public function feedback($string)
{
// just keep it quiet
}
}
Related
public function onSystemCron(&$logs, &$lastRun, $force)
{
$clear_tmp = true;
$clear_logs = [];
$log_files = $this->_application->Filter('system_admin_system_logs', []);
foreach ($log_files as $log_name => $log) {
$clear_logs[$log_name] = empty($log['days']) ? 365 : $log['days'];
}
if (!isset($lastRun[$this->_name])) {
$lastRun[$this->_name] = [];
} elseif (!is_array($lastRun[$this->_name])) {
$lastRun[$this->_name] = [
'tmp' => $lastRun[$this->_name]
];
}
if (!$force) {
if (!empty($lastRun[$this->_name]['tmp'])
&& time() - $lastRun[$this->_name]['tmp'] < 604800
) {
// Less than a week since last run so do not clear tmp
$clear_tmp = false;
}
<?php
namespace -\System\Tool;
class RunCronTool extends AbstractTool
{
protected function _systemToolInfo()
{
return [
'label' => __('Run cron', 'directories'),
'description' => __('Use this tool to manually run cron.', 'directories'),
'weight' => 15,
];
}
public function systemToolRunTask($task, array $settings, $iteration, $total, array &$storage, array &$logs)
{
$this->_application->callHelper('System_Cron', [&$logs, true]);
return 1;
}
}
I have an issue with a WordPress plugin. It offers a button which will force run cron. Usually, making use of this button is not necessary. I tested my WP Cron and it's working fine, so the plugin is the problem. Any data which is in relation to the plugin does not get updated by WP cron. So I'm forced to click this button manually.
So, is there any solution to automate the function of the button?
I mean, I have to open the plugin settings and click on a button to run Cron. Is there any chance to automate this process?
Can provide more information if needed.
Many thanks.
Yes, I have no idea what I'm doing thanks.
When the OS wants to "kick" WordPress's cron system, it does an HTTP(S) GET to example.com/wp-cron.php .
It's possible to do this from php code, like so.
if ( ! wp_doing_cron() ) {
$url = get_site_url( null, 'wp-cron.php' );
$req = new \WP_Http();
$req->get( $url );
}
Do this last in a page view. I do it from the shutdown hook. It won't hang your page because wp-cron.php uses fastcgi_finish_request() to respond immediately to its client.
This lets you run cron from your code if DISABLE_WP_CRON is true or in other circumstances where it won't get called on its own.
But be careful. Don't use this unless you really need it. It's a bad idea to work around a site owner's need to set DISABLE_WP_CRON.
I am migrating and troubleshooting a wordpress theme. I have a gutenberg block and an alert module set up to use the Timber composer package that allows the use of the twig templating engine.
I have it configured in a class
ProcessorTable.php
<?php
namespace CRG\Blocks;
class ProcessorTable
{
public function __construct()
{
$this->createProcessorTable();
}
public function createProcessorTable()
{
if (function_exists('acf_register_block')) {
// register a custom vue gravity forms block
acf_register_block(array(
'name' => 'processor-table-block',
'title' => __('Processor Table Block'),
'description' => __('A Block for displaying a contracted database processors table'),
'category' => 'crg-custom-blocks',
'icon' => 'welcome-write-blog',
'render_callback' => array($this, 'render_processor_table'),
'keywords' => array( 'table' ),
));
}
}
public function render_processor_table($block, $content = '', $is_preview = false)
{
$context = \Timber\Timber::context();
// Store block values.
$context['block'] = $block
// Store field values.
$context['fields'] = get_fields();
// Store $is_preview value.
$context['is_preview'] = $is_preview;
$twigPath = TEMPLATEPATH . "/src/views/blocks/block-processor-table.html.twig";
\Timber\Timber::render($twigPath, $context, 600);
}
}
AlertNotification.php
<?php
namespace CRG\Controllers\SiteWide;
class AlertNotification {
public function AlertModal(){
$context = \Timber\Timber::context();
$context['alert_header_text'] = get_field('alert_header_text', 'options');
$context['alert_text'] = get_field('alert_text', 'options');
$context['alert_icon'] = get_field('alert_icon', 'options');
$context['alert_color'] = get_field('alert_color', 'options');
$context['alert_toggle'] = get_field('alert_toggle', 'options');
\Timber\Timber::render( TEMPLATEPATH . "/src/views/sitewide/alert-modal.html.twig", $context );
}
}
this code worked on a cpanel server and on nexcess managed wordpress hosting, but when I migrated it to kinsta the code stopped rendering. It looks like the Timber::context() works, and the method can find the html.twig files, but it can't render the twig template, and produces no errors
I have tried troubleshooting this by checking the composer package versions, reinstalling the timber composer package, testing the code outside of the class directly into the functions.php file, and verifying the code can reach and output the file contents as a string. I checked the error log files and was unable to find a solution, or a cause of the error.
Hosting configuration:
Kinsta Caching: disabled
Wordpress debugging is enabled
Running on PHP 7.4
Using MySQL
I added this to my functions.php file
\Timber\Timber::$locations = TEMPLATEPATH . "/src/views";
so everything in the functions.php file for timber to work would look like this
require_once(__DIR__ . '/vendor/autoload.php');
$timber = new Timber\Timber();
\Timber\Timber::$locations = TEMPLATEPATH . "/src/views";
then in my ProcessorTable.php file I can call the Twig file path like this
$twigPath = "/blocks/block-processor-table.html.twig";
return \Timber\Timber::render( $twigPath, $context);
My guess about what went wrong is that the timber package was looking in the wrong director when rendering, based on incorrect locations information the Timber:locations method allows you to set up a custom location to store the templates: https://timber.github.io/docs/guides/template-locations/
Not sure why this would have worked on other systems, but not Kinsta. However, this appears to be a good practice going forward by explicitly setting the views.
I'm a Frontend developer and I've decided to expand my knowledge learning PHP. I'm still learning the syntax / ways of accomplishing stuff so please bear with me.
As I began working on my first PHP project I realised that I needed to create a path map just to keep things clean and DRY.
This is what my php file structure looks like by now:
That is easy to represent using a JSON like data structure, but I've found a embarrassing hard time trying to achieve that using PHP data types ( I'm still learning the syntax / ways of doing things ).
I've been reading a bit and I decided to use some associative arrays,
I've come up with this solution, which works but I wanted to check if a simpler solution is possible. ( I'm aiming to learn the best practices of PHP )
See:
$paths = array(
'dirs' => array(
'base' => '/php/'
)
);
$paths['dirs']['common'] = $paths['dirs']['base'] . 'common/';
$paths['dirs']['home'] = $paths['dirs']['base'] . 'home/';
$paths['files'] = array(
'home' => $paths['dirs']['home'] . 'home.php',
'header' => $paths['dirs']['common'] . 'header.php',
'scripts' => $paths['dirs']['common'] . 'scripts.php',
'footer' => $paths['dirs']['common'] . 'footer.php',
'core' => $paths['dirs']['base'] . 'core.php',
'business-variables' => $paths['dirs']['base'] . 'business-variables.php'
);
Am I doing bad practices here?
Is there a better / simpler / standard way of doing this?
If you need to work with files - the best and easiest way is use DirectoryIterator. In this case each file will be object and you can use his methods. Here a small example:
// or '/php/' in your case
$path = '/';
foreach (new DirectoryIterator($path) as $file) {
if ($file->isDot()) continue;
if ($file->isDir()) {
print $file->getFilename() . '<br/>';
}
}
Provided you can't just read the folder structure using DirectoryIterator...
For readability I'd do something like this:
class FileStructure {
const BASE = '/php/';
const COMMON = 'common/%s';
const HOME = 'home/%s';
public function Paths(){
return array(
'home' => $this->Home('home.php'),
'header' => $this->Common('header.php')
);
}
private function Common($file = null){
return sprintf(static::BASE.static::COMMON, $file);
}
private function Home($file = null){
return sprintf(static::BASE.static::HOME, $file);
}
}
How can we setup multiple themes in codeigniter as in Magento?
I want to do this in HMVC CI.
I want to add theming feature with my development. I am using HMVC setup from Codeigniter HMVC
This is properly set and works fine.
This is a great development approach.
Here I am trying to add theme feature with this code, that could be controlled from admin panel.
/themes/themeName/templates/
/themes/themeName/modulesName/View
Approach is if view file not found in themes folder, it should come from
application/modules/moduleName.
What changes I have to do with this script ?
Can anyone suggest me what modification nee to do with this code?
Perhaps I misunderstood but why not extend CI's Loader class and add a theme function? I did this recently and it works great.
public function theme($view, $theme, $vars = array(), $return = FALSE) {
// Check if extension was specified. If not, add .php
$ext = pathinfo(APPPATH . $theme, PATHINFO_EXTENSION);
$view_file = ($ext == '') ? $view.'.php' : $view;
// Verify whether or not the requested theme file exists and set the template directory path prefix accordingly
if(file_exists(APPPATH . "views/store/theme/$theme/$view_file")) {
$this->_ci_view_paths = array(APPPATH . "views/store/theme/$theme/" => true);
}
else {
$this->_ci_view_paths = array(APPPATH . "views/store/theme/default/" => true);
}
return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_object_to_array($vars), '_ci_return' => $return));
}
My website having feature requirement of blogging. I have to make blog which would look same like my website appearance.
How to combine CodeIgniter and Wordpress blogging(only) functionality such that it should look like within same website?
I have seen this question: Wordpress template with codeigniter. But didn't got much clue.
Seems like a bit of overkill.
Why not use a Restful service like json_api to retrieve your posts, then copy over the css file(parts)?
You do this you will need to create 2 files and modify 2 existing functions. One function is in CodeIgniter and the other is in Wordpress.
Here are the steps.
1.) Open your configs/hooks.php file and create a pre_controller hook as follows:
$hook['pre_controller'] = array(
'class' => '',
'function' => 'wp_init',
'filename' => 'wordpress_helper.php',
'filepath' => 'helpers'
);
2.) Create a new file in your helpers directory called 'wordpress_helper.php', and add the following code to it:
/**
*
*/
function wp_init(){
$CI =& get_instance();
$do_blog = TRUE; // this can be a function call to determine whether to load CI or WP
/* here we check whether to do the blog and also we make sure this is a
front-end index call so it does not interfere with other CI modules.
*/
if($do_blog
&& ($CI->router->class == "index" && $CI->router->method == "index")
)
{
// these Wordpress variables need to be globalized because this is a function here eh!
global $post, $q_config, $wp;
global $wp_rewrite, $wp_query, $wp_the_query;
global $allowedentitynames;
global $qs_openssl_functions_used; // this one is needed for qtranslate
// this can be used to help run CI code within Wordpress.
define("CIWORDPRESSED", TRUE);
require_once './wp-load.php';
define('WP_USE_THEMES', true);
// Loads the WordPress Environment and Template
require('./wp-blog-header.php');
// were done. No need to load any more CI stuff.
die();
}
}
3.) Open wp-includes/link-template.php and made the following edit:
if ( ! function_exists('site_url'))
{
function site_url( $path = '', $scheme = null ) {
return get_site_url( null, $path, $scheme );
}
}
4.) Copy url_helper.php from the CodeIgniter helper folder to your APPPATH helper folder
and make the following edit:
if ( ! function_exists('site_url'))
{
function site_url($uri = '', $scheme = null)
{
// if we are in wordpress mode, do the wordpress thingy
if(defined('CIWORDPRESSED') && CIWORDPRESSED){
return get_site_url( null, $path, $scheme );
}else{
$CI =& get_instance();
return $CI->config->site_url($uri);
}
}
}
The steps above will allow you to dynamically load either your CI app or your WP site based on some simple filtering. It also gives you access to all CI functionality within WP of that is something you can use.