How to change Moodle icons in file picker? - icons

Summary
I need to replace the Moodle icons in the Moodle file picker with custom icons. I'm referring to the icons that appear in the File Picker window next to "Content Bank" "Server Files" "Recent Files" and "Private Files". (See screenshot below)
screenshot of file picker with Moodle icons
When I pull the URL for the image I get this file path: website.com/theme/image.php/trema/repository_contentbank/1601324857/icon
I can't seem to figure this file path out in order to replace the Moodle icon image. Does anyone know how to do this?
What I've Tried
I've already replaced all of the png and svg Moodle icon files in the "pix" folders. No dice.
I've sifted through the "repository" folder, the "contentbank" folder and any other subfolder I could find but no luck.
I've also looked at the theme/image.php to look for instances where I can replace the file name but I can't figure it out. I'm posting the code for that file below.
Code
Here's the theme/image.php code...
define('NO_DEBUG_DISPLAY', true);
// we need just the values from config.php and minlib.php
define('ABORT_AFTER_CONFIG', true);
require('../config.php'); // this stops immediately at the beginning of lib/setup.php
if ($slashargument = min_get_slash_argument()) {
$slashargument = ltrim($slashargument, '/');
if (substr_count($slashargument, '/') < 3) {
image_not_found();
}
if (strpos($slashargument, '_s/') === 0) {
// Can't use SVG
$slashargument = substr($slashargument, 3);
$usesvg = false;
} else {
$usesvg = true;
}
// image must be last because it may contain "/"
list($themename, $component, $rev, $image) = explode('/', $slashargument, 4);
$themename = min_clean_param($themename, 'SAFEDIR');
$component = min_clean_param($component, 'SAFEDIR');
$rev = min_clean_param($rev, 'INT');
$image = min_clean_param($image, 'SAFEPATH');
} else {
$themename = min_optional_param('theme', 'standard', 'SAFEDIR');
$component = min_optional_param('component', 'core', 'SAFEDIR');
$rev = min_optional_param('rev', -1, 'INT');
$image = min_optional_param('image', '', 'SAFEPATH');
$usesvg = (bool)min_optional_param('svg', '1', 'INT');
}
if (empty($component) or $component === 'moodle' or $component === 'core') {
$component = 'core';
}
if (empty($image)) {
image_not_found();
}
if (file_exists("$CFG->dirroot/theme/$themename/config.php")) {
// exists
} else if (!empty($CFG->themedir) and file_exists("$CFG->themedir/$themename/config.php")) {
// exists
} else {
image_not_found();
}
$candidatelocation = "$CFG->localcachedir/theme/$rev/$themename/pix/$component";
$etag = sha1("$rev/$themename/$component/$image");
if ($rev > 0) {
if (file_exists("$candidatelocation/$image.error")) {
// This is a major speedup if there are multiple missing images,
// the only problem is that random requests may pollute our cache.
image_not_found();
}
$cacheimage = false;
if ($usesvg && file_exists("$candidatelocation/$image.svg")) {
$cacheimage = "$candidatelocation/$image.svg";
$ext = 'svg';
} else if (file_exists("$candidatelocation/$image.png")) {
$cacheimage = "$candidatelocation/$image.png";
$ext = 'png';
} else if (file_exists("$candidatelocation/$image.gif")) {
$cacheimage = "$candidatelocation/$image.gif";
$ext = 'gif';
} else if (file_exists("$candidatelocation/$image.jpg")) {
$cacheimage = "$candidatelocation/$image.jpg";
$ext = 'jpg';
} else if (file_exists("$candidatelocation/$image.jpeg")) {
$cacheimage = "$candidatelocation/$image.jpeg";
$ext = 'jpeg';
} else if (file_exists("$candidatelocation/$image.ico")) {
$cacheimage = "$candidatelocation/$image.ico";
$ext = 'ico';
}
if ($cacheimage) {
if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
// We do not actually need to verify the etag value because our files
// never change in cache because we increment the rev parameter.
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
$mimetype = get_contenttype_from_ext($ext);
header('HTTP/1.1 304 Not Modified');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Cache-Control: public, max-age='.$lifetime.', no-transform');
header('Content-Type: '.$mimetype);
header('Etag: "'.$etag.'"');
die;
}
send_cached_image($cacheimage, $etag);
}
}
//=================================================================================
// ok, now we need to start normal moodle script, we need to load all libs and $DB
define('ABORT_AFTER_CONFIG_CANCEL', true);
define('NO_MOODLE_COOKIES', true); // Session not used here
define('NO_UPGRADE_CHECK', true); // Ignore upgrade check
require("$CFG->dirroot/lib/setup.php");
$theme = theme_config::load($themename);
$themerev = theme_get_revision();
if ($themerev <= 0 or $rev != $themerev) {
// Do not send caching headers if they do not request current revision,
// we do not want to pollute browser caches with outdated images.
$imagefile = $theme->resolve_image_location($image, $component, $usesvg);
if (empty($imagefile) or !is_readable($imagefile)) {
image_not_found();
}
send_uncached_image($imagefile);
}
make_localcache_directory('theme', false);
// At this stage caching is enabled, and either:
// * we have no cached copy of the image in any format (either SVG, or non-SVG); or
// * we have a cached copy of the SVG, but the non-SVG was requested by the browser.
//
// Because of the way in which the cache return code works above:
// * if we are allowed to return SVG, we do not need to cache the non-SVG version; however
// * if the browser has requested the non-SVG version, we *must* cache _both_ the SVG, and the non-SVG versions.
// First get all copies - including, potentially, the SVG version.
$imagefile = $theme->resolve_image_location($image, $component, true);
if (empty($imagefile) || !is_readable($imagefile)) {
// Unable to find a copy of the image file in any format.
// We write a .error file for the image now - this will be used above when searching for cached copies to prevent
// trying to find the image in the future.
if (!file_exists($candidatelocation)) {
#mkdir($candidatelocation, $CFG->directorypermissions, true);
}
// Make note we can not find this file.
$cacheimage = "$candidatelocation/$image.error";
$fp = fopen($cacheimage, 'w');
fclose($fp);
image_not_found();
}
// The image was found, and it is readable.
$pathinfo = pathinfo($imagefile);
// Attempt to cache it if necessary.
// We don't really want to overwrite any existing cache items just for the sake of it.
$cacheimage = "$candidatelocation/$image.{$pathinfo['extension']}";
if (!file_exists($cacheimage)) {
// We don't already hold a cached copy of this image. Cache it now.
$cacheimage = cache_image($image, $imagefile, $candidatelocation);
}
if (!$usesvg && $pathinfo['extension'] === 'svg') {
// The browser has requested that a non-SVG version be returned.
// The version found so far is the SVG version - try and find the non-SVG version.
$imagefile = $theme->resolve_image_location($image, $component, false);
if (empty($imagefile) || !is_readable($imagefile)) {
// A non-SVG file could not be found at all.
// The browser has requested a non-SVG version, so we must return image_not_found().
// We must *not* write an .error file because the SVG is available.
image_not_found();
}
// An non-SVG version of image was found - cache it.
// This will be used below in the image serving code.
$cacheimage = cache_image($image, $imagefile, $candidatelocation);
}
if (connection_aborted()) {
// Request was cancelled - do not send anything.
die;
}
// Make sure nothing failed.
clearstatcache();
if (file_exists($cacheimage)) {
// The cached copy was found, and is accessible. Serve it.
send_cached_image($cacheimage, $etag);
}
send_uncached_image($imagefile);
//=================================================================================
//=== utility functions ==
// we are not using filelib because we need to fine tune all header
// parameters to get the best performance.
function send_cached_image($imagepath, $etag) {
global $CFG;
require("$CFG->dirroot/lib/xsendfilelib.php");
// 90 days only - based on Moodle point release cadence being every 3 months.
$lifetime = 60 * 60 * 24 * 90;
$pathinfo = pathinfo($imagepath);
$imagename = $pathinfo['filename'].'.'.$pathinfo['extension'];
$mimetype = get_contenttype_from_ext($pathinfo['extension']);
header('Etag: "'.$etag.'"');
header('Content-Disposition: inline; filename="'.$imagename.'"');
header('Last-Modified: '. gmdate('D, d M Y H:i:s', filemtime($imagepath)) .' GMT');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + $lifetime) .' GMT');
header('Pragma: ');
header('Cache-Control: public, max-age='.$lifetime.', no-transform, immutable');
header('Accept-Ranges: none');
header('Content-Type: '.$mimetype);
if (xsendfile($imagepath)) {
die;
}
if ($mimetype === 'image/svg+xml') {
// SVG format is a text file. So we can compress SVG files.
if (!min_enable_zlib_compression()) {
header('Content-Length: '.filesize($imagepath));
}
} else {
// No need to compress other image formats.
header('Content-Length: '.filesize($imagepath));
}
readfile($imagepath);
die;
}
function send_uncached_image($imagepath) {
$pathinfo = pathinfo($imagepath);
$imagename = $pathinfo['filename'].'.'.$pathinfo['extension'];
$mimetype = get_contenttype_from_ext($pathinfo['extension']);
header('Content-Disposition: inline; filename="'.$imagename.'"');
header('Last-Modified: '. gmdate('D, d M Y H:i:s', time()) .' GMT');
header('Expires: '. gmdate('D, d M Y H:i:s', time() + 15) .' GMT');
header('Pragma: ');
header('Accept-Ranges: none');
header('Content-Type: '.$mimetype);
header('Content-Length: '.filesize($imagepath));
readfile($imagepath);
die;
}
function image_not_found() {
header('HTTP/1.0 404 not found');
die('Image was not found, sorry.');
}
function get_contenttype_from_ext($ext) {
switch ($ext) {
case 'svg':
return 'image/svg+xml';
case 'png':
return 'image/png';
case 'gif':
return 'image/gif';
case 'jpg':
case 'jpeg':
return 'image/jpeg';
case 'ico':
return 'image/vnd.microsoft.icon';
}
return 'document/unknown';
}
/**
* Caches a given image file.
*
* #param string $image The name of the image that was requested.
* #param string $imagefile The location of the image file we want to cache.
* #param string $candidatelocation The location to cache it in.
* #return string The path to the cached image.
*/
function cache_image($image, $imagefile, $candidatelocation) {
global $CFG;
$pathinfo = pathinfo($imagefile);
$cacheimage = "$candidatelocation/$image.".$pathinfo['extension'];
clearstatcache();
if (!file_exists(dirname($cacheimage))) {
#mkdir(dirname($cacheimage), $CFG->directorypermissions, true);
}
// Prevent serving of incomplete file from concurrent request,
// the rename() should be more atomic than copy().
ignore_user_abort(true);
if (#copy($imagefile, $cacheimage.'.tmp')) {
rename($cacheimage.'.tmp', $cacheimage);
#chmod($cacheimage, $CFG->filepermissions);
#unlink($cacheimage.'.tmp'); // just in case anything fails
}
return $cacheimage;
}

