A portion of my Laravel app uses the cli to do batch processing. I'm trying to make a progress bar that will give useful information as to how far along in the process you are. One thing I am doing batch processing on is addresses. I'd like to format it somewhat similar to this:
Processing addresses...
Local Shopping Mall
123 Fake street
Cityville, USA
12345
4/378 [>---------------------------] 1%
After the first address, I'd like to move the cursor back to right after 'Processing addresses...' and I'd like to overwrite the old address with the new one.
Right now, I'm getting this:
Processing addresses...
124 Fake street
Cityville, USA
12345
125 Fake street
Cityville, USA
12345
126 Fake street
Cityville, USA
12345
127 Fake street
Cityville, USA
12345
4/378 [>---------------------------] 1%
Here's the (slightly modified) code I'm using:
public function handle()
{
$this->info('Processing addresses...');
$addresses = \App\Address::all();
$bar = $this->output->createProgressBar(count($addresses));
foreach ($addresses as $address) {
$bar->clear();
$this->info("\r" . $this->_getFormattedAddress($address));
$bar->advance();
sleep(1);
}
}
private function _getFormattedAddress(\App\Address $address){
$out = "";
$out .= $address->address1 . "\n";
$out .= $address->address2 . "\n";
$out .= $address->city . "\n";
$out .= $address->region . "\n";
$out .= $address->iso_code . "\n";
$out .= $address->postal_code . "\n";
return $out;
}
"\r" - return point to start line and $this->info() - add PHP_EOL to end line.
If you need return cursor to top output. I dont know Laravel can do it or not. But this class help you:
class OutputCliHelper
{
protected static $lastLines = 0;
/**
* #param $data
* #return string
*/
public static function replaceSingleLine($data){
return "\r".$data;
}
/**
* #return int
*/
public static function getLastLines()
{
return static::$lastLines;
}
/**
* #param $data
* #param null $lastLines
* #return string
*/
public static function replaceMultiLine($data, $lastLines = null) {
if(!is_null($lastLines)) {
static::$lastLines = $lastLines;
}
$term_width = exec('tput cols', $toss, $status);
if($status) {
$term_width = 64; // Arbitrary fall-back term width.
}
$lineCount = 0;
foreach(explode(PHP_EOL, $data) as $line) {
$lineCount += count(str_split($line, $term_width));
}
$magic = '';
// Erasure MAGIC: Clear as many lines as the last output had.
for($i = 0; $i < static::$lastLines; $i++) {
// Return to the beginning of the line
$magic .= "\r";
// Erase to the end of the line
$magic .= "\033[K";
// Move cursor Up a line
$magic .= "\033[1A";
// Return to the beginning of the line
$magic .= "\r";
// Erase to the end of the line
$magic .= "\033[K";
// Return to the beginning of the line
$magic .= "\r";
// Can be consolodated into
// $magic .= "\r\033[K\033[1A\r\033[K\r";
}
static::$lastLines = $lineCount;
return $magic.$data."\n";
}
}
Now you need get output string (implode PHP_EOL), and call:
$text = "{$id} Fake street\nCityville, USA\n12345";
$progressOutputString = ...;
$yourOutputString = $text.PHP_EOL.$progressOutputString;
echo OutputCliHelper::replaceMultiLine($yourOutputString);
For example:
public function handle()
{
$output = new BufferedOutput(); //Symfony
$progress = new ProgressBar($output); //Symfony
$progress->start(10);
for ($i = 0; $i < 10; $i++) {
$progress->advance();
$tmp = explode(PHP_EOL, $output->fetch());
$string = array_pop($tmp);
echo static::replaceMultiLine($string.PHP_EOL.mt_rand());
}
}
Related
I'm trying to achieve a dynamic query with DQL in doctrine. I've checked several post about this subject but all the solutions are static. I want to achieve somethin like this:
$qb->where(
$qb->expr()->orX(
$qb->expr()->like('e.cliente', ':cliente_tag'),
$qb->expr()->like('e.cliente', ':cliente_tag2'),
$qb->expr()->like('e.cliente', ':cliente_tag3')
),
$qb->expr()->orX(
$qb->expr()->like('e.apoderado', ':apoderado_tag'),
$qb->expr()->like('e.apoderado', ':apoderado_tag2'),
$qb->expr()->like('e.apoderado', ':apoderado_tag3')
)
);
but inside a loop like this:
foreach ($options['camposTexto'] as $i => $campoTexto) {
switch ($campoTexto['appliedTo']) {
case 'apoderado': {
$exp = [];
foreach ($campoTexto['tags'] as $tag) {
$exp[] = $qb->expr()->like('e.apoderado', ':apoderado_tag' . $i);
$parameters['apoderado_tag' . $i] = '%' . $tag . '%';
}
if ($isFirst) {
$isFirst = false;
$qb->where($qb->expr()->orX($exp));
} else {
$qb->andWhere($qb->expr()->orX($exp));
}
break;
}
case 'cliente': {
$exp = [];
foreach ($campoTexto['tags'] as $tag) {
$expresiones[] = $qb->expr()->like('e.cliente', ':cliente_tag' . $i);
$parameters['cliente_tag' . $i] = '%' . $tag . '%';
}
if ($isFirst) {
$isFirst = false;
$qb->where($qb->expr()->orX($exp));
} else {
$qb->andWhere($qb->expr()->orX($exp));
}
break;
}
}
}
tags is an array of strings. As you see I passed the array of expresions but doctrine throws me an Exception.
So far so now I have not found any solution to my problem.
Any Idea?
Thanks in advance!
Looking at this post I figured out the solution. It would be something like this:
case 'apoderado': {
$orX = $qb->expr()->orX();
foreach ($campoTexto['tags'] as $y => $tag) {
$orX->add($qb->expr()->like('e.apoderado', $qb->expr()->literal('%' . $tag . '%'))); //<= with literal because I can't set the parameters later in the qb
}
$expresiones[] = $orX;
break;
}
after all the case/break
$andX = $qb->expr()->andX();
$qb->where($andX->addMultiple($expresiones));
return $qb->getQuery()->getResult();
I have a Product Entity. Each product can have 0 or N pictures.
I have a ProductPhoto Entity which has a order property (0 = first).
On a page, I list all the pictures of my current product. I would like to manage pictures order with 2 up/down arrows.
When the user clicks on an arrow, it moves up/down the picture compared to the others.
So, on each arrow, there is a link that corresponds to a route action in my ProductController.
It's not very complicated to update only the order of the picture that moved, but I don't know how to update the order of other pictures in the ArrayCollection...
/**
* Manage picture order
*
* #Route("/products/{id}/photos/{idphoto}/move-{direction}", name="prod_move_photo")
*/
public function movePhotoAction($id, $idphoto, $direction, Request $request) {
$em = $this->getDoctrine()->getManager();
$photo = $em->getRepository('AppBundle:ProductPhoto')->find($idphoto);
if ($direction == 'up') {
$order = $photo->getOrder() - 1;
if ($order >= 0)
$photo->setOrder($order);
else
$photo->setOrder(0);
} elseif ($direction == 'down') {
$order = $photo->getOrder() + 1;
$photo->setOrder($order);
} else {
throw $this->createNotFoundException("The type of ordering '" . $direction . "' doesn't exist.");
}
$em->flush();
// redirection
return $this->redirectToRoute('prod_photos', array('id' => $id));
}
Maybe using PHP uksort() ?
It looks like you want to update order fields in two ProductPhotos moved by each other, right? So please try this:
/**
* Manage picture order
*
* #Route("/products/{id}/photos/{idphoto}/move-{direction}", name="prod_move_photo")
*/
public function movePhotoAction($id, $idphoto, $direction, Request $request) {
$em = $this->getDoctrine()->getManager();
$photo = $em->getRepository('AppBundle:ProductPhoto')->find($idphoto);
$order = $photo->getOrder();
switch($direction) {
case 'up':
$newOrder = ($order >= 1) ? ($order - 1) : (int) 0;
break;
case 'down':
$newOrder = $order + 1;
break;
default:
throw $this->createNotFoundException("The type of ordering '" . $direction . "' doesn't exist.");
}
$anotherPhoto = $em->getRepository('AppBundle:ProductPhoto')->findOneByOrder($newOrder);
$photo->setOrder($newOrder);
$anotherPhoto->setOrder($order);
$em->flush();
// redirection
return $this->redirectToRoute('prod_photos', array('id' => $id));
}
The solution of #Snegirekk works very well (and I use it), but here is the solution I found, if it can help...
/**
* Manage picture order
*
* #Route("/products/{id}/photos/{idphoto}/move-{direction}", name="prod_move_photo")
*/
public function movePhotoAction($id, $idphoto, $direction, Request $request) {
$em = $this->getDoctrine()->getManager();
$photo = $em->getRepository('AppBundle:ProductPhoto')->find($idphoto);
// Current order of the photo
$currentPos = $photo->getOrder();
// Determine new order
if ($direction == 'up') {
$newPos = ($currentPos > 0) ? $currentPos - 1 : 0;
} elseif ($direction == 'down') {
$newPos = $currentPos + 1;
} else {
throw $this->createNotFoundException("The type of ordering '" . $direction . "' doesn't exist.");
}
$product = $em->getRepository('AppBundle:Product')->find($id);
// Get product photos with actual order (moveElement() needs an array)
$photos = $product->getPhotos()->toArray();
// Reorder photos in ArrayCollection
$this->moveElement($photos, $currentPos, $newPos);
// Reorder photos in database (with keys of formatted ArrayCollection)
foreach ($photos as $order => $p) {
$p->setOrder($order);
}
$em->flush();
// redirection
return $this->redirectToRoute('prod_photos', array('id' => $id));
}
/**
* Move an array element to a new index
*
* #param array $array Array of elements to sort
* #param integer $currentPos Current position of the element to move
* #param integer $newPos New position of the element to move
* #return void
*/
public function moveElement(&$array, $currentPos, $newPos) {
$out = array_splice($array, $currentPos, 1);
array_splice($array, $newPos, 0, $out);
}
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]);
}
Apparently the GridFieldExportButton only exports the currently visible data-set (paginated). Is there a way to make it export all the rows from a model?
Or alternatively: Is there a way to show all rows (eg. bypass pagination), so that the user can perform an export after showing all the rows? I don't want to show all rows all the time (which would probably be possible by setting ModelAdmin::set_page_length(<ridiculouslyHighNumber>);) but only on demand.
You can override ModelAdmin::getExportFields() to define the columns you want to export.
The method needs to return an array with column name as the key, and the db field as the value.
For example:
class MyCustomModelAdmin extends ModelAdmin {
....
public function getExportFields() {
return array(
'FirstName' => 'FirstName',
'Surname' => 'Surname',
'Age' => 'Age'
);
}
}
Solved it by creating a custom subclass of the GridFieldExportButton and using this for my models. The key is to use $gridField->getList(); instead of $gridField->getManipulatedList(); in the generateExportFileData method.
Here's the complete class for anybody interested:
class GridFieldExportAllButton extends GridFieldExportButton {
/**
* Generate export fields for CSV.
*
* #param GridField $gridField
* #return array
*/
public function generateExportFileData($gridField) {
$separator = $this->csvSeparator;
$csvColumns = ($this->exportColumns)
? $this->exportColumns
: singleton($gridField->getModelClass())->summaryFields();
$fileData = '';
$columnData = array();
$fieldItems = new ArrayList();
if($this->csvHasHeader) {
$headers = array();
// determine the CSV headers. If a field is callable (e.g. anonymous function) then use the
// source name as the header instead
foreach($csvColumns as $columnSource => $columnHeader) {
$headers[] = (!is_string($columnHeader) && is_callable($columnHeader)) ? $columnSource : $columnHeader;
}
$fileData .= "\"" . implode("\"{$separator}\"", array_values($headers)) . "\"";
$fileData .= "\n";
}
$items = $gridField->getList();
foreach($items as $item) {
$columnData = array();
foreach($csvColumns as $columnSource => $columnHeader) {
if(!is_string($columnHeader) && is_callable($columnHeader)) {
if($item->hasMethod($columnSource)) {
$relObj = $item->{$columnSource}();
} else {
$relObj = $item->relObject($columnSource);
}
$value = $columnHeader($relObj);
} else {
$value = $gridField->getDataFieldValue($item, $columnSource);
}
$value = str_replace(array("\r", "\n"), "\n", $value);
$columnData[] = '"' . str_replace('"', '\"', $value) . '"';
}
$fileData .= implode($separator, $columnData);
$fileData .= "\n";
$item->destroy();
}
return $fileData;
}
}
Thanks for this!
I had to use this for Members GF in Security Admin.
Created an extension for anyone interested.
class SecurityAdminExtension extends Extension{
function updateEditForm($form){
$gf = $form->Fields()->fieldByName('Root.Users.Members');
$gfConfig = $gf->getConfig();
$gfConfig->removeComponentsByType('GridFieldExportButton');
$gfConfig->addComponent(new GridFieldExportAllButton());
}
}
I while back, I created a little plugin to make it easy to export DataObjects to CSV or Excel files.
https://github.com/firebrandhq/excel-export
It comes with a button you can add to a grid field.
It's got a dependency on PHP-Excel.
I've been struggling a while in order to get my primary links to display only the first level entries (roots). I have the following code in my template.php :
I've tried changing the $level variable to 0 but no effect. I don't know where (the hell) to stop the recursion.
function supertheme_navigation_links($menu_name, $level = 2) {
// Don't even bother querying the menu table if no menu is specified.
if (empty($menu_name)) {
return array();
}
// Get the menu hierarchy for the current page.
$tree_page = menu_tree_page_data($menu_name);
// Also get the full menu hierarchy.
$tree_all = menu_tree_all_data($menu_name);
// Go down the active trail until the right level is reached.
while ($level-- > 0 && $tree_page) {
// Loop through the current level's items until we find one that is in trail.
while ($item = array_shift($tree_page)) {
if ($item['link']['in_active_trail']) {
// If the item is in the active trail, we continue in the subtree.
$tree_page = empty($item['below']) ? array() : $item['below'];
break;
}
}
}
return supertheme_navigation_links_level($tree_page, $tree_all);
}
/**
* Helper function for supertheme_navigation_links to recursively create an array of links.
* (Both trees are required in order to include every menu item and active trail info.)
*/
function supertheme_navigation_links_level($tree_page, $tree_all) {
$links = array();
foreach ($tree_all as $key => $item) {
$item_page = $tree_page[$key];
$item_all = $tree_all[$key];
if (!$item_all['link']['hidden']) {
$l = $item_all['link']['localized_options'];
$l['href'] = $item_all['link']['href'];
$l['title'] = $item_all['link']['title'];
if ($item_page['link']['in_active_trail']) {
if (empty($l['attributes']['class'])) {
$l['attributes']['class'] = 'active-trail';
}
else {
$l['attributes']['class'] .= ' active-trail';
}
}
if ($item_all['below']) {
$l['children'] = supertheme_navigation_links_level($item_page['below'], $item_all['below']);
}
// Keyed with unique menu id to generate classes from theme_links().
$links['menu-'. $item_all['link']['mlid']] = $l;
}
}
return $links;
}
/**
* Return a themed set of links. (Extended to support multidimensional arrays of links.)
*/
function supertheme_links($links, $attributes = array('class' => 'links')) {
$output = '';
if (count($links) > 0) {
$output = '<ul'. drupal_attributes($attributes) .'>';
$num_links = count($links);
$i = 1;
foreach ($links as $key => $link) {
$class = $key;
// Add first, last and active classes to the list of links to help out themers.
if ($i == 1) {
$class .= ' first';
}
if ($i == $num_links) {
$class .= ' last';
}
if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page()))) {
$class .= ' active';
}
// Added: if the link has child items, add a haschildren class
if (isset($link['children'])) {
$class .= ' haschildren';
}
$output .= '<li'. drupal_attributes(array('class' => $class)) .'>';
if (isset($link['href'])) {
// Pass in $link as $options, they share the same keys.
$output .= l($link['title'], $link['href'], $link);
}
else if (!empty($link['title'])) {
// Some links are actually not links, but we wrap these in <span> for adding title and class attributes
if (empty($link['html'])) {
$link['title'] = check_plain($link['title']);
}
$span_attributes = '';
if (isset($link['attributes'])) {
$span_attributes = drupal_attributes($link['attributes']);
}
$output .= '<span'. $span_attributes .'>'. $link['title'] .'</span>';
}
// Added: if the link has child items, print them out recursively
if (isset($link['children'])) {
$output .= "\n" . theme('links', $link['children'], array());
}
$i++;
$output .= "</li>\n";
}
$output .= '</ul>';
}
return $output;
}
function supertheme_primary_links() {
return supertheme_navigation_links(variable_get('menu_primary_links_source', 'primary-links'
));
}
3 comments:
Not totally sure what you meant when you said you tried to change $level to 0, but then it will not enter in the loop: while ($level-- > 0 && $tree_page)
To be able to stop a recursion, the recursive function needs to have an argument with the depth. So INSIDE the recursive function you decide when to stop: if ($depth <1) { return; } and everytime you call the recursive funcion you call it with $depth-1
If you want this functionality 'out-of-the-box', try http://drupal.org/project/menu_block , or get some inspiration from its code: (see menu_tree_depth_trim function)
I believe, that if you set your menu to be non-expanded under menu settings that Drupal will handle the rest for you.
Another option would be to use nice menus which mainly is used to show expandable menus, but also can be used to control the level of menu expansion.