Leaderboard
Popular Content
Showing content with the highest reputation on 10/14/2014 in all areas
-
Until I bought Lister Pro, I did not fully realize how much it could increase your productivity. Working with the tree (or using the built-in search engine for quicker access) is intuitive, and I'd say fast enough in most cases, but it's a one-size-fits-all solution. With the tree, there's no easy way to group pages under different parents, or search for pages that fit one or more criteria and view and edit them quickly. Since Lister Pro is a customizable search engine, you can target with precision which criteria you'd like to use to return the pages you'd like to view and/or edit. In a few clicks, I can set up my own results in a easy-to-scan table view. You can save these results, and they show up under the "Pages" dropdown menu for quick access, which for me wasn't clear from Ryan's description of the plugin. This is incredibly powerful. Let's take an example. Let's say I run an Website showing cultural events across the US, organized by cities (New York, Washington, DC, etc.) and categories (Music, Performing arts, etc.). I have various editors. One of them would like to view only the events he's been assigned to, e.g. all music events created this year in Washington, DC. I can easily create a customizable admin view for him: The results look like this (the columns can be adjusted to your needs of course, and the editor can also filter the results even further): The editor has super quick access to his own "admin view" from here (I've called this page "Recent events" in this example): I can go ahead and create various views for each of my editors. If I work alone, I can do the same to gain quick create, view and/or edit access to whatever views I choose. I hope you can see the power of this module, and I'm not even talking about the included actions that let you manipulate the results, such as email users, exporting to CVS (very useful to export results for offline data analysis in Excel), etc.10 points
-
This is just an idea I've been playing with a while: adding support for SOUNDS LIKE operator. I'm not sure how much sense it makes in the larger context, but I'm posting it here nevertheless in case it results in something useful As a bit of a background, in MySQL SOUNDS LIKE is just a shortcut for comparing two strings passed to SOUNDEX() and as Wikipedia kindly explains, "soundex is a phonetic algorithm for indexing names by sound, as pronounced in English". In other words, SOUNDEX() converts a string to an "index" of sorts, and comparing index values of two separate strings answers the question of "do these two sound alike (in English)". SOUNDS LIKE works best with English words, and literally words, since comparing entire sentences is often much less sensible (there's no native "wildcard soundex operator" in MySQL) -- names of people, products, countries, cities, etc. are a good example where one might benefit from SOUNDS LIKE. Cases I've really wished this would've been possible have included especially name and city searches, where people tend to mistype things a lot. Yesterday I decided that this would be a cool experiment, so I applied following hack to the DatabaseQuerySelectFulltext.php and Selector.php, making "€=" the SOUNDS LIKE operator. This is wrong at many levels, but works as a simple proof of concept: diff --git a/wire/core/DatabaseQuerySelectFulltext.php b/wire/core/DatabaseQuerySelectFulltext.php index 9c6064f..421f38a 100644 --- a/wire/core/DatabaseQuerySelectFulltext.php +++ b/wire/core/DatabaseQuerySelectFulltext.php @@ -83,6 +83,12 @@ class DatabaseQuerySelectFulltext extends Wire { $query->where("$tableField LIKE '%$v%'"); // SLOW, but assumed break; + case '€=': + $v = $database->escapeStr($value); + $v = $this->escapeLIKE($v); + $query->where("$tableField SOUNDS LIKE '$v'"); // SLOW, but assumed + break; + case '^=': case '%^=': // match at start using only LIKE (no index) $v = $database->escapeStr($value); diff --git a/wire/core/Selector.php b/wire/core/Selector.php index 31748f9..fccfe8a 100644 --- a/wire/core/Selector.php +++ b/wire/core/Selector.php @@ -204,6 +204,7 @@ abstract class Selector extends WireData { Selectors::addType(SelectorLessThanEqual::getOperator(), 'SelectorLessThanEqual'); Selectors::addType(SelectorContains::getOperator(), 'SelectorContains'); Selectors::addType(SelectorContainsLike::getOperator(), 'SelectorContainsLike'); + Selectors::addType(SelectorSoundsLike::getOperator(), 'SelectorSoundex'); Selectors::addType(SelectorContainsWords::getOperator(), 'SelectorContainsWords'); Selectors::addType(SelectorStarts::getOperator(), 'SelectorStarts'); Selectors::addType(SelectorStartsLike::getOperator(), 'SelectorStartsLike'); @@ -284,6 +285,10 @@ class SelectorContainsLike extends SelectorContains { public static function getOperator() { return '%='; } } +class SelectorSoundsLike extends SelectorContains { + public static function getOperator() { return '€='; } +} + /** * Selector that matches one string value that happens to have all of it's words present in another string value (regardless of individual word location) * Just for fun I've been testing this new operator with a script like this (and using one of the most mistyped names I've seen on this forum as a test subject), to see if it works at all: <?php require 'index.php'; foreach (wire('pages')->find('title€=antti') as $k => $p) { echo $k . ". " . $p->url . "\n"; } // results look something like this: // 0. /anttti/ // 1. /antti/ // 2. /anti/ // 3. /antii/ So, what do you folks think -- is there anything in this that might be worth taking further? Can you think of a better approach to this, such as a custom fieldtype with SOUNDS LIKE support, or something? Anything else?9 points
-
Hello Gabe and welcome, Are we talking about a one single template called material here? Are all of the characteristics usually defined for all of the materials? And the problem is that you don't want to give your client access to the templates and fields of the system? If the answer to all questions is 'yes', then I think approaching the problem with autoloading modules, hooks, PageTables etc is a bit overkill. It is amazingly simple to create a Process-module that you can give the client access to, which simply allows the client to add/edit/remove such fields on the material-template. Here are all the relevant API-methods $template = wire('templates')->get('material'); // Adding a field $field = new Field(); $field->name = "c_durability"; $field->label = "Durability"; $field->type = "Integer"; $field->save(); $template->fields->add($field); $template->save('fields'); // Updating the label $field = wire('fields')->get('c_durability'); $field->label = "Characteristics: Durability"; $field->save(); // Removing the field $field = wire('fields')->get('c_durability'); $fieldGroups = $field->getFieldgroups(); if($fieldGroups->count() > 0) { foreach($fieldGroups as $fieldGroup) { $fieldGroup->remove($field); $fieldGroup->save(); } } wire('fields')->delete($field); Now of course you still need an UI with some confirmation dialogs and validation, but I'm sure you get the idea (if not, ask away). If you really, really want to create a module that does this automatically based on pages, then yes, that can be done with the same methods as well. Such module could be something like this class PageFieldCreator extends WireData implements Module { protected $material_template = "material"; protected $char_template = "characteristic"; protected $template; public static function getModuleInfo() { return Array( 'title' => __('Page Field Creator', __FILE__), 'summary' => __('Demo-module that creates fields from pages'), 'version' => 1, 'singular' => true, 'autoload' => true, ); } public function init() { $this->addHookAfter('Pages::saved', $this, 'saveHook'); } public function saveHook(HookEvent $e) { $page = $e->argument(0); if($page->template->name != $this->char_template) return; if($page->parent->isTrash()) $this->handleDelete($page); else $this->handleSave($page); } protected function handleDelete(Page $page) { $fieldname = $this->getFieldname($page); $field = $this->fields->get($fieldname); if(!$field) return; $fieldGroups = $field->getFieldgroups(); if($fieldGroups->count() > 0) { foreach($fieldGroups as $fieldGroup) { $fieldGroup->remove($field); $fieldGroup->save(); } } $this->fields->delete($field); $this->message(__("Characteristic {$fieldname} deleted")); } protected function handleSave(Page $page) { $fieldname = $this->getFieldname($page); $field = $this->fields->get($fieldname); if(!$field) $this->handleNew($page); else $this->handleUpdate($field,$page); } protected function handleUpdate(Field $field, Page $page) { if(strcmp($field->label, $page->title) == 0) return; $field->label = $page->title; $field->save(); $this->message(__("Characteristic {$field->name} updated")); } protected function handleNew(Page $page) { $field = new Field(); $field->name = $this->getFieldname($page); $field->label = $page->title; $field->type = "Integer"; $field->save(); $tpl = $this->getTemplate(); $tpl->fields->add($field); $tpl->save('fields'); $this->message(__("Characteristic {$field->name} created")); } protected function getFieldname(Page $page) { return "c_".$this->sanitizer->fieldName($page->name); } protected function getTemplate() { if(!isset($this->template)) $this->template = $this->templates->get($this->material_template); return $this->template; } } Such module would create/update/delete a field and update the template called material when a page that has the template characteristic is created/updated/trashed. The demo creates the field using the name of the page prefixed with "c_". While this would technically work, I'd still just create a Process-module for managing them to gain more fine-tuned control. Like I mentioned in the beginning, hooking is also a bit overkill since the fields are rarely modified.9 points
-
I think it would be great to have pluggable selector operators and/or matchers supported directly in the core. If it did, such behavior - and others - could then be easily extended with community modules. For an example, if I preferred an UDF Levenshtein distance calculator instead of SOUNDEX to implement fuzzy searching, I could then do that.8 points
-
Hi Neo, welcome. Your requirements should be no problem with ProcessWire. Even with a limited PHP knowledge, you will get used to the API quite fast. To your questions: - Of course it is possible. You need a basic understanding of PHP (loops,ifelse,variables) but nothing more than a usual template language or the WordPress markup would require. Y - A higher time because you will have to learn some stuff before. Maybe start with a smaller page as you suggested and then move forward. - You could do it this way. Build the HTML/CSS/JS and then replace everything with dynamic content from ProcessWire. Did you look at the Cheatsheet already? And maybe this tutorial is interessting to you. Hopefully someone will give you a more detailed answer, I have to leave now.6 points
-
Yes, I know you can put a module together to achieve something similar, but (1) it's much faster and easier to do it with Lister Pro, (2) it's more flexible, and (3) I do want to support the development of Processwire, which has become an indispensable tool for me, and a pleasure to use. I don't really need other pro modules such as Form Builder (it's not too hard to build forms from the API) o the Pro Fields (regular fields are sufficient most of the time), but they do add simplicity, convenience and enhance productivity -- which is one reason why I bought these other two modules. Another important reason is because I'd like Ryan to keep dedicating as much time as possible to PW development, and these are just small tokens of my gratitude to his dedication, amazing work and constant willingness to help others. And finally, I'll take this opportunity to thank the other forum moderators and contributors who keep this community so alive and friendly, and keep beefing up PW funcionality with new modules. In any case, LostKobrakai, I know you meant to highlight PW's flexibility (you can build your own modules to add funcionality), and not discourage people from buying the Pro modules.6 points
-
Adds twig support for language translator list. To get your `.twig` files listed as well, you need to wrap the phrases like this: {{ __('text to translate', 'filepath/filename relative to site/templates/') }} {{ __('another text to translate', 'home') }} {{ __('one more text to translate', 'partials/header') }} first line - general usage second line - example for a text in site/templates/home.twig third line - example tor a text in site/templates/partials/header.twig5 points
-
Hey DaveP! Correct, it doesn't, but if you take a closer look, I was referring to a MySQL UDF. Stored procedures or post-processing with PHP would be awfully slow in comparison, which is the exact reason that forced me to write an UDF implementation back in the days. I can dig it up if you are interested in the subject. (Compiling and linking UDFs with MySQL isn't really as complex as they make it sound.) On a sidenote, Elasticsearch for an example - which is backed by Lucene - would obviously provide even greater speeds for fuzzy searching. However we were talking about something that can be combined with the native PW-selectors (i.e. something that can be sticked inside the resulting SQL query). The purpose of my example was to promote a generic way for hooking custom selector operators to existing matchers.5 points
-
$out = $this->_("Live long and prosper"); // syntax within a class $out = __("Live long and prosper!"); // syntax outside of a class All the information you need is right here in the docs.5 points
-
Hi Macrura, I've just done something similar to this for my current project. I needed to add data-start-time attributes to every option in a select dropdown so I decided to extend the InputfieldSelectMultiple class to provide a replacement addOption() implementation that added the required data attribute as part of the internal attributes array for each option. Here's what I did... public function addOption($value, $label = null, array $attributes = null) { if ( is_null($value) || (is_string($value) && !strlen($value)) ) { return $this; } if (null === $attributes) { $attributes = array(); } $extra_atts = $this->extendAttributes($value, $label); $attributes = array_merge($attributes, $extra_atts); return parent::addOption($value, $label, $attributes); } public function ___extendAttributes($id, $value) { $atts = array(); /** * Either hook this method to do what you want or implement things directly if this * is the only use of this Inputfield. * For your example you'd grab the fields you want from your page and put into data * attributes... */ //$page = wire()->pages->get($id); //$atts['data-description'] = $page->description; return $atts; } I've updated the example above to output data-description. Hope that helps you with some ideas!4 points
-
Maintenance Update @jlahijani, the bug is fixed. Issue was exact as you described ! Added the template name to the JSON object ( for ProcessPageEdit process) Made the 'Include theme based files' conditional (PW >= 2.5.0) Fixed the empty Theme based files field notes (config settings) Fixed a view typos4 points
-
Thought some of you might find this interesting. Free and open source, and they will be providing migration scripts. https://www.kickstarter.com/projects/1221714515/flarum-forums-reimagined3 points
-
Picking up on sforsman's idea, have a look at Ryan's Event Fieldtype, especially how he stores timestamps. Pretty straightforward: https://processwire.com/talk/topic/5040-events-fieldtype-inputfield-how-to-make-a-table-fieldtypeinputfield/3 points
-
Just create a custom Fieldtype (module) that stores the timestamps properly in a table designed for the purpose. Saving and querying them would then be blazing fast and no need for dirty tricks like comma delimited values on a blob field (which wouldn't obviously be very flexible to query on - especially if you need comparison operators - nor would it be very flexible to modify the values). If you need help implementing such Fieldtype, just let me know.3 points
-
Maybe you could add a check if InputfieldColorpicker is installed and in that case use this instead of InputfieldText.3 points
-
ProcessWire doesn't store metadata of changes, it only stores the new values and that's it. If you want to get this functionality, you would need to build a small module which hooks into page::save and saves the date to a hidden date field, if the checkbox got checked. This way you can store and sort by date and status.3 points
-
You don't need a module for it, instead of using the page tables autonaming function, just use the template specific one, which you can find in the template settings. The only backdraw would be, that all pages with this template get autonaming, instead of only the ones, created in a pagetable.3 points
-
If you're comfortable building markup, then you should absolutely build your markup first and then convert it into CMS templates. (This is the nice thing about markup-agnostic CMS's like ProcessWire... they do not dictate any sort of structure on you). The general idea is that you will be creating "templates" for your site... so go through your design and figure out which pages are basically the same as each other and which are different. Generally, there will be 1 template for the home page, a few templates for generic interior pages (e.g. "full width", "left sidebar", "3 column"... totally depends on your specific design), then a template for special pages that are related to specific "things"... like individual news articles or individual blog posts or individual portfolio pieces or individual employees, etc. Each "template" consists of your overall site markup, interspersed with editable fields (content areas) -- in general, wherever you see "Lorem Ipsum" text in your design, you'd replace that with a field. Then you will also have some places where you have a little php code to output lists of pages (e.g. "list the 3 most recent news articles on the home page) -- but don't worry, it's not much php code and ProcessWire is very logical and consistent with how you interact with it so it is easier to understand than other systems (in my experience). To speak to your specific requirements: Brochure style / informational sites: Absolutely! This is where CMS's other than Wordpress really shine, because you don't need to shoehorn a brochure site structure into a blogging system. Multiple, editable content areas per page: Absolutely! This is the foundation of ProcessWire... it is entirely up to you to define which fields should be in which page templates (if you've used the "Advanced Custom Fields" plugin in Wordpress, you can think of ProcessWire being an entire CMS based around that concept). Portfolio functionality: Absolutely! Create a template for "portfolio" and set up the fields you want for it (a text field for title, a textarea/CKEditor field for the description, an images field for the photos, see below for "categories"). Latest portfolio items are displayed on the front page (e.g. latest 6): Absolutely! In your "home.php" template file, put some code like this: <?php foreach ($pages->find("template=portfolio,sort=-date,limit=6") as $page) { ?> <a href="<?=$page->url?>"><?=$page->title?></a> <?php } ?> See here for more about querying pages: https://processwire.com/api/variables/page/ Editable slideshow on the front page (text and image): Yep! Create an "image" field, name it "home_slideshow" (for example), and assign it to your "home" template. Then in the home.php template file, put some code like this: <div class="your-slideshow-container-class"> <?php foreach($page->home_slideshow as $image) { ?> <div class="your-slide-class"> <img src="<?=$image->url?>" alt="<?=$image->description?>"> <p><?=$image->description?></p> </div> <?php } ?> </div> <script> //Your slideshow script here... (or include it in your html <head> or down above the closing </body> tag... whatever) </script> See here for more about how to interact with image fields: https://processwire.com/api/fieldtypes/images/ (Also, if you want more complex content in each slideshow slide [more than just an image and a description], you could make it a "Repeater" field and include image, title, description, caption, link to page, etc etc.... as many sub-fields as you need for each slide) News / Blog functionality: Sure... this is where (for better or worse) you will be doing more manual work than in Wordpress. News is rather easy... just create a template for "news", and create a top-level "news" page on your site and have your site editors add new news pages under there. Use the processwire query interface to list out news pages on the top-level index page (like how we did with portfolios on the home page). For "Blog Functionality", what exactly do you mean? Check out Ryan's "blog profile" for a lot of examples on how to structure this kind of thing: http://modules.processwire.com/modules/blog-profile/ Latest news / blog excerpts are displayed on the front page: Oh yeah! Same idea as the portfolios (just change the "template" you're retrieving to "blog" or "news", if that's what you named those templates). Custom contact forms: This one is tricky. There is a paid plugin for forms: http://modules.processwire.com/modules/form-builder/ ... I've not used it myself, I'm sure it's nice (although it seems like it might be difficult to skin it with your own markup... not sure about that though because I haven't used it yet). Doing forms yourself could be possible with more work, for example: https://processwire.com/talk/topic/2089-create-simple-forms-using-api/ . In general, this seems like a problem that has not been solved perfectly yet (outside of the paid plugin of course, which is surely worth the money if building a site for a paying client, but maybe not so much when just experimenting with a system as you and I are right now). One last note about categories and tags: This is a bit different than other systems. It seems that in ProcessWire, when you need custom stuff, you implement it as templates and pages. So you create hidden pages with sub-pages under them, and these act as sort of database records (kind of, I think?). Here is a great explanation of how to create a tags system using this method: https://processwire.com/talk/topic/3942-help-for-creating-tags-please/?p=38605 ...and also check out the aforementioned "blog profile" to see a working example of how this is implemented: http://modules.processwire.com/modules/blog-profile/ . Hope that helps! (Also note that I'm relatively new to ProcessWire myself... although I have a ton of experience with other CMS's... so hopefully others will pipe in with more detailed or accurate info if I've missed anything or gotten anything wrong).2 points
-
Results for saving 581 timestamps: 1. save as comma delimited values in a textarea field: 0.0258 s 2. save each in a page (create and sve 581 pages): 18.1477 s So clearly, option 1 would be the way to go for saving timestamps. Next I will benchmark search times and add them to this post. Search results 1.Searching through 5 pages to find a timestamp inside a comma delimited list (about 600 timestamps) with this query. 5matches. $ads = $pages->find("template=advertisement, ad_server={$serverID}, ad_publish_at*=$ts"); 0.0264 s 2. Searching through 2600 pages to find the timestamp. with this query. 5 matches. $ads = $pages->find("template=publishing_schedule, publish_at=$ts"); 0.0200 s I will have to create some more ads to see how option 1 performs when more pages need to be searched. But ATM it looks like I will go with option 1 because it is significantly faster when saving timestamps.2 points
-
PW doesn't store the date/time a field is changed. I think the easiest approach might be to add a hidden field called date_terminated. Then add a hook on page save that sets the date if the checkbox field has been checked. Place this is your admin.php file. wire()->addHookAfter('Pages::saveReady', function(HookEvent $event) { $page = $event->arguments("page"); if($page->user_terminated == 1 && $page->date_terminated == '') { $page->date_terminated = time(); } }); That is a little rough but will hopefully get you started. You should add an if statement to limit this to just the relevant template so it is not run on all pages. I am sure other improvements could also be made. PS Apparently I was writing this while LostKobrakai was responding2 points
-
EDIT: Attention, changed the code because have overlooked the $params variable! Tom, you can get the information from PiM with this code: // create 4 variables that need to be passed by reference and set them initially to 0; $x = $y = $w = $h = 0; // create an empty array variable named params, it is needed to pass by reference to the method $params = array(); // now call the public static methode fileThumbnailModuleCoordsRead, what additionally to the above variables needs the pageimage and the name of your thumb, (prefix) if (ImageManipulator::fileThumbnailModuleCoordsRead( $pageimage, $x, $y, $w, $h, $params, 'thumb') { // it returns true if could get permanent settings, and now you can do what you want with it ... // does this work:?? $large = $pageimage->size($w, $h, array("quality" => 25, "cropping" => "{$x},{$y}" )); } else { // to bad, no permanent settings available ... do you have a fallback? } ----- If you want to use this with PW 2.5.+, you can use a new option together with ImageSizer. It is called cropExtra and takes an array with the 4 dimensions for a crop. You can use it like this: $x = $y = $w = $h = 0; $params = array(); if (ImageManipulator::fileThumbnailModuleCoordsRead( $image, $x, $y, $w, $h, $params, 'thumb') { $large = $image->size($w, $h, array('quality' => 25, 'cropExtra' => array($x, $y, $w +1, $h))); // * } * we need the 'w +1' actually, because there is an issue with this new functionallity in ImageSizer2 points
-
https://github.com/ryancramerdesign/ProcessWire/issues/663 At least it no longer breaks the page Hopefully Ryan will deal with the rest of it sometime soon.2 points
-
@kongondo I don't want do discurage buying pro modules, especially if you earn money with it. But for private/small websites, this can be an option, which I wanted to highlight. There are also other reasons to buy the pro version.2 points
-
mySQL fulltext searches 'WITH QUERY EXPANSION' are really fun/rad/useful. (@sforsman I didn't think mySQL had native support for Levenshtein distances without adding modules/triggers/routines etc? PHP does, so post-processing search results that way works.)2 points
-
I found that downloading ckeditor and implementing it was way easyer, Download from here: http://ckeditor.com/download Include ckeditor.js on website along with jquery plugin. <?php echo '<script src="'.$config->urls->templates.'js/ckeditor/ckeditor.js"></script>'; echo '<script src="'.$config->urls->templates.'js/ckeditor/adapters/jquery.js"></script>'; ?> enable plugin for textarea with id selector in your js file function enableCkeditor() { $( 'textarea#editor1' ).ckeditor(); }; and woop it works http://hate.it/add-hate/ you need to be loged in to see form tho . Hope it helps someone2 points
-
@LostKobrakai I've fiddled something for you. If you got any questions concerning the code just let me know. @Martijn: If you're interested in digging deeper into javascript patterns this is a "must read": http://addyosmani.com/resources/essentialjsdesignpatterns/book/2 points
-
I have some questions but first I'd like you to enable debug-mode in your site/config.php ($config->debug), then try again and post us the result. If you still get the same page (which I'm hoping you don't), could you answer these questions Are you able to access the web server's error log? If yes, please post the error here Do you have something in site/assets/logs/error.txt? If yes, please post the error here Do you get that on the frontend of your site, or when trying to access the admin-site? What modules do you have under site/modules? Could you post us the result of <?php print_r(get_loaded_extensions()); ?> (or `php -m` if you can access a shell)?2 points
-
The reason this won't work is that you're using "this" wrong. "This" in your case isn't referring to your namespace but to document/global. You have to either wrap your code inside a self executing function or create a constructor and instanciate it with the new keyword. Have a look at this link: http://toddmotto.com/understanding-the-this-keyword-in-javascript/2 points
-
Not sure if this has been brought up before, but it would be great if the Page field could be configured to allow you to edit any page (in a popup window) that you have selected when using the asmselect. It would work just like the new PageTable field does, except allow you to select and edit existing pages instead of new ones. There are several use cases I've found myself in where this would have been a really handy feature.1 point
-
Hey, I think it would be a good addition if languages would be installable via "Modules" because they're in the modules directory anyway and you can even download then over the "New" tab. But afterwards they won't appear in the languages section. Maybe the whole backend translation stuff should be modularized anyway. But not sure if that's possible.1 point
-
Here's how it worked for me in CKEditor (in case you persist on this route - but I would go for Hanna Code as Adrian suggested. As usual, the "culprit" is our friend HTML Purifier (read from here up to and including Ryan's comment here about its pros and cons before deciding whether to implement #5 below!) For iframes, the Extra allowed content seems to have no effect - btw, the correct syntax here is for example, div(*) not div[*] Add 'Iframe' (note the spelling) to your CKEditor Toolbar Leave ACF on No need to switch the field's "Content Type" from "Markup/HTML" to "Unknown" Turn HTML Purifier off (gasp! ) Enjoy your embedded video1 point
-
1 point
-
Thanks for the link to the debug class. I found two posts that show how to use the Debug::Timer() method here and here. Will use those examples in my code for benchmarking my searches and get back with results here.1 point
-
No problem at all! I'm just glad if it was any help for you. PS. Just let us know if you need any help with the actual Process-module - I can also write an example of those if you like (since they work slightly different compared to the one I posted). However this is where you should probably start if you are going to experiment yourself: https://github.com/ryancramerdesign/ProcessHello.1 point
-
1 point
-
Thanks SO much sforsman! I really appreciate you taking the time to put this together. The process module definitely is the quick and easy way to get this done. I'm going to play around with the module though just to get a better grasp on how modules work. Thanks again to everyone help with this!1 point
-
Somehow I don't like the way they insult the entirety of existing forum software ("it sucks") in the second sentence of a rather unprofessional kickstarter-video Which is a shame, because i really like Laravel, and the whole API-thing ^^1 point
-
I think there may be a small bug with this plugin. Let's say you do a fresh install of PW (and use the default admin theme). Then you install Admin Custom Files and check "Include theme based files" and set the folder as "AdminCustomFiles". Then uou create the folder "AdminCustomFiles" in your /site/ folder and then create a css file called AdminThemeDefault.css, and let's say you modify it so #masthead{background:red !important} It doesn't load the file. I think I know the reason... When using the default admin theme without any other admin theme installed, PW doesn't allow the option to switch admin themes per user. As a result, even though you are using the default admin theme, it's not specifically SET. Therefore, to work around this, you have to install another admin theme (like Reno), which then allows you to choose which admin theme a user can have when editing a user. After specifically setting the admin theme to default, only then does the "AdminThemeDefault.css" load. Let me know if what I stated is clear, repeatable and/or if you need a video demonstration. Jonathan1 point
-
SomeDay Tool I'm working on a series of restricted collaborative websites that are geared towards me working with my clients. It's a mashup of CRM, Project/Task Management, Tracking, Documentation and Configuration Control features. Everything is controlled by PW Roles. It's a tool that allows me to have a great deal of transparency with those I work with. The objective is to easily know the who, what, when, where and how of whatever is being worked on. I call it a Collaborative Resource Management System (CRMS) which has the nickname of SomeDay. I currently have one of these websites for each of my important clients and an additional one to keep track of my internal work. They all use the inherent native power of PW to produce specialized status reports. The websites intentionally incorporates the front-end and back-end of the PW platform. There is a lot of information stored within this type of website, what I like to call an Information Portal. You are able to easily query a status and get a good view of what's going on. This is a long-term ongoing project and I will update this post as more features/capabilities are added. Meanwhile, here are a few pictures of the CRMS Tool: Figure 1-3 (The SomeDay Tool for one of my clients) Figure 1 (Login courtesy of Adrian's Protected Mode Module) Figure 2, Homepage is a summary of all information Figure 3, Each item can be clicked on to get to detailed information Figures 4-7 (My internal SomeDay Tool website) Figure 4, (Login) Figure 5, Homepage Summary for my internal work Figure 6 , Homepage Summary Figure 7, FlagPages showing Bookmarks Figures 8-11 (Looking at a Task) Figure 8, Front-end view Figure 9, Back-end view (editing) Figure 10, Place for adding Priority or Statuses Figure 11, Private Tab only available to selected PW Roles Figures 12 and 13 (Site Structure) Figure 12, CRMS structural flow Figure 13, Tasks and Sub-Tasks can be added to Projects Figures 14-16 (Customized Reports) Figure 14, Review all items based on what type of work it is Figure 15, Review items based on their status Figure 16, Listing of all items with a status category of Active That's it for now.1 point
-
I'm pretty sure you could write a module that would do this, and would dynamically set the name and title according to the template and your rules... so you wouldn't use the auto page name feature, but would write your own, sort of like this: https://processwire.com/talk/topic/1648-ok-to-change-page-name-path-after-save/?p=152321 point
-
Love this! And really nice video (I want a drawing of me now, too ^^). I donated 5€ and hope to see my name in your cafe the next time I go to porto1 point
-
Wow ok, thanks for the encouraging comments guys I've worked with PW for quite a while, surfing the core silently in the background (I'm a little shy like that). But one day apeisa gave me a proper kick in the knee and told me to grow a pair - I did, ergo, here I am. I must say you have a really good thing going on here and I'm very happy to be part of it!1 point
-
It would be great to automatically add a tab to all templates (like you can do with fields). This is handy for the SEO tab for example.1 point
-
Hi Beluga, Thanks for sharing that link. Very interesting. I've made a basic PM tool using PW, I'll be expanding it little more for a client's project. I'll post it here once it's ready. For now it's just a simple task management system. If anyone's interested, feel free to contact me, I've a demo online which I'm using to manage my company's projects. Have a great day!1 point
-
I think these guys covered it really well, but here's an overly simple alternate example in case it helps. class YourModule extends WireData implements Module, ConfigurableModule { public static function getModuleInfo() { return array('title' => 'Your Module', 'version' => 1); } const defaultValue = 'renobird'; public function __construct() { $this->set('yourname', self::defaultValue); // set default value in construct } public function init() { // while you need this function here, you don't have to do anything with it // note that $this->yourname will already be populated with the configured // value (if different from default) and ready to use if you want it } public function execute() { // will return configured value, or 'renobird' if not yet configured return $this->yourname; } public static function getModuleConfigInputfields(array $data) { // if yourname isn't yet in $data, put our default value in there if(!isset($data['yourname'])) $data['yourname'] = self::defaultValue; $form = new InputfieldWrapper(); $f = wire('modules')->get('InputfieldText'); $f->name = 'yourname'; $f->label = 'Enter your name'; $f->value = $data['yourname']; $form->add($f); return $form; } } If you have the need to manage several configuration values, then you can save yourself some time by using a static array of default values like in the examples before mine. Also this: foreach(self::$configDefaults as $key => $value) { if(!isset($data[$key]) || $data[$key]=='') $data[$key] = $value; } could also be written as this: $data = array_merge($data, self::$configDefaults);1 point
-
And add the following at the top of your static public function getModuleConfigInputfields(array $data) {: foreach(self::$configDefaults as $key => $value) { if(!isset($data[$key]) || $data[$key]=='') $data[$key] = $value; } Be careful here, only keys that are not set in the $data array should inherit the default value. Otherwise if a config value is empty, you'd overwrite it with the default value all the time, if the default value ist not empty. Suppose a checkbox with possible values 1 and '' default=1 (checked) User opens Module Config and does uncheck the box After reloading, the box would be displayed as checked Setting the default data in the constructor makes sure that you'll have every config key available as property (->key). The workflow here is: Pw creates your module --> calls constructor Pw does set all the stored config values to the module Pw calls init() method As you can see, modified default config values will get overridden in step 2. Default config values that are not yet stored in the database are set by you in the constructor.1 point
-
Hey reno, You can set your defaults like this. Just put it before your init(): protected static $configDefaults = array( "defaultSelector" => "default value here" ); /** * Data as used by the get/set functions * */ protected $data = array(); Add this line to your inputfield: $field->attr('value', $data["defaultSelector"]); And add the following at the top of your static public function getModuleConfigInputfields(array $data) {: foreach(self::$configDefaults as $key => $value) { if(!isset($data[$key]) || $data[$key]=='') $data[$key] = $value; } You can then access this throughout your module using: $this->defaultSelector; At least that is how I have been doing it - maybe someone else has something cleaner Does that make sense? PS, Sorry for all the consecutive edits. I really shouldn't try to offer device when I am busy with other things and rushing EDIT Again - I just read that tutorial you pointed to and I see if uses: public function __construct() to set the default values. I haven't used that before. I guess I started by copying from other modules and never came across that approach. I guess there are a few different options, but construct does seem cleaner.1 point
-
As you wait for other responses, have a look at the following examples from AdminSaveActions module. It looks really straightforward. Thanks for asking this question...I'll be needing this soon . Seems, you set the defaults in one function, then build the form/fields in another function using the defaults previously set...or something along those lines AdminSaveActions module static public function getDefaultConfig() {} https://github.com/niklaka/AdminSaveActions/blob/master/AdminSaveActions.module#L51 public function __construct() {} https://github.com/niklaka/AdminSaveActions/blob/master/AdminSaveActions.module#L105 public static function getModuleConfigInputfields(array $data) {} https://github.com/niklaka/AdminSaveActions/blob/master/AdminSaveActions.module#L390 MaintenanceMode module static public function getDefaultData() {} https://github.com/Notanotherdotcom/Maintenance-Mode/blob/master/MaintenanceMode.module#L25 public function __construct() {} https://github.com/Notanotherdotcom/Maintenance-Mode/blob/master/MaintenanceMode.module#L38 static public function getModuleConfigInputfields(array $data) {} https://github.com/Notanotherdotcom/Maintenance-Mode/blob/master/MaintenanceMode.module#L52 ProcessTrashman - Last one, static public function getDefaultData() {} https://github.com/apeisa/Trashman/blob/master/ProcessTrashman.module#L42 public function __construct() {} https://github.com/apeisa/Trashman/blob/master/ProcessTrashman.module#L54 static public function getModuleConfigInputfields(array $data) {} https://github.com/apeisa/Trashman/blob/master/ProcessTrashman.module#L255 MarkupRSS module - Sorry, the very last one (this one has more detailed defaults and some notes) protected static $defaultConfigData = array() https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/modules/Markup/MarkupRSS.module#L32 public function __construct() {} https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/modules/Markup/MarkupRSS.module#L67 static public function getModuleConfigInputfields(array $data) {} https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/modules/Markup/MarkupRSS.module#L219 Tutorial on forum - Create your first Module with configuration settings and a overview page: http://processwire.com/talk/topic/2318-create-your-first-module-with-configuration-settings-and-a-overview-page/ However, this Tut does not set defaults, which I think I have read somewhere is good practice to do? OK, sorry, am overdoing it but this will help me later too ;-) Edit: corrected wrong links + added a tutorial from the forums Edit 2: Something has been eating my links! Anyway, added methods/properties names...1 point
-
You MadeMyDay I followed your advice though I changed some a bit. Here's what I did in details: ~ created a template called "tags" without a (php) file ~ created a template called "tag" without a (php) file * "tags" template -> family: set "Allowed template(s) for children" as "tag" * "tag" template -> family: set "Allowed template(s) for parents" as "tags" ~ created a "tags" field with Type as "Page"; Input -> "Parent of selectable page(s)" as "Tags" (page), "Input field type" -> AsmSelect; "Allow new pages to be created from field?" checked. Set "template for selectable pages" as "tag". ~ created a template called "post" and added "tags" field in addition to its default "title" and "body" fields ~ created a hidden page called "tags" with template "tags" ~ created children of "tags" page as tags with a template "tag" ~ created tags.php file and put the following code in it to render the tags list: <?php include_once("./head.inc"); $tags = $pages->get("/tags/")->children->find("sort=title"); echo "<ul>"; foreach($tags as $tag){ //iterate over each tag echo "<li><a href='{$tag->url}'>{$tag->title}</a></li>"; // and generate link to that page } echo "</ul>"; include("./foot.inc"); ?> ~ created tag.php and put the following code in it to get the list of posts related to the selected tag: $thisTag = $page->title; $tagPages = $pages->find("tags.title=$thisTag"); foreach ($tagPages as $tp){ // iterate over the pages with that tag echo " <div class='teaser'> <h3>{$tp->title}</h3> <p>{$tp->summary}</p> <a href='{$tp->url}'>Read more...</a> </div>"; } include("./foot.inc"); ?> Thank you very much again MadeMyDay and please let me know if there is anything else to improve or fix.1 point
-
I recently had to setup front-end system to handle logins, password resets and changing passwords, so here's about how it was done. This should be functional code, but consider it pseudocode as you may need to make minor adjustments here and there. Please let me know if anything that doesn't compile and I'll correct it here. The template approach used here is the one I most often use, which is that the templates may generate output, but not echo it. Instead, they stuff any generated output into a variable ($page->body in this case). Then the main.php template is included at the end, and it handles sending the output. This 'main' template approach is preferable to separate head/foot includes when dealing with login stuff, because we can start sessions and do redirects before any output is actually sent. For a simple example of a main template, see the end of this post. 1. In Admin > Setup > Fields, create a new text field called 'tmp_pass' and add it to the 'user' template. This will enable us to keep track of a temporary, randomly generated password for the user, when they request a password reset. 2a. Create a new template file called reset-pass.php that has the following: /site/templates/reset-pass.php $showForm = true; $email = $sanitizer->email($input->post->email); if($email) { $u = $users->get("email=$email"); if($u->id) { // generate a random, temporary password $pass = ''; $chars = 'abcdefghjkmnopqrstuvwxyz23456789'; // add more as you see fit $length = mt_rand(9,12); // password between 9 and 12 characters for($n = 0; $n < $length; $n++) $pass .= $chars[mt_rand(0, strlen($chars)-1)]; $u->of(false); $u->tmp_pass = $pass; // populate a temporary pass to their profile $u->save(); $u->of(true); $message = "Your temporary password on our web site is: $pass\n"; $message .= "Please change it after you login."; mail($u->email, "Password reset", $message, "From: noreply@{$config->httpHost}"); $page->body = "<p>An email has been dispatched to you with further instructions.</p>"; $showForm = false; } else { $page->body = "<p>Sorry, account doesn't exist or doesn't have an email.</p>"; } } if($showForm) $page->body .= " <h2>Reset your password</h2> <form action='./' method='post'> <label>E-Mail <input type='email' name='email'></label> <input type='submit'> </form> "; // include the main HTML/markup template that outputs at least $page->body in an HTML document include('./main.php'); 2b. Create a page called /reset-pass/ that uses the above template. 3a. Create a login.php template. This is identical to other examples you may have seen, but with one major difference: it supports our password reset capability, where the user may login with a temporary password, when present. When successfully logging in with tmp_pass, the real password is changed to tmp_pass. Upon any successful authentication tmp_pass is cleared out for security. /site/templates/login.php if($user->isLoggedin()) $session->redirect('/profile/'); if($input->post->username && $input->post->pass) { $username = $sanitizer->username($input->post->username); $pass = $input->post->pass; $u = $users->get($username); if($u->id && $u->tmp_pass && $u->tmp_pass === $pass) { // user logging in with tmp_pass, so change it to be their real pass $u->of(false); $u->pass = $u->tmp_pass; $u->save(); $u->of(true); } $u = $session->login($username, $pass); if($u) { // user is logged in, get rid of tmp_pass $u->of(false); $u->tmp_pass = ''; $u->save(); // now redirect to the profile edit page $session->redirect('/profile/'); } } // present the login form $headline = $input->post->username ? "Login failed" : "Please login"; $page->body = " <h2>$headline</h2> <form action='./' method='post'> <p> <label>Username <input type='text' name='username'></label> <label>Password <input type='password' name='pass'></label> </p> <input type='submit'> </form> <p><a href='/reset-pass/'>Forgot your password?</a></p> "; include("./main.php"); // main markup template 3b. Create a /login/ page that uses the above template. 4a. Build a profile editing template that at least lets them change their password (but take it further if you want): /site/templates/profile.php // if user isn't logged in, then we pretend this page doesn't exist if(!$user->isLoggedin()) throw new Wire404Exception(); // check if they submitted a password change $pass = $input->post->pass; if($pass) { if(strlen($pass) < 6) { $page->body .= "<p>New password must be 6+ characters</p>"; } else if($pass !== $input->post->pass_confirm) { $page->body .= "<p>Passwords do not match</p>"; } else { $user->of(false); $user->pass = $pass; $user->save(); $user->of(true); $page->body .= "<p>Your password has been changed.</p>"; } } // display a password change form $page->body .= " <h2>Change password</h2> <form action='./' method='post'> <p> <label>New Password <input type='password' name='pass'></label><br> <label>New Password (confirm) <input type='password' name='pass_confirm'></label> </p> <input type='submit'> </form> <p><a href='/logout/'>Logout</a></p> "; include("./main.php"); 4b. Create a page called /profile/ that uses the template above. 5. Just to be complete, make a logout.php template and create a page called /logout/ that uses it. /site/templates/logout.php if($user->isLoggedin()) $session->logout(); $session->redirect('/'); 6. The above templates include main.php at the end. This should just be an HTML document that outputs your site's markup, like a separate head.inc or foot.inc would do, except that it's all in one file and called after the output is generated, and we leave the job of sending the output to main.php. An example of the simplest possible main.php would be: /site/templates/main.php <html> <head> <title><?=$page->title?></title> </head> <body> <?=$page->body?> </body> </html>1 point