Maybe try enable "Theme designer mode" HOME -> SITE ADMINISTRATION -> APPEARANCE -> THEMES -> THEME SETTINGS and purge caches.

Related

How to Save Multiple User Metadata in Database?

I am trying to create a registration form (here) with the help of a free plugin.
There are many elements in the registration form.
In the admin panel, meta keys can be assigned for each element in the plugin interface. So there is such an opportunity.
I'm trying to collect many of the elements on the same meta key by taking advantage of this possibility. In this direction, I gave the common meta key value to the elements I created. For example: info_about_register
So far everything is fine. However, if the form is posted, I can only get the last entry in the usermeta table. So the plugin is not serializing the same meta key data. An array does not occur.
There are many form elements. I want to pull these to the admin panel later. Therefore, I think that defining a separate line for each element will tire the system a lot. I contacted the plugin developers about this issue. However, no response for about 1.5 weeks.
I tried to solve this problem myself and found the codes where the action was taken. I made some changes to these. However, I was not successful. I would be very happy if you guide me.
.../includes/class-frontend.php
/*
* Called after submission save
* Registers new user into WordPress.
* Also map field values to user meta (If configured)
*/
public function after_submission_insertion($errors, $submission, $data) {
$sub_model = erforms()->submission;
$form_model = erforms()->form;
$form = $form_model->get_form($submission['form_id']);
// Copy attachment values in data from submission (as $data does not have any uploaded file values)
if(!empty($submission['attachments'])){
foreach($submission['attachments'] as $attachment){
if(!isset($data[$attachment['f_name']])){
$data[$attachment['f_name']]= $attachment['f_val'];
}
}
}
if ($form['type'] == "reg") { // Handling of registration forms
$user = 0;
$id = 0;
// Get mapping for user meta fields if any
$user_field_map = erforms_filter_user_fields($form['id'], $submission['fields_data']);
// Avoid user registration process if user already logged in
if (!is_user_logged_in()) {
$email_or_username = $user_field_map['user_email'];
if (isset($user_field_map['password'])) {
// Silently creates user
$username = isset($user_field_map['username']) ? $data[$user_field_map['username']] : $data[$email_or_username];
do_action('erf_before_user_creation',$submission);
$id = wp_create_user($username, $data[$user_field_map['password']], $data[$email_or_username]);
} else {
// Register user and sends random password via email notification
do_action('erf_before_user_creation',$submission);
$id = register_new_user($data[$email_or_username], $data[$email_or_username]);
}
if (is_wp_error($id)) {
// In case something goes wrong delete the submission
wp_delete_post($submission['id'], true);
$error_code = $id->get_error_code();
if ($error_code == 'existing_user_login') {
$email_or_username = 'username_error';
}
$errors[] = array($email_or_username, $id->get_error_message($id->get_error_code()));
return $errors;
} else {
$selected_role = erforms_get_selected_role($submission['form_id'], $data);
if (!empty($selected_role)) { // Means user has selected any role
$user_model = erforms()->user;
$selected_role= apply_filters('erf_before_setting_user_role',$selected_role,$id,$form, $submission);
$user_model->set_user_role($id, $selected_role);
}
foreach ($user_field_map as $req_key => $meta_key) {
$is_primary_key = in_array($meta_key, erforms_primary_field_types());
if (isset($data[$req_key]) && !$is_primary_key) {
$m_keys= explode(',',$meta_key);
foreach($m_keys as $m_key){
if(!empty($m_key)){
$status = erforms_update_user_meta($id, $m_key, $data[$req_key]);
do_action('erf_user_meta_updated',$m_key,$id,$data[$req_key],$status);
}
}
}
}
do_action('erf_user_created', $id, $form['id'], $submission['id']);
}
} else {
// Get user details
$user = wp_get_current_user();
$id = $user->ID;
foreach ($user_field_map as $req_key => $meta_key) {
$is_primary_key = in_array($meta_key, erforms_primary_field_types());
if (isset($data[$req_key]) && !$is_primary_key) {
$m_keys= explode(',',$meta_key);
foreach($m_keys as $m_key){
if(!empty($m_key)){
$status= erforms_update_user_meta($id,$m_key,$data[$req_key]);
do_action('erf_user_meta_updated',$m_key,$id,$data[$req_key],$status);
}
}
}
}
//...
// User meta,URL params or default values should be prefilled only when we are not loading submission data
if(empty($submission)){
$user_meta = erforms()->user->frontend_localize_user_meta($form);
$filtered_url_params = array();
foreach ($_GET as $key => $val) {
$filtered_url_params[urldecode(strtolower(wp_unslash($key)))] = sanitize_text_field(wp_unslash($val));
}
$url_keys = array_keys($filtered_url_params);
foreach ($form['fields'] as $field) {
$label = !empty($field['label']) ? strtolower(str_replace(' ', '_', $field['label'])) : '';
$label = str_replace('&', 'and', $label); // Cause URL params do not allow &
if (!empty($field['name']) && !empty($label) && in_array($label, $url_keys)) {
if (!isset($user_meta[$field['name']]) && !empty($filtered_url_params[$label])) {
$user_meta[$field['name']] = stristr($filtered_url_params[$label], '|') ? explode('|', $filtered_url_params[$label]) : $filtered_url_params[$label];
}
}
if(!empty($field['name']) && empty($user_meta[$field['name']]) && !empty($field['value'])){
$user_meta[$field['name']] = $field['value'];
}
}
if (!empty($user_meta)) {
$data['user_meta'] = $user_meta;
}
}
$data= apply_filters('erf_form_localize_data',$data,$form);
return $data;
}
.../includes/functions.php
/**
* Wrapper to call update_user_meta function.
* This simply calls wordpress meta function and does not add any special prefix.
* Checks for any special meta key to update user table data.
* For example: display_name : updates user's display name. Instead of adding display_name usermeta
*/
function erforms_update_user_meta($user_id, $m_key, $m_val) {
switch ($m_key) {
case 'display_name' : $status = wp_update_user(array('ID' => $user_id, $m_key => $m_val));
return is_wp_error($status) ? false : true;
}
return update_user_meta($user_id, $m_key, $m_val);
}
Update:
Data can be stored as an array with a definition like below. Also, the key values ($all_meta_value[$req_key]) are equal to the id, name values automatically assigned to the HTML elements by the plugin.
Using this, different conditional states can be written.
The following code must be defined in the function.php file in the child theme:
add_action('erf_user_meta_updated','for_new_user_meta_uptated',10);
function for_new_user_meta_uptated($data){
//if the defined meta key (info_about_register) matches
if($m_key == 'info_about_register'){
$all_meta_value[$req_key] = $data;
}
}
add_action('erf_user_created', 'for_new_user_created',10);
function for_new_user_created($id){
//Save the values as a new user meta
add_user_meta($id, 'new_info_about_register', $get_meta_value);
//Remove saved single element user meta.
delete_user_meta($id, 'info_about_register');
}

Password migration from Drupal to Yii

Has anyone already migrate a site from Drupal to Yii?
Is there some code in Yii that can implement the Drupal encryption and salt for user password?
I have, but not to YII. Its not a big deal. You can use the same salt and encryption in YII as well (easier since both are PHP based).
Check these two pages:
http://www.yiiframework.com/wiki/425
https://api.drupal.org/api/drupal/includes!password.inc/function/user_hash_password/7
Thanks Amar, I follow your links and
I create the YII functions for migrating from drupal7.
They work for me and I could save 1 working hour to someone (not more I guess)
I put all of them in
class UserIdentity extends CUserIdentity
and use this way in
..
} else if (self::user_check_password($this->password, $users->password) ) {
..
in public function authenticate()
private function user_check_password($password, $registered_password) {
if (substr($registered_password, 0, 2) == 'U$') {
// This may be an updated password from user_update_7000(). Such hashes
// have 'U' added as the first character and need an extra md5().
$stored_hash = substr($registered_password, 1);
$password = md5($password);
}
else {
$stored_hash = $registered_password;
}
$type = substr($stored_hash, 0, 3);
switch ($type) {
case '$S$':
// A normal Drupal 7 password using sha512.
$hash = self::_password_crypt('sha512', $password, $stored_hash);
break;
case '$H$':
// phpBB3 uses "$H$" for the same thing as "$P$".
case '$P$':
// A phpass password generated using md5. This is an
// imported password or from an earlier Drupal version.
$hash = self::_password_crypt('md5', $password, $stored_hash);
break;
default:
return FALSE;
}
return ($hash && $stored_hash == $hash);
}
private function user_hash_password($password) {
return self::_password_crypt('sha512', $password, self::_password_generate_salt(15));
}
private function _password_crypt($algo, $password, $setting) {
// The first 12 characters of an existing hash are its setting string.
$setting = substr($setting, 0, 12);
if ($setting[0] != '$' || $setting[2] != '$') {
return FALSE;
}
$count_log2 = self::_password_get_count_log2($setting);
// Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT
if ($count_log2 < 7 || $count_log2 > 30) {
return FALSE;
}
$salt = substr($setting, 4, 8);
// Hashes must have an 8 character salt.
if (strlen($salt) != 8) {
return FALSE;
}
// Convert the base 2 logarithm into an integer.
$count = 1 << $count_log2;
// We rely on the hash() function being available in PHP 5.2+.
$hash = hash($algo, $salt . $password, TRUE);
do {
$hash = hash($algo, $hash . $password, TRUE);
} while (--$count);
$len = strlen($hash);
$output = $setting . self::_password_base64_encode($hash, $len);
// _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
// _password_base64_encode() of a 64 byte sha512 will always be 86 characters.
$expected = 12 + ceil((8 * $len) / 6);
return (strlen($output) == $expected) ? substr($output, 0, 55) : FALSE;
}
private function _password_generate_salt($count_log2) {
$output = '$S$';
// Ensure that $count_log2 is within set bounds.
$count_log2 = self::_password_enforce_log2_boundaries($count_log2);
// We encode the final log2 iteration count in base 64.
$itoa64 = self::_password_itoa64();
$output .= $itoa64[$count_log2];
// 6 bytes is the standard salt for a portable phpass hash.
$output .= self::_password_base64_encode(self::drupal_random_bytes(6), 6);
return $output;
}
private function _password_enforce_log2_boundaries($count_log2) {
if ($count_log2 < 7) {
return 7;
}
elseif ($count_log2 > 30) {
return 30;
}
return (int) $count_log2;
}
private function _password_itoa64() {
return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
}
private function _password_base64_encode($input, $count) {
$output = '';
$i = 0;
$itoa64 = self::_password_itoa64();
do {
$value = ord($input[$i++]);
$output .= $itoa64[$value & 0x3f];
if ($i < $count) {
$value |= ord($input[$i]) << 8;
}
$output .= $itoa64[($value >> 6) & 0x3f];
if ($i++ >= $count) {
break;
}
if ($i < $count) {
$value |= ord($input[$i]) << 16;
}
$output .= $itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count) {
break;
}
$output .= $itoa64[($value >> 18) & 0x3f];
} while ($i < $count);
return $output;
}
private function drupal_random_bytes($count) {
// $random_state does not use drupal_static as it stores random bytes.
static $random_state, $bytes, $has_openssl;
$missing_bytes = $count - strlen($bytes);
if ($missing_bytes > 0) {
// PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes()
// locking on Windows and rendered it unusable.
if (!isset($has_openssl)) {
$has_openssl = version_compare(PHP_VERSION, '5.3.4', '>=') && function_exists('openssl_random_pseudo_bytes');
}
// openssl_random_pseudo_bytes() will find entropy in a system-dependent
// way.
if ($has_openssl) {
$bytes .= openssl_random_pseudo_bytes($missing_bytes);
}
// Else, read directly from /dev/urandom, which is available on many *nix
// systems and is considered cryptographically secure.
elseif ($fh = #fopen('/dev/urandom', 'rb')) {
// PHP only performs buffered reads, so in reality it will always read
// at least 4096 bytes. Thus, it costs nothing extra to read and store
// that much so as to speed any additional invocations.
$bytes .= fread($fh, max(4096, $missing_bytes));
fclose($fh);
}
// If we couldn't get enough entropy, this simple hash-based PRNG will
// generate a good set of pseudo-random bytes on any system.
// Note that it may be important that our $random_state is passed
// through hash() prior to being rolled into $output, that the two hash()
// invocations are different, and that the extra input into the first one -
// the microtime() - is prepended rather than appended. This is to avoid
// directly leaking $random_state via the $output stream, which could
// allow for trivial prediction of further "random" numbers.
if (strlen($bytes) < $count) {
// Initialize on the first call. The contents of $_SERVER includes a mix of
// user-specific and system information that varies a little with each page.
if (!isset($random_state)) {
$random_state = print_r($_SERVER, TRUE);
if (function_exists('getmypid')) {
// Further initialize with the somewhat random PHP process ID.
$random_state .= getmypid();
}
$bytes = '';
}
do {
$random_state = hash('sha256', microtime() . mt_rand() . $random_state);
$bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
} while (strlen($bytes) < $count);
}
}
$output = substr($bytes, 0, $count);
$bytes = substr($bytes, $count);
return $output;
}
private function _password_get_count_log2($setting) {
$itoa64 = self::_password_itoa64();
return strpos($itoa64, $setting[3]);
}

Automatically populating dataobjectset with contents of assets subfolder in Silverstripe

I'm currently working on a Silverstripe 3.1 website that has dozens of random header images.
I can easily setup a "HeaderImage" databobjectset, but manually adding every image via the CMS would be a tedious headache.
Is there a simple way to have a dataobjectset automatically populated by the contents of a folder.
For example every image file in /assets/header-images/ automatically becomes a "HeaderImage" object. I want to be able to easily add or remove images.
Any ideas would be appreciated.
some details about the proposed solutions.
1) Like #3dgoo mentioned, using the GridFieldBulkEditingTools module. Download the latest master of best via composer "colymba/gridfield-bulk-editing-tools": "dev-master". This will let you upload a bunch of images and will create a DataObject for each one. Use the Bulk upload button. Here is how to have it set up in ModelAdmin:
class HeaderAdmin extends ModelAdmin
{
private static $managed_models = array('HeaderImage');
private static $url_segment = 'header-admin';
private static $menu_title = 'Header admin';
public function getEditForm($id = null, $fields = null)
{
$form = parent::getEditForm($id, $fields);
$gridField = $form->Fields()->fieldByName($this->sanitiseClassName('HeaderImage'));
if ( $gridField )
{
$gridField->getConfig()->addComponent(new GridFieldBulkImageUpload());
}
return $form;
}
}
2) Another solution, which would require a lot more work, is create a BuildTask and sort out the logic in run():
class ImportHeaderImagesTask extends BuildTask
{
protected $title = 'Import Header Images';
protected $description = 'Import Header Images......';
/**
* Check that the user has appropriate permissions to execute this task
*/
public function init()
{
if( !Director::is_cli() && !Director::isDev() && !Permission::check('ADMIN') )
{
return Security::permissionFailure();
}
parent::init();
}
/**
* Do some stuff
*/
public function run($request)
{
// this is where files are uploaded manually
$TempFTPFolder = ASSETS_PATH . '/FTP';
// This is the folder where files will be moved
$LiveFolderPath = 'assets/path/to/final/live/folder/';
$LiveFolder = DataObject::get_one('File', "Filename = '$LiveFolderPath'");
if ( file_exists( $TempFTPFolder ) && $LiveFolder->ID ) // if the FTP upload folder exist and the destination live folder exist
{
$FTPList = scandir( $TempFTPFolder ); // get the FTP folder content
foreach ($FTPList as $FileFolder)
{
$FTPFile = $TempFTPFolder . '/' . $FileFolder;
if ( is_file( $FTPFile ) ) // process files only
{
// Create File object for the live version
$NewFile = new File();
$NewFile->setParentID( $LiveFolder->ID );
$NewFile->setName( $FileFolder );
// get target name/path
$RenameTarget = $NewFile->getFullPath();
if ( $RenameTarget )
{
$moved = false;
try {
$moved = rename( $FTPFile, $RenameTarget ); // move the FTP file to the live folder
} catch (Exception $e) {}
if ( $moved )
{
$NewFile->write();
// create DataObject and add image relation
$HeaderImage = HeaderImage::create();
$HeaderImage->ImageID = $NewFile->ID;
$HeaderImage->write();
}
}
}
}
}
}
}
You can run this tasks via the dev/ url or via the command line or a CRON job. Note that I adapted the run() logic from something I've done a while ago, so not guaranteed it will work by just copy/pasting.

Silverstripe Custom Validator on Uploadfield

I am using foresight.js to load hi-res images for retina devices. Foresight attempts to replace lo-res images with 2x-pixel density images. Since foresight attempts to replace lo-res images before the page has rendered, it is not possible for me to use the GD image resizing methods in the template for my resized images. So, I am allowing the SS 3.1 cms user to upload one large image and having the system re-size it after upload - leaving a 1x and 2x image in the assets folder.
My question is how can I set a custom validation error message if the cms user does not upload a large enough image?
Here is the code that resizes the image on upload.
class ResampledImage extends Image {
static $default_lores_x = 250;
static $default_lores_y = 250;
static $default_hires_x = 500;
static $default_hires_y = 500;
static $default_assets_dir = 'Uploads';
static $hires_flag = '2x';
function getLoResX() {
return ( static::$lores_x ) ? static::$lores_x : self::$default_lores_x;
}
function getLoResY() {
return ( static::$lores_y ) ? static::$lores_y : self::$default_lores_y;
}
function getHiResX() {
return ( static::$hires_x ) ? static::$hires_x : self::$default_hires_x;
}
function getHiResY() {
return ( static::$hires_y ) ? static::$hires_y : self::$default_hires_y;
}
function getAssetsDir() {
return ( static::$assets_dir ) ? static::$assets_dir : self::$default_assets_dir;
}
function onAfterUpload() {
$this->createResampledImages();
}
function onAfterWrite() {
$this->createResampledImages();
}
function createResampledImages() {
$extension = strtolower($this->getExtension());
if( $this->getHeight() >= $this->getHiResX() || $this->getWidth() >= $this->getHiResY() ) {
$original = $this->getFullPath();
$resampled = $original. '.tmp.'. $extension;
$orig_title = $this->getTitle();
$path_to_hires = Director::baseFolder() . '/' . ASSETS_DIR . '/' . $this->getAssetsDir();
$hires = $path_to_hires . '/' . $orig_title . self::$hires_flag . '.' . $extension;
$gd_lg = new GD($original);
$gd_sm = new GD($original);
if ( $gd_lg->hasImageResource() ) {
$gd_lg = $gd_lg->resizeRatio($this->getHiResX(), $this->getHiResY());
if ( $gd_lg )
$gd_lg->writeTo($hires);
}
if($gd_sm->hasImageResource()) {
$gd_sm = $gd_sm->resizeRatio($this->getLoResX(), $this->getLoResY());
if($gd_sm) {
$gd_sm->writeTo($resampled);
unlink($original);
rename($resampled, $original);
}
}
}
}
Looking at UploadField::setFileEditValidator() it appears that I can designate a method on my extended Image class to use as a Validator so that I can check for $this->getWidth() and $this->getHeight() and return an error if they are not large enough.
Is this possible?
I tried adding the following method to ResampledImage, but this was unsuccessful:
function MyValidator() {
$valid = true;
if ( $this->getHeight() < $this->getHiResX() || $this->getWidth() < $this->getHiResY() ) {
$this->validationError("Thumbnail",'Please upload a larger image');
$valid = false;
}
return $valid;
}
I think the fileEditValidator is acutally used after the image has been uploaded and is for the EditForm when displayed/edited.
Seems that what you are looking for is validate the Upload. You can set a custom Upload_Validator with setValidator($validator) on your UploadField.
So what I would try is create a custom validator class (maybe named CustomUploadValidator) that extends Upload_Validator (source can be found in the Upload.php file in the framework). So, something along those lines:
$myValidator = new CustomUploadValidator();
$uploadField->setValidator($myValidator);
In your custom validator class maybe create a method isImageLargeEnough() which you would call in the validate() method:
public function validate() {
if(!$this->isImageLargeEnough()) {
$this->errors[] = 'Image size is not large enough';
return false;
}
return parent::validate();
}
In your isImageLargeEnough() you can access the uploaded image through $this->tmpFile. So maybe do something like:
public function isImageLargeEnough()
{
$imageSize = getimagesize( $this->tmpFile["tmp_name"] );
if ($imageSize !== false)
{
if ( $imageSize[0] < 500 || $imageSize[1] < 500 )
{
return false;
}
}
return true;
}
Here the min width/height are hard coded to 500, but you can probably implement a setMinImageSizes method that stores those on a variable in your custom validator class. which could be called like $uploadField->getValidator()->setMinImageSize(543, 876);
None of this is actually tested, but hopefully it can give you some pointers on what to look for.

Drupal 7 with Multiple Login sources?

I'm just a new to Drupal but i wanna make my site to be authenticated from external login sources. I mean, not to use the current Drupal database to login. Then use one or more external databases.
To make it a little more clear:
User Login Authentication will not use the existing Drupal database.
Then it will use the external database(s)
External Database will be one or more (If User is not found on one external database, then found on another source again.)
I've been trying to hack the Drupal Login but i can't make it yet as the structure is complicated enough. Any solution or suggestion please?
Which files will be needed to modify?
You shouldn't completely discard Drupal's user system for this, but rather piggy tail on it with your own authentication. The way this works is by 1) collecting user credentials, 2) authenticating against your custom sources and 3) registering/logging in an external user.
Collecting credentials
One way of collecting user credentials is by hijacking the user login form. You do this by implementing hook_form_alter() and add your own validation callback.
function mymodule_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == 'user_login') {
$form['#validate'][] = array('mymodule_user_login_validate');
$form['#validate'][] = array('mymodule_user_login_submit');
}
}
The login form will look exactly the same and but it will send the username and password to your callback instead.
The user_login_name_validate() and user_login_final_validate() callbacks are user.module's default validators. They supply user blocking functionality and brute force protection but feel free to leave them out.
Authentication
When the login form is sent you pick up the credentials and authenticate against your custom sources.
function mymodule_user_login_validate($form, &$form_state) {
mymodule_authenticate(
$form_state['values']['name'],
$form_state['values']['name']
);
}
If the credentials doesn't check out you can just call form_set_error() to inform the user of what went wrong. Doing this will also make sure no further validation or submit callbacks are executed.
External users
So if no error is evoked then Drupal will move on to running your submit callback. Then we can run user_external_login_register() to create our external user as a Drupal user.
function mymodule_user_login_submit($form, &$form_state) {
user_external_login_register($form_state['values']['name'], 'mymodule');
}
As the name implies, this function will also log the user in. And that should be it!
There's a module for that
Chances are there is already a module for what you want to do. Always go search for existing contrib modules before starting your own!
define('DRUPAL_ROOT', 'full/path/to/drupal');
$drupal_url = 'http://site.com';
drupal_external_load($drupal_path, $drupal_url);
if ( !($user->uid > 0) ) {
drupal_external_login('user', 'pass');
} else {
print 'user is already logged';
}
function drupal_external_load($drupal_url) {
global $base_url;
// set drupal base_url (if not set set in settings.php)
// because it's used in session name
$base_url = $drupal_url;
// save current path
$current_path = getcwd();
// move to drupal path, because it uses relative path for its includes
chdir(DRUPAL_ROOT);
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
// to use the drupal global user var (instead of session hack)
// drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
// return the current path
chdir($current_path);
}
// load a drupal user into the user obj
function drupal_external_userload($user_info = array()) {
// Dynamically compose a SQL query:
$query = array();
$params = array();
if (is_numeric($user_info)) {
$user_info = array('uid' => $user_info);
}
elseif (!is_array($user_info)) {
return FALSE;
}
foreach ($user_info as $key => $value) {
if ($key == 'uid' || $key == 'status') {
$query[] = "$key = %d";
$params[] = $value;
}
else if ($key == 'pass') {
$query[] = "pass = '%s'";
$params[] = md5($value);
}
else {
$query[]= "LOWER($key) = LOWER('%s')";
$params[] = $value;
}
}
$result = db_query('SELECT * FROM {users} u WHERE '. implode(' AND ', $query), $params);
if ($user = db_fetch_object($result)) {
$user = drupal_unpack($user);
$user->roles = array();
if ($user->uid) {
$user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
}
else {
$user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user';
}
$result = db_query('SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON
ur.rid = r.rid WHERE ur.uid = %d', $user->uid);
while ($role = db_fetch_object($result)) {
$user->roles[$role->rid] = $role->name;
}
//user_module_invoke('load', $user_info, $user);
}
else {
$user = FALSE;
}
return $user;
}
// don't send any headers before calling this
function drupal_external_login($username, $password) {
global $user;
if ( $user->uid > 0 ) {
if ( $user->name != $username ) {
drupal_external_logout();
} else {
return true;
}
}
require DRUPAL_ROOT. '/includes/password.inc' ;
$account = user_load_by_name($username);
if ( user_check_password($password, $account) ) {
$user = $account;
drupal_session_regenerate();
return true;
}
return false;
}
function drupal_external_logout() {
global $user;
session_destroy();
$user = drupal_anonymous_user();
}

Resources