Leaderboard


Popular Content

Showing content with the highest reputation since 01/09/2012 in all areas

  1. 68 points
    Here's a video of a module we're working on that I thought you guys might like. The module, Lister, provides a different type of Page List than the tree that you usually interact with in ProcessWire. It gives you a table of pages with customizable columns, filters and actions. Rather than try to explain what it does, I figured I'd show you. This module also uses a new (soon to be released) Inputfield invented by Apeisa, developed by me, and sponsored by Avoine, called InputfieldSelector – it's what you see on the configuration screen as well as the Filters tab. I recommend bumping up the size/quality to 720p so that you can properly see everything. The video has no sound... I tried to do one with narration, but that didn't work out.
  2. 57 points
    Since you guys asked for it, I'll take a stab at a case study on the development process. Most of the development was done in about a week and a half. I started with the basic profile, but it ended up being something somewhat similar to the Blog profile in terms of how it's structured. Below I'll cover some details on the biggest parts of the project, which included data conversion, the template structure, the front-end development and anything else I can think of. Data Conversion from WordPress to ProcessWire One of the larger parts of the project was converting all of the data over from WordPress to ProcessWire. I wrote a conversion script so that we could re-import as many times as needed since new stories get added to cmscritic.com almost daily. In order to get the data out of WordPress, I queried the WordPress database directly (my local copy of it anyway) to extract what we needed from the tables wp_posts for the blog posts and pages, and then wp_terms, wp_term_relationships, and wp_term_taxonomy for the topics and tags. WordPress stores its TinyMCE text in a state that is something in between text and HTML, with the most obvious thing being that there are no <p> tags present in the wp_posts database. Rather than trying to figure out the full methodology behind that, I just included WP's wp-formatting.php file and ran the wpautop() function on the body text before inserting into ProcessWire. I know a lot of people have bad things to say about WordPress's architecture, but I must admit that the fact that I can just include a single file from WordPress's core without worrying about any other dependencies was a nice situation, at least in this case. In order to keep track of the WordPress pages imported into ProcessWire through repeat imports, I kept a "wpid" field in ProcessWire. That just held the WordPress post ID from the wp_posts table. That way, when importing, I could very easily tell if we needed to create a new page or modify an existing one. Another factor that had to be considered during import was that the site used a lot of "Hana code", which looked like [hana-code-insert name="something" /]. I solved this by making our own version of the Hanna code module, which was posted earlier this week. Here's an abbreviated look at how to import posts from WordPress to ProcessWire: $wpdb = new PDO("mysql:dbname=wp_cmscritic;host=localhost", "root", "root", array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'")); $posts = wire('pages')->get('/posts/'); $sql = " SELECT * FROM wp_posts WHERE post_type='post' AND post_status='publish' ORDER BY post_date "; $query = $wpdb->prepare($sql); $query->execute(); while($row = $query->fetch(PDO::FETCH_ASSOC)) { $post = $posts->child("wpid=$row[ID]"); // do we already have this post? if(!$post->id) { // create a new post $post = new Page(); $post->template = 'post'; $post->parent = $posts; echo "Creating new post...\n"; } $post->of(false); $post->name = wire('sanitizer')->pageName($row['post_name']); $post->title = $row['post_title']; $post->date = $row['post_date']; $post->summary = $row['post_excerpt']; $post->wpid = $row['ID']; // assign the bodycopy after adding <p> tags // the wpautop() function is from WordPress /wp-includes/wp-formatting.php $post->body = wpautop($row['post_content']); $post->save(); echo "Saved post: $post->path\n"; } What I've left out here is the importing of images, topics, tags, and setting the correct authors for each post. If anyone is interested, I'll be happy to go more in depth on that, but didn't want to overwhelm this message with code. Template File Structure This site makes use of the $config->prependTemplateFile to automatically include the file _init.php before rendering a template file, and $config->appendTemplateFile to automatically include the file _main.php after. So the /site/config.php has this: $config->prependTemplateFile = '_init.php'; $config->appendTemplateFile = '_main.php'; You may recognize this as being the same setup from the Skyscrapers profile. The _init.php includes files containing functions we want to be available to all of our templates, and set default values for the regions we populate: /site/templates/_init.php /** * Include function and hook definition files * */ require_once("./includes/render.php"); require_once("./includes/hooks.php"); /** * Initialize variables populated by templates that get output in _main.php * */ $browserTitle = $page->get('browser_title|title'); $body = "<h1>" . $page->get('headline|title') . "</h1>" . $page->body; $side = ''; $renderMain = true; // whether to include the _main.php file The includes/render.php file that is included above includes several functions for generating markup of navigation and post summaries, or any other shared markup generation functions. Examples are renderPost(), renderNav(), renderTags(). This is similar to the blog.inc file from the Blog profile except that I'm letting these functions generate and return their own markup rather than splitting them into separate view files. I personally find this easier to maintain even if it's not as MVC. The includes/hooks.php sets up any hooks I want to be present for all of my templates. I could have also done this with an autoload module, but found this to just be a little simpler since my hooks were only needed on the front-end. The main hook of interest is one that makes all posts look like they live off the root "/" level rather than "/posts/" (where they actually live). This was in order to keep consistency with the URLs as they were in WordPress, so that the new site would have all the same URL as the old site, without the need for 301 redirects. /site/templates/includes/hooks.php /** * This hook modifies the default behavior of the Page::path function (and thereby Page::url) * * The primary purpose is to redefine blog posts to be accessed at a URL off the root level * rather than under /posts/ (where they actually live). * */ wire()->addHookBefore('Page::path', function($event) { $page = $event->object; if($page->template == 'post') { // ensure that pages with template 'post' live off the root rather than '/posts/' $event->replace = true; $event->return = "/$page->name/"; } }); Our /site/templates/_main.php contains the entire markup for the overall template used site wide, from <html> to </html>. It outputs those variables we defined in _init.php in the right places. For example, $body gets output in the <div id='bodycopy'>, $side gets output in the right <aside>, and $browserTitle gets output in the <title> tag. /site/templates/_main.php <?php if($renderMain): ?> <html> <head> <title><?=$browserTitle?></title> </head> <body> <div id='masthead'> // ... </div> <div id='content'> <div id='bodycopy'><?=$body?></div> <aside id='sidebar'><?=$side?></aside> </div> <footer> // ... </footer> </body> </html> <?php endif; ?> We use the rest of the site's template files to simply populate those $body, $side and $browserTitle variables with the contents of the page. As an example, this is an abbreviated version of the /site/templates/post.php template: /site/templates/post.php // functions from /site/templates/includes/render.php $meta = renderMeta($page); $tags = renderTags($page); $authorBox = renderAuthor($page->createdUser); $comments = renderComments($page); $body = " <article class='post post-full'> <header> <h1>$page->title</h1> $meta </header> $page->body $tags $authorBox $comments </article> "; if(count($page->related)) { $side = "<h4>Related Stories</h4>" . renderNav($page->related); } What might also be of interest is the homepage template, as it handles the other part of routing of post URLs since they are living off the root rather than in /posts/. That means the homepage is what is triggering the render of each post: /site/templates/home.php if(strlen($input->urlSegment2)) { // we only accept 1 URL segment here, so 404 if there are any more throw new Wire404Exception(); } else if(strlen($input->urlSegment1)) { // render the blog post named in urlSegment1 $name = $sanitizer->pageName($input->urlSegment1); $post = $pages->get("/posts/")->child("name=$name"); if($post->id) echo $post->render(); else throw new Wire404Exception(); // tell _main.php not to include itself after this $renderMain = false; } else { // regular homepage output $limit = 7; // number of posts to render per page $posts = $pages->find("parent=/posts/, limit=$limit, sort=-date"); $body = renderPosts($posts); } The rest of the site's template files were handled in the same way. Though most were a little simpler than this. Several were simply blank, since the default values populated in _init.php were all that some needed. Front-end development using Foundation 4 The front-end was developed with the Foundation 4 CSS framework. I started with the Foundation blog template and then tweaked the markup and css till I had something that I thought was workable. Then Mike and I sent the _main.php template file back and forth a few times, tweaking and changing it further. There was no formal design process here. It was kind of a photoshop tennis (but in markup and CSS) where we collaborated on it equally, but all under Mike's direction. After a day or two of collaboration, I think we both felt like we had something that was very good for the reader, even if it didn't originate from a design in Photoshop or some other tool like that. I think it helps a lot that Foundation provides a great starting point and lends itself well to fine tuning it the way you want it. I also felt that the mobile-first methodology worked particularly well here. Comments System using Disqus We converted the comments system over to Disqus while the site was still running WordPress. This was done for a few reasons: Disqus comments provide one of the best experiences for the user, in my opinion. They also are platform agnostic, in that we could convert the whole site from WP to PW and not have to change a thing about the comments… no data conversion or importing necessary. Lastly, ProcessWire's built-in comments system is not quite as powerful as WordPress's yet, so I wanted cmscritic.com to get an upgrade in that area rather than anything else, and Disqus is definitely an upgrade from WP's comments. In order to ensure that Disqus could recognize the relations of comment threads to posts, we again made use of that $page->wpid variable that keeps the original WordPress ID, and also relates to the ID used by the Disqus comments. This is only for posts that originated in WordPress, as new posts use a ProcessWire-specific ID.
  3. 55 points
    Field dependencies are coming in ProcessWire 2.4, and I just wanted to give you guys a little preview of it. The development of this new feature is being sponsored by Avoine, the company where Antti works (he also specified how it should work). Field dependencies are basically just a way of saying that one field depends on another. It dictates which fields should be shown in a given context. In our case, it also gets into whether a field is required or not. This short video demonstrates how it works: (switch to the 720p and full screen version, as YouTube's default settings for this video make it impossible to see): // Edit @Adamkiss: Here's link for those on mobile: youtu.be/hqLs9YNYKMM The implementation here is done both client-side and server side. Javascript handles the showing/hiding of fields and the required vs. not required state changes. On the server side, it doesn't process any fields that aren't shown, and honors the required rules. A separate processing occurs both client side and server side, ensuring that the user can't make their own rules by manipulating the markup or post data. These field dependencies can be used with any Inputfield forms. That means you'll be able to use it not just in ProcessWire, but in FormBuilder, and via the API too. It's very simple to use from the API. All you have to do is specify a ProcessWire selector to either "showIf" or "requiredIf" to the Inputfield, and ProcessWire takes care of the rest: // show this field only if field 'subscribe' is checked $inputfield->showIf = "subscribe=1"; // show this field only if 'price > 0' and at least one category selected $inputfield->showIf = "price>0, categories.count>0"; // make this field required only if 'email' is populated $inputfield->required = true; $inputfield->requiredIf = "email!=''"; This feature will be in the 2.4 core (rather than as a separate module), so it will also be ready and available for things like module and field configuration screens.
  4. 53 points
    This basic tutorial is primarily aimed at those new to PW. It could also serve as a reference to others more seasoned PW users. The question about how to categorise content comes up in the forums now and again. Hopefully with this post we’ll have a reference to guide us right here in the tutorials board. Many times we need to organise our site content into various categories in order to make better sense of the data or to logically and easily access it. So, how do you organise your data when you need to use categories? Here are a few tips gathered from the PW forums on how to go about this. Using these tips will, hopefully, help you avoid repeating yourself in your code and site content and keep things simple. See the links at the end of this post to some useful discussion around the topic of categorisation. Before making decisions about how to organise your site, you need to consider at least three questions: What items on my site are the main items of interest? These could be people or things (cars, plants, etc.). In most cases, these are the most important content on which all the other stuff point to. Where do items need to be grouped into categories? This is about where items need to “live”. It is about the attributes of the items of interest (e.g. responsibilities, job types, colour, etc.). Attributes can have sub-attributes (e.g. a category job type = driver could be further sub-classified as job type role = train driver). Can they live in more than one place? - This is about having multiple attributes. There could be other issues such as the type of content your main items of interest are but that’s for another post. We’ll keep these examples simple. The main principles explained below still apply. There are at least three possible ways in which you can organise your content depending on your answers to the above three questions. These are: Single category Simple multiple categories Complex multiple categories These are illustrated below. Note that this is what I call them; these are not PW terms. 1. Single Category Suppose you need to do a site for a company that’s made up of several Departments each with employees performing unique functions. These could include “Finance”; “Media Communications”; “Administration”; “Technicians”; “Human Resources”; “Logistics”. We ask ourselves the following questions based on our 3 questions above: 1. Q: What items on my site are the main items of interest? A: Employees. 2. Q: What attributes of our items of interests are we interested in? A: Departments. (Single main category) 3. Do the Departments have sub-categories? A: Yes. (Multiple sub-categories) 4.Can Employees belong to multiple sub-categories? A: No. (Single sub-category) We conclude that what we need is a Single Category model. Why? This is because, in Single Categories model, items of interest can only belong to 1 and only 1 main/parent category and within that only 1 sub-category Employees in this company can only belong to one and only one department. Finance guys do their finance and Logistics guys do their stuff. Letting Techies do press conferences is probably not going to work; that we leave to the Media guys . Assuming the company has the following employees - James, John, Mary, Ahmed, Peter, Jason, Barbara etc., arranging our site content to fit this model could look like the following: Items of interest = Employees Categories = Departments Adopting out strategy to keep it simple and logical, let us write down, hierarchically, our employee names against their departments to mimic the PW tree like this: James Finance John Finance Mary Technician Ahmed Logistics Barbara Media Etc. We notice, of course, that departments start repeating. It doesn't look like we are doing this very logically. If we think about this carefully, we will conclude that, naturally, the thing (attribute in this case) that keeps repeating should be the main criteria for our categorisation. This may seem obvious, but it is worth pointing out. Also, remember, that as per the responses to our questions, the categories (Finance, Logistics, etc.) do not have sub-categories. In this aspect, we are OK. Using this principle about repeating attributes, we find that Departments, rather than Employees, need to be the main categories. Hence, we categorise our PW site content by doing the following. Create a template for each Department. Hence, we have a template called Finance, Logistics, etc. Add the fields needed to those templates. This could be a text field for holding Employee phone numbers, email field for email, title field for their names, etc. Create top level pages for each Department and assign to them their respective templates. Give them appropriate titles, e.g., Finance, Media, etc. Create a page for each employee as a child page of the Department which they belong to. Give them appropriate titles, e.g. James, John, etc. We end up with a tree that looks like this: 1. Finance (ex. main category) a. James (ex. item of interest) b. John c. Shah d. Anne 2. Logistics (ex. main category) a. Ahmed b. Matthew c. Robert d. Cynthia 3. Media a. Barbara b. Jason c. Danita 4. Human Resources a. Michael b. Pedro c. Sally 5. Technician a. Mary b. Oswald c. Dmitri d. Osiris Since an employee can only belong to one Department, our work here is done. We can then use PW variables, e.g. $page->find, $pages->find with the appropriate selectors to find employees within a Department. This is a very basic example, of course, but you get the idea. You have the choice of creating one template file for each category template as well. I prefer the method of using one main template file (see this thread). You could do that and have all Departments use different templates but a single template file. In the template file you can include code to pull in, for example, the file “technician.inc” to display the relevant content when pages using the template “Technician” are viewed. Example code to access and show content in Single Categories model $hr = $pages->find("template=human-resources, limit 50"); foreach ($hr as $h) { echo "{$h->title}"; } But sites do not always lend themselves to this model. Many times, items of interest will need to belong to multiple categories. 2. Simple Multiple Categories Let’s say you were building a site for cars - red cars, blue cars, 2-seaters, 5-seaters, etc. Again, we ask ourselves our questions based on our initial three questions: 1. Q: What items on my site are the main items of interest? A: Cars. 2. Q: What attributes of our items of interests are we interested in? A: Colour, Number of seats, Models, Year of manufacture, Types. (Multiple categories) 3. Do these multiple attributes have sub-attributes? A: Yes. e.g., the attribute Colour has several sub-categories - red, white, green, etc. (Multiple sub-categories) 4. Can Cars have multiple sub-attributes? A: No. e.g., a yellow car cannot be a green car. (Single sub-categories) We therefore conclude that what we need is a Simple Multiple Category model. Why? This is because, in Simple Multiple Categories, items of interest can belong to multiple parent categories. However, within those parent categories, they can only belong to one sub-category. Assuming we have the following cars, manufactured between 2005 and 2008, as items of interest: Mercedes, Volvo, Ford, Subaru, Toyota, Nissan, Peugeot, Renault, Mazda, arranging our site content to fit this model could look like the following: Items of interest = Cars Categories = Model, Year, Colour, Number of seats, Type Sub Categories = Model [Prius, etc.]; Year [2005, 2006, 2007, 2008]; Colour [Red, Silver, Black, White, Green]; Number of seats [2, 5, 7]; Types [sports, SUV, MPV]. Adopting out strategy to keep it simple and logical, if we wrote down our cars names against their attributes like this: Mercedes Model-Name: Year: 2005 Colour: Silver Seats: 2-seater Type: Sports Volvo Model-Name: Year: 2007 Colour: Green Seats: 5-seater Type: SUV Ford Model-Name: Year: 2007 Colour: Red Seats: 7-seater Type: MPV Etc We notice, again, that car attributes start repeating. In order not to repeat ourselves, we want to avoid the situation where our child pages “names” keep repeating. For instance, in the above example tree, we want to avoid repeating year, colour, etc. within the tree. Of course in the frontend our output needs to look like the above where we can list our cars and their respective attributes. We just don’t need a tree that looks like this in the backend. Since we have multiple categories and sub-categories, we need to rethink our strategy for categorising our content as illustrated below. The strategy we used in the first category model will not work well here. Hence, these repeating attributes (year, colour, etc.) need to be the main criteria for our categorisation. We need to end up with a tree that looks like this: 1. Cars a. Mercedes (ex. item of interest) b. Volvo c. Ford d. Subaru e. Toyota f. Range Rover g. Peugeot h. Renault i. Mazda 2. Model (ex. main category) a. Fiesta (ex. sub-category) b. Miata c. Impreza d. Matrix e. Prius f. E-Class g. XC-90 h. Scenic i. L322 j. 505 3. Year a. 2005 b. 2006 c. 2007 (ex. sub-category) d. 2008 4. Colour a. Red b. Silver c. Black d. White e. Green 5. Number of Seats a. 2 b. 5 c. 7 6. Type a. MPV b. Sports c. SUV d. Other At the top of the tree, we have our main items of interest, Cars. They do not have to come first on top of the tree like that but it just makes sense to have them like this. Next, we have the Cars’ categories (attributes). The main categories are parent pages. Each main category has children which act as its sub-categories (cars’ sub-attributes). For instance, the main category colour has sub-categories “red”, “green”, etc. Grouping them under their main category like this makes better sense than having them dangling all over the tree as parent pages themselves. Now that we know what we want to achieve, the next question is how do we go about relating our categories and sub-categories to our main items of interest, i.e., cars? Fields come to mind. OK, yes, but what about the sub-categories (2006, red, 5-seater, etc.)? Surely, we can’t keep typing those in text fields! Of course not; this is PW. We like to simplify tasks as much as we can. What we need is a special type of field. Page Reference Fields or Page Fieldtypes add the ability to reference other pages, either single or multiple pages, within a page. For instance, we could have a Page Reference Field in the template that our Car pages use. Let’s call this “car-template”. When viewing Car pages, we would have the ability to select other pages on our site that we wish to reference, for instance, because they are related to the page we are viewing. In other cases, we could also wish to reference other pages that contain attributes/values of the page we are viewing. This is the situation with our Cars example above. Hence, the sub-categories/sub-attributes for our Cars will be pulled into our car pages using Page Reference Fields. There are two types of Page Reference Fields; single page and multiple pages. What each do is obvious from their names. Single Page Reference Fields will only reference one page at a time. Multiple Page Reference Fields will reference multiple pages. OK, let’s go back to the issue at hand. We need to categorise Cars by various attributes. Do we need to reference the main categories (Year, Type, etc.) in our Car pages? In fact, we don’t. What we need to reference are the sub-categories, i.e. 2005, red, SUV, etc. These will provide the actual attributes regarding the parent attribute of the Cars. We have said we do not wish to type these sub-categories/attributes all the time hence we use Page Reference Fields. Which type of Page Reference Field should we use? Remember that our Cars can have only one sub-category/sub-attribute. That’s our cue right there. In order to select one and only one sub-attribute per Car, we need to use the single Page Reference Field. Hence, we categorise our Cars PW site by doing the following (you may follow a different order of tasks if you wish). Create a template to be used by the Car pages. Give it a name such as car-template Create a page for each of your cars and make them use the car-template Create one template to be used by all the main attribute/categories and their children (the sub-categories). We do not need a template for each of the categories/sub-categories. I name my template “car-attributes” Of course you can name yours differently if you wish. Add the fields needed to this template. You don’t need anything other than a title field for each actually. Create top level pages for each main category and assign to them the template car-attributes. As before, give your pages meaningful titles. Do the same respectively for their child pages. E.g., you should have the following child pages under the parent “Year” - 2005, 2006, 2007 and 2008. Create the Page Reference Fields for each of your main categories/parent attributes. Using our example, you should end up with 5 Page Reference Fields (model, year, colour, seats and type). Each of these should be single Page Reference Fields. It’s a good idea, under the BASICS settings while editing the fields, to include some Description text to, include additional info about the field, e.g. instructions. In addition, you don’t want any page that doesn't belong to a particular attribute to be selectable using any of the Page Reference Fields. For instance, when referencing the year a car was manufactured, we want to be able to only select children of the page Year since that is where the year sub-categories are. We do not want to be able to select children of Colour (red, green, etc.) as the year a car was manufactured! How do we go about this? PW makes this very easy. Once you have created your Page Reference Fields, while still in the editing field mode, look under the settings INPUT. The fourth option down that page is “Selectable Pages”. Its first child option is “Parent of selectable page(s)”. Where it says “Select the parent of the pages that are selectable” click on change to change the parent. By now you know where I am going with this. For the Page Reference Field named Year, choose the page “Year” as the parent whose children will be selectable when using that Page Reference Field to select pages. Similarly, do this for the remaining 4 Page Reference Fields. Note that under this field settings INPUT you can change how you want your pages to be selectable. Be careful that you only select the types that match single Page Reference Fields, i.e. the ones WITHOUT *. For single Page Reference Fields, you have the choices:Select - a drop down select Radio buttons PageListSelect Now edit the car-template to add all 5 of your Car Page Reference Fields. We are now ready to roll. Go ahead and edit your Car pages. In each of them you will see your 5 Page Reference Fields. If you followed the instructions correctly, each of them should only have the relevant child pages/sub-attributes as selectable. Do your edits - select year when car was manufactured, its colour, type, number of seats, etc. and hit Save. By the way, note that Page Reference Fields give you access to all the fields and properties of the page being referenced! You have access to the referenced page’s title, name, path, children, template name, page reference fields, etc. This is really useful when creating complex sites. I call it going down the rabbit hole! These properties of the referenced page are available to you on request. It does mean that you will have to specifically echo out the property you want from that page. Page Reference Fields are echoed out like any other field. Example code to access and show content in Simple Multiple Categories model $cars = $pages->find("template=car-template, limit=10, colour=red, year=2006, seats=5"); foreach ($cars as $car) { echo $car->title; echo $car->year; echo $car->colour; } I have made the above verbose so you can easily follow what I'm trying to achieve. The above code will find 10 red 5-seater cars manufactured in 2006. Remember, colour, year and seats are the names of your custom Page Reference Fields that you created earlier. Some sites will have content that belong to multiple categories and multiple sub-categories. We address this below. 3. Complex Multiple Categories Suppose you are developing a site for a school. The school has teachers (duh!) some of whom teach more than one subject. Besides their classroom duties, some teachers are active in various clubs. On the administration side, some teachers are involved in various committees. You know the drill by now. Let’s deal with our basic questions. 1. Q: What items on my site are the main items of interest? A: Teachers. 2. Q: What attributes of our items of interest are we interested in? A: Subjects, Administration, Clubs (Multiple categories) 3. Do these multiple attributes have sub-attributes? A: Yes. e.g., the attribute Subjects has several sub-categories - History, Maths, Chemistry, Physics, Geography, English, etc. (Multiple sub-categories) 4. Can Teachers have multiple sub-attributes? A: Yes. e.g., a Teacher who teaches both maths and chemistry (Multiple sub-categories) Apart from the response to the final question, the other responses are identical to our previous model, i.e. the Simple Multiple Categories. We already know how to deal with multiple categories so we’ll skip some of the steps we followed in the previous example. Since our items of interest (Teachers) can belong to more than one sub-category, we conclude that what we need is a Complex Multiple Category model. In Complex Multiple Categories, items of interest can belong to multiple parent categories and multiple sub-categories both within and without main/parent categories. By now we should know what will be the main criteria for our categorisation. We need to end up with a tree that looks like this: 1. Teachers a. Mr Smith (ex. item of interest) b. Mrs Wesley c. Ms Rodriguez d. Mr Peres e. Mr Jane f. Mrs Potter g. Ms Graham h. Mrs Basket i. Dr Cooper 2. Subjects (ex. main category) a. History (ex. sub-category) b. Maths c. English d. Physics e. Chemistry f. Geography g. Religion h. Biology i. French j. Music 3. Clubs a. Basketball b. Debate c. Football d. Scouts e. Sailing f. Writing 4. Administration a. Discipline b. Counselling c. Exams board d. Public relations e. Education We are ready to build our site. Which type of Page Reference Field should we use? Remember that our Teachers can teach more than one subject and can be involved in various sub-category activities. That’s our cue right there. In order to select multiple attributes/categories, we of course go for the multiple Page Reference Field. Similar to the previous example, create necessary templates and fields for the site. For our multiple Page Reference Fields, remember to select the correct input field types. These should match multiple Page Reference Fields and are marked with *. For multiple Page Reference Fields, the available choices are: Select Multiple* AsmSelect* Checkboxes* PageListSelectMultiple* PageAutoComplete* Remember to add the multiple Page Reference Fields to the Teachers template. Go ahead and test different selectors, e.g. find Teachers that teach Maths and Chemistry and are involved in the Writing club. Whether you get results or not depends on whether there is actually that combination. An important point to remember is that your multiple Page Reference Fields will return an array of pages. You will need to traverse them using foreach (or similar). Example code Complex Multiple Categories model Find the subjects taught by the Teacher whose page we are currently viewing. You can use if statements to only show results if a result is found. In this case, of course we expect a result to be found; if a Teacher doesn't teach any subject, he/she has no business teaching! subjects is the name of one of your custom Multiple Page Reference Fields. echo "<ul>"; foreach ($page->subjects as $x) { echo "<li>{$x->title}</li>"; } echo "</ul>"; There will be situations where you will need to use both Single and Multiple Page Reference Fields (independently, of course). For instance, in our Teachers example, we might be interested in the Gender of the Teacher. That would require a Single Page Reference Field. Summary What we have learnt: Categorising our site content need not be a nightmare if we carefully think it through. Of course not all sites will fit neatly into the 3 models discussed. By providing answers to a few simple key questions, we will be able to quickly arrive at a decision on how to categorise our content. There are at least 3 models we can adopt to categorise our content - single category; simple multiple category; and complex multiple category. In the latter two models, we make full use of PW’s powerful Page Reference Fields to mimic a relational database enabling us to roll out complex sites fast and easy. Useful links: http://processwire.com/talk/topic/3553-handling-categories-on-a-product-catalogue/ http://processwire.com/videos/create-new-page-references/ http://processwire.com/videos/page-fieldtype/ http://processwire.com/talk/topic/1041-raydale-multimedia-a-case-study/ http://processwire.com/talk/topic/683-page-content-within-another-page/ http://processwire.com/talk/topic/2780-displaying-products-category-wise/ http://processwire.com/talk/topic/1916-another-categories-question/ http://processwire.com/talk/topic/2802-how-would-you-build-a-daily-newspaper/ http://processwire.com/talk/topic/2519-nested-categories/ http://processwire.com/talk/topic/71-categorizingtagging-content/ http://processwire.com/talk/topic/2309-best-way-to-organize-categories-in-this-case/ http://processwire.com/talk/topic/2200-related-pages/ http://processwire.com/talk/topic/64-how-do-you-call-data-from-a-page-or-pages-into-another-page/
  5. 51 points
    This is already old news to many of you here, but I finished writing up the full announcement today so figured I should post it (click the link below). Numerous upgrades and refinements make ProcessWire 2.4 our most friendly and powerful version yet! ProcessWire 2.4 is focused on listening to the feedback from of our users and answering with the best CMS experience for web designers/developers and their clients. Read the full announcement. For those upgrading from a previous version of ProcessWire, please read all of the upgrade instructions. Hope that you enjoy this new version! A huge thanks to Avoine for sponsoring the Field Dependencies feature new to ProcessWire 2.4!
  6. 49 points
    Just wanted to share what I recently used to create forms in modules and in frontend using the API and Inputfield modules PW provides and uses on its own. I think many newcomers or also advanced user aren't aware what is already possible in templates with some simple and flexible code. Learning this can greatly help in any aspect when you develop with PW. It's not as easy and powerful as FormBuilder but a great example of what can be archieved within PW. Really? Tell me more The output markup generated with something like echo $form->render(); will be a like the one you get with FormBuilder or admin forms in backend. It's what PW is made of. Now since 2.2.5~ somewhere, the "required" option is possible for all fields (previous not) and that makes it easier a lot for validation and also it renders inline errors already nicely (due to Ryan FormBuilder yah!). For example the Password inputfield already provides two field to confirm the password and will validate it. De- and encryption method also exists. Or you can also use columns width setting for a field, which was added not so long ago. Some fields like Asm MultiSelect would require to also include their css and js to work but haven't tried. Also file uploads isn't there, but maybe at some point there will be more options. It would be still possible to code your own uploader when the form is submitted. Validation? If you understand a little more how PW works with forms and inputfields you can simply add you own validation, do hooks and lots of magic with very easy code to read and maintain. You can also use the processInput($input->post) method of a form that PW uses itself to validate a form. So getting to see if there was any errors is simply checking for $form->getErrors();. Also the $form->processInput($input->post) will prevent CSRF attacks and the form will append a hidden field automaticly. It's also worth noting that processInput() will work also with an array (key=>value) of data it doesn't have to be the one from $input->post. Styling? It works well if you take your own CSS or just pick the inputfields.css from the templates-admin folder as a start. Also the CSS file from the wire/modules/InputfieldRadios module can be helpful to add. And that's it. It's not very hard to get it display nicely. Here an code example of a simple form. <?php $out = ''; // create a new form field (also field wrapper) $form = $modules->get("InputfieldForm"); $form->action = "./"; $form->method = "post"; $form->attr("id+name",'subscribe-form'); // create a text input $field = $modules->get("InputfieldText"); $field->label = "Name"; $field->attr('id+name','name'); $field->required = 1; $form->append($field); // append the field to the form // create email field $field = $modules->get("InputfieldEmail"); $field->label = "E-Mail"; $field->attr('id+name','email'); $field->required = 1; $form->append($field); // append the field // you get the idea $field = $modules->get("InputfieldPassword"); $field->label = "Passwort"; $field->attr("id+name","pass"); $field->required = 1; $form->append($field); // oh a submit button! $submit = $modules->get("InputfieldSubmit"); $submit->attr("value","Subscribe"); $submit->attr("id+name","submit"); $form->append($submit); // form was submitted so we process the form if($input->post->submit) { // user submitted the form, process it and check for errors $form->processInput($input->post); // here is a good point for extra/custom validation and manipulate fields $email = $form->get("email"); if($email && (strpos($email->value,'@hotmail') !== FALSE)){ // attach an error to the field // and it will get displayed along the field $email->error("Sorry we don't accept hotmail addresses for now."); } if($form->getErrors()) { // the form is processed and populated // but contains errors $out .= $form->render(); } else { // do with the form what you like, create and save it as page // or send emails. to get the values you can use // $email = $form->get("email")->value; // $name = $form->get("name")->value; // $pass = $form->get("pass")->value; // // to sanitize input // $name = $sanitizer->text($input->post->name); // $email = $sanitizer->email($form->get("email")->value); $out .= "<p>Thanks! Your submission was successful."; } } else { // render out form without processing $out .= $form->render(); } include("./head.inc"); echo $out; include("./foot.inc"); Here the code snippet as gist github: https://gist.github.com/4027908 Maybe there's something I'm not aware of yet, so if there something to still care about just let me know. Maybe some example of hooks could be appended here too. Thanks Edit March 2017: This code still works in PW2.8 and PW3.
  7. 49 points
    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>
  8. 46 points
    The Module Blog for ProcessWire replicates and extends the popular Blog Profile. Blog is now in version 2. Please read the README in the Github link below in its entirety before using this module As of 20 December 2017 ProcessWire versions earlier than 3.x are not supported Blog Documentation is here (Work in Progress!) See this post for new features in version 2 or the readme in GitHub. To upgrade from version 1, see these instructions. ################################################## Most of the text below refers to Blog version 1 (left here for posterity). Blog version 1 consists of two modules: ProcessBlog: Manage Blog in the backend/Admin. MarkupBlog: Display Blog in the frontend. Being a module, Blog can be installed in both fresh and existing sites. Note, however, that presently, ProcessBlog is not compatible with existing installs of the Blog Profile. This is because of various structural and naming differences in respect of Fields, Templates, Template Files and Pages. If there is demand for such compatibility, I will code a separate version for managing Blog Profile installs. In order to use the 'Recent Tweets Widget', you will need to separately install and setup the module 'MarkupTwitterFeed'. Please read the README in the Github link below in its entirety before using this module (especially the bit about the Pages, etc. created by the module). I'll appreciate Beta testers, thanks! Stable release works fine. Download Modules Directory: http://modules.processwire.com/modules/process-blog/ Github: https://github.com/kongondo/Blog You can also install from right within your ProcessWire install. Screenshots (Blog version 1) Video Demos ProcessBlog MarkupBlog Credits Ryan Cramer The Alpha Testers and 'Critics' License GPL2
  9. 43 points
    You've been using MODX but now you've found ProcessWire. It’s totally amazed you and you can’t wait to get started. But…you are wondering where everything is. If this is you, read on… This guide is primarily aimed at those who are coming in from MODX and wish to know how to accomplish “MODX things” the “ProcessWire (PW) way”. This is not meant to be a full blown PW tutorial. It will focus on some key MODX concepts/tasks and how to accomplish those in PW. It will cover, whenever possible, both versions of MODX - Evolution and Revolution. The guide assumes that you’ve at least logged into a PW site and/or viewed a demo. The Table of Contents mostly reflects MODX terminology. Table of Contents 1. Manager 2. File System 3. Resources 4. Templates 5. Template Variables (TVs) 6. Template Files 7. Snippets 8. Modules 9. Plugins 10. Chunks 11. Miscellaneous 12. Examples - the PW way… a. Template Variables b. Snippets c. Modules d. Plugins e. Chunks 1. Manager In PW, the “manager” is referred to as the “Admin”. The default location of the Admin is www.yoursite.com/processwire. As of PW 2.3, you can rename the Admin to anything you wish during install. Just be careful to remember the name you use or you will be locked out! (If that happens, there are ways to get around it though). Logging into Admin, you will notice a tree just like in MODX. The Admin runs on jQuery UI and as you’ve seen, it is insanely fast! Don’t be fooled by its simple facade. PW is a really powerful CMS, highly extensible and very easy to use. Customising the PW admin is very easy. There are a number of custom themes available. It is also trivial to make yours. Custom Admin themes go into the folder site/templates-admin/. You can have only one theme at a time. On a related matter, if you wish to create a custom Admin page, it is easy to do so. See this thread for more info. 2. File System After installing PW, you will see two main folders; “site” and “wire” in your file system. Site is where all things related to your site reside. This is your playground and will survive an upgrade. Wire houses the Core. You will never have to go into that folder. 3. Resources In MODX, Resources can be many things (documents, etc.). There is no such term in PW. However, the most important “resources” you need to know about in PW are page and pages. Pages are a very powerful concept in PW. Page can refer to your website’s frontend pages, i.e. what can be viewable by your website users. I say can be for a reason. There are many uses for Pages in PW. This can be confusing to newbies but once you get the concept, you will appreciate the power of the system. Just because something is a Page does not mean it has to be viewable. It can have other uses such as a container that holds data for use by other Pages - in this case the Pages do not have to be displayed on the frontend. In fact, everything you see on the PW tree is a Page. That’s right; even the Admin and its components (Users, Roles, etc.) are all Pages! Still confused by Pages? Have a read here. Pages reside in the PW tree - you may have noticed . You can drag and drop pages to move them around. If drag and drop doesn’t work you probably have MySQL 5.0.5.1 installed. Upgrade your MySQL and you are sorted. The root of your site is the uppermost Page in the tree. In the default PW install this is called Home. You can change the name to something else. The default PW install comes with a number of Pages pre-installed. Try editing a Page. All those Fields you see on that Page when under the “CONTENT” tab? They are not default Fields. They are all Custom Fields! Yes, not even the Title! The only required Field for a Page is “name”. You find this under the “SETTINGS” tab on the page edit screen. More about Fields below… Other related stuff: Menuindex: As an aside, if outputting something like a menu, unless you state otherwise, it will reflect the tree hierarchy. Show in Menu: This functionality is covered by the Page status, whether hidden or not. Hidden pages do not get output on menus (more about menus later) unless explicitly stated so using PW API selectors (see below). Menu Title: No such term in PW. You can name your menu items what you wish to name them. Very important: All Pages must be assigned a Template. 4. Templates In PW, Templates mean something slightly different compared to MODX and many other CMS. MODX describes templates as: That is not entirely true of PW Templates. In PW, the term Template is used in the sense of the English definition of the term: PW Templates serve as a pattern for the foundation of your Pages. By foundation, I do not mean the HTML or CSS. Rather, the Template servers as blueprint for your Pages. What is available to the Template is available to the Page that uses it. Templates establish a pattern for the Pages by the inclusion of Fields to the Template. A Template can have as many or as few Fields as you wish. You can create as many or as few Templates as you wish. You can easily change the Template a Page uses when editing the Page. See under SETTINGS. Note that if you have Fields on that Page that are not in the Template you are switching to, those Page Fields will be lost! PW will warn you about this when switching Templates though. OK, so how do you show “Resources to the world”? That will be the work of Template Files which we’ll look at in section 6. 5. Template Variables A powerful feature of MODX is Template Variables (TVs). If you loved MODX TVs you will absolutely adore the PW equivalent. In PW, TVs are known as Fields. You can define your own Custom Fields. In fact, you will need to create your own in most cases. This is because PW does not have any required Field except for “name”. It is perfectly reasonable to have a Page with only the name Field! Many people do add at least a Title Field for such Pages. In the default PW install, the reason you see the Title Field in all Templates is because it has been set as a “Global” Field under the ADVANCED settings of the Field (edit the Field to see this setting). There are many types of Fields to hold all sorts of data - images, texts, urls, passwords, reference to Pages, etc. You can call your Fields anything you wish. You can call the Field for your main content “body”, or “stuff”, or “content” or whatever you wish as long as you follow the naming convention, i.e. “[a-z], numbers [0-9], or underscores (no dashes or spaces)”. Fields are reusable across different Templates. The order in which they appear on your Page follows the order in which they are arranged on your Template. However, it is important to note that: The order in which Fields appear on your page and/or the inclusion of a Field on your Page does not mean that: The Field will be output on the Frontend. The inclusion of a Field on a Page does not automatically mean it will be output; PW does not make that decision for you. It only makes the Field available to you to use as you require. You can output all or none or few of the Fields on your Page. The order in which Fields appear on a Page does not mean the same order will be reflected when you output the Page. You make that decision. Also note that you are able to arrange Fields side-by-side on your Page (via settings on the Page’s Template) to mimic your site’s layout or for other visual/ease-of-use purposes as you edit the Page in the Admin. You can also give each Field a label and a description. These will appear above each Field when editing a Page. In most cases, the content of Fields will be saved directly to the database. In the case of file related Fields, the path to the file will be stored in the database. There are no direct equivalents of MODX TVs “@Bindings” (data sources). These are inherently built into the different types of Fields. Note that you cannot run PHP code within Fields (so no @Eval). This is by design. Fields are a very powerful and much loved concept in PW. Just Google "custom fields cms" and you’ll see what comes up tops. 6. Template Files So far, we've seen that you create Fields, add them to a Template you've created and edit a Page using that Template to input your content into that Page’s Fields. So far so good but how do you output the content to the world? You do this via Template Files. Template Files live in /site/templates/. You have to create your own Template Files. In MODX, a Template will have both system fields, e.g. body, title, etc. and custom fields (TVs). Rendering a Template is achieved by adding MODX tags to your Template. In PW, in order for the content of your Page to be seen by the world, its Template must have a Template File (or there must be some other Template File associated with a different Template that is dynamically pulling and outputting elsewhere the content of a Page whose Template does not have a Template File). This does not mean that all content within a Page will be automatically output by the Template File. No; in the Template File you can choose to render all or some Fields present in the Pages using that Template or none at all! You tell the Template about the associated Template File when you create/edit the Template. PW assumes that there is a Template File with the same name as the Template in /site/templates/ and will tell you if it does not find one. However, you have two other choices. You can either enter an alternative name for your Template File or tell PW you do not wish to have a Template File for that Template. This means that a Template does not require a Template File. Obviously, in such a case, you will not be able to directly output the content of the Pages using that Template. In some cases, that is exactly what you want . As you get to know more about the system, you will find out how powerful and flexible the PW Template system can be. For instance, you can use your Template as a controller. That’s beyond this guide but feel free to search the forums for more info. Template Files are typically PHP files with logic to dynamically output your content. In most cases, Template Files are HTML with PHP tags inserted to output your content. The PHP in the Template File will in most cases be PW API. Hence, you will see things like $page and $pages in Template Files. The former always refers to the current Page and the latter to any other Pages in the system. These are very powerful variables in PW and give you access to ALL information about ALL pages including their Fields and whether those are empty or not, etc. See below for more info about these variables and have a look the docs too. Note that Templates do not care about what’s in your Template Files. In fact, they won’t even check. All they want is for you to tell them if and how you wish your Pages' contents to be rendered. Your Template Files can even contain pure HTML (although that won’t be dynamic!)! Your Template Files can have references to other things related (or not related!) to the Page using that Template File. For instance, within your Template File, you can pull in the 10 latest “posts” from your Blog or the Children Pages of that Page. There are just too many possibilities to list them all here. Don’t let the PHP in Template Files scare you if you are no coder. I am no coder but I am able to use PW. You will only need to know at least some very basic PHP to use PW. The most important are: echo; foreach; and if… Anything else is a bonus in most cases. In addition, you will need to know how to use the two most important PW variables - $page and $pages. With these two, most of what you would have done in vanilla PHP is covered. They are easy to use and to understand and very much follow the jQuery concept. Check out the docs to learn more. 7. Snippets In MODX, Snippets are the method by which MODX allows you to run dynamic PHP code in any of your pages. In PW, the term Snippets does not exist. What!?! Not to worry; in PW MODX-like Snippet functionality can be achieved in two ways: Template Files: Most PW Template Files are essentially dynamic PHP code. That’s Snippets for you right there. PW Modules: PW Modules do what MODX Snippets do and more (see below). It’s just an issue of terminology. For instance, the popular MODX Snippet WayFinder has a Module counterpart in PW. This is the Module Markup Simple Navigation. However, you do not need to use the Module to create a menu in PW. You can do the same thing using PHP in your Template File. See the default PW install for a simple example. Check out section 12 of this guide for example “popular-MODX-Snippets-to-PW-how-tos”. 8. Modules MODX Evolution defines Modules as “a program that can only be executed from within the manager.” There are PW Modules that fit this definition, for instance, the Module Batcher which is equivalent to the MODX Revolution add-on Batcher. However, there are other PW Modules that are executed in the frontend, e.g. Markup Simple Navigation previously mentioned. In fact, in PW, a Module is PHP that extends the functionality of PW. Modules contain PHP classes that adhere to PW’s Module interface. 9. Plugins In MODX Plugins are PHP code that are set to execute during certain system events. In PW, although the term Plugin does not exist (except maybe in reference to Modules), MODX Plugin functionality is easily doable in PW. This is achieved via Hooks. There is one difference though. In MODX, Plugins are standalone code you can download and install. You cannot download and install PW Hooks. Instead, PW contains many methods that you may hook into in order to modify the behaviour of the method. In other words, PW offers the ability to hook into its processes/events and manipulate them before or after the event or even replace them, etc. Hooks are usually invoked inside Modules. However, Hooks may be attached from anywhere that you use PW's API, for instance in Template Files. The average PW user will not need to use Hooks. For more info about Hooks check the documentation. 10. Chunks MODX defines Chunks as “bits of static text which you can reuse across your site”. There is no equivalent term in PW. You can, however, easily create Chunks in PW. You can create Chunks as a Page that contains various Fields each of which can act as a Chunk. This means you can have Chunks of all sorts of data. You can then set the Page you create to hold your Chunks as hidden (not available to searches). The Page can also be assigned a Template without a Template File to further limit frontend access. It can also be created as a child/grandchild of the page Admin. That will limit access by User (e.g. login required to view it in Admin). Accessing the Fields of that Page as your Chunks using PW API is quite trivial really. Since you can use labels and descriptions to further define each of your Pages’ Fields, this makes it quite easy to describe what each Chunk is for and/or give instructions on how to use the Chunks. See this example for more info. You can also simply use text files to pull into your content as Chunks. Personally I prefer using Pages as Chunks when I need to. In MODX, it is usual for many Snippets to use Chunks (HTML + placeholders) to structure their output (i.e. tpl Chunks). You do not need to do this in PW. Code output can be wrapped in HTML right within the Template File. The important thing to remember is that any Field in any PW Page is available to any other Page and any Template File. Cross-referencing Fields and Pages is child’s play. Seriously; it is that easy. 11. Miscellaneous Tag Syntax: PW does not use a templating language (tagging syntax) like MODX does. See this article why this decision was taken. I agree 100% with the approach and have come to realise its many benefits over using a templating language. However, there are two Modules that allow you to use template language tags in PW. I have never used them so cannot comment further. They are here and here. Settings Page: PW has no settings page like MODX. Many settings are set in the /site/config.php. Other MODX settings equivalents are interspersed in various places including caching content on a Template by Template basis and in Modules. You can also store custom settings needed for your site in the /site/config.php. You can even use Pages to store your settings as mentioned in section 10 (Chunks). Modularity: PW is a very modular system. The whole of PW is made up of Modules (Core Modules) that accomplish different tasks. 12. Examples - the PW way… In this section I will show you equivalent MODX versus PW add-ons as well as how to accomplish various task using PW “Snippets” equivalent. a. Template Variables As previously mentioned, MODX TVs are PW Fields (although PW Fields are more versatile). Outputting the contents of your Fields is very simple. echo $page->name_of_your_field; This gives you the contents of the Field in the current page. Works slightly different for image fields though. For other pages echo: One page $pages->get(ID or PATH or NAME, etc.)->name_of_your_field; //note replace ID with ID of the Page you want, etc. Many pages $pages->find("selectors"); //this will return an array. You can then go through the array using foreach as shown in the examples below to output the field(s) contents MODX - output main content of Page //MODX Evolution[*content*] //MODX Revolution[[*content]] ProcessWire - output main content of Page //ProcessWire echo $page->content;//note; this assumes you have a Field called content in the Template of the current page. In ProcessWire you can find Fields of other pages like so $fruits = $pages->find("template=yumyum, limit=10"); foreach($fruits as $fruit) { echo "<li><a href='{$fruit->url}'>{$fruit->title}</a></li>"; } //find 10 Pages that use the Template yumyum and echo their url and titles in a list. From these examples, you can see the elegance and flexibility of TVs done the PW way. Since there is separation between a Template and a Template File, you can conditionally echo out the contents of Fields found in Pages. b. Snippets Common MODX Snippets and PW equivalent Modules 1. WayFinder: Markup Simple Navigation or code in Template File (see head.inc in default PW installation for a simple menu). 2. Ditto (Evolution) and getResources (Revolution): Functionality inbuilt in PW. Use $page and $pages variables + selectors to find anything, anywhere. 3. Jot (Evolution) and Quip (Revolution): Comments Module is part of the PW Core. It is not enabled by default. You will have to enable it in the Admin. See also the related Comments Manager Module. 4. eForm (Evolution) and FormIt and FormItBuilder (Revolution): There are various Form parser codes in the Forums. There is also a commercial Form Builder Module. It’s not expensive, is developed by PW’s lead developer and proceeds support the PW project. 5. MaxiGallery (Evolution) and Gallery (Revolution): Presently, there is no equivalent. However, it is quite easy to build a photo album. See this tutorial in the wiki. There is also an Images Manager Module (still in alpha though). 6. AjaxSearch: Ajax Page Search Module. 7. Breadcrumbs: Quite easy to accomplish using PW API. See default PW install for an example. 8. FirstChildRedirect: Very easy to do using PW API like this: $session->redirect($page->children->first()->url); in a Template File. 9. getField (Evolution) and getResourceField (Revolution): Inbuilt in PW $page and $pages variables as shown above. 10. GetParent: Inbuilt in PW $page and $pages variables. E.g. $page->parent. 11. getPage: PW has inbuilt pagination as part of the Core (Pagination Markup Module). See this article for a quick tutorial. 12. UltimateParent: Inbuilt in PW API as rootParent. 13. if (Revolution): Use vanilla PHP with PW variable and selectors in Template Files. 14. VersionX (Revolution): Versioning for text-based fields is coming in PW 2.4 (release date summer 2013). Support for maintaining separate draft and live versions coming in PW 2.5 (Winter 2013/2014). Currently, there is also the Module Version Control for Text Fields. 15. getRelated (Revolution): No out-of-the-box Module for this. Has been previously accomplished using PW API in various ways. Have a look in the forums. 16. importX (Revolution): Import Pages CSV Module. 17. phpThumbOf: Thumbnail functionality is inbuilt in PW. There is also the Module Thumbnails that further extends this functionality. 18. getValue and getValues (Revolution): Inbuilt in PW. You can get the value of any Page using PW API - $page and $pages. 19. getFeed (Revolution): RSS Feed Loader Module. 20. MIGX (Revolution): Repeater Module which is part of the PW Core. It is not installed by default. You will need to do that yourself. For more info see this tutorial. 21. Articles (Revolution): See the Blog Profile Module. 22. NewsPublisher (Revolution): See below under Plugins. Examples MODX Ditto [!Ditto? &parents=`5`&extenders=`summary` &tpl=`tplBlog` &orderBy=`createdon ASC`&display=`6` &truncText=`Continue Reading This Article` !] PW equivalent $items = $pages->get(5)->children("sort=date,limit=6"); foreach ($items as $item) { echo $item->title; echo $item->summary;//etc. } //this assumes you have a Field called summary on that Page The first line in the above gives you all information about the 6 child Pages of the Page with ID #5. It returns an array. In other words, a basket of various documents containing all the info about those documents. Next, you traverse the array using foreach. In layman terms, you rummage through the basket picking goodies! In order to wrap HTML around the code output, we do it like this instead (there’s other ways to do it as well!) echo "<ul class='articles'>"; foreach($pages->get(5)->children("sort=date,limit=6") as $item) { echo "<li><p><a href='{$item->url}'>{$item->title}</a><br /> <span class='summary'>{$item->summary}</span></p></li>"; } echo "</ul>"; In this example, we have asked PW to grab the child Pages directly within the foreach rather than creating a variable $items first. MODX getResources // Output a list of child Resources of the current Resource, using the 'myRowTpl' chunk: [[!getResources? &parents=`[[*id]]` &tpl=`myRowTpl`]] PW equivalent echo "<ul>"; foreach ($page->children as $child) { echo "<li><a href='{$child->url}'>{$child->title}</a></li>"; } echo "</ul>"; //You might want to limit the number of child Pages you are getting if they are many! MODX getResources // Output the top 5 latest published Resources beneath the Resource with ID '5', with tpl 'blogPost': [[!getResources? &parents=`5` &limit=`5` &tpl=`blogPost` &includeContent=`1`]] PW equivalent echo "<h3>Latest Posts</h3>"; $posts = $pages->get(5)->children("limit=5"); foreach ($posts as $post) { echo $post->body; } c. Modules 1. Batcher and Docmanager (Revolution): Batcher Module (covers most equivalent functions). 2. DocFinder (Evolution): Inbuilt in PW default install. d. Plugins 1. QuickManager (Evolution): Several Modules can do this, i.e., Fredi, Page Frontend Edit and Inline Editor Modules. 2. ManagerManager (Evolution): Same functionality can be achieved using Templates, Access Control and the Modules Page Edit Field Permission, Page Edit Per User and Page Edit Per Role. e. Chunks See this example. Btw, the PW Cheat Sheet is you best friend... Hope this has been helpful! /kongondo
  10. 43 points
    Table Use this for tabular data, like rate tables or other things that you might typically represent in a spreadsheet. Use it for situations where you don't need the full-blown flexibility of repeaters, as it's technically more efficient with far less overhead than repeaters. Something like the Events Fieldtype could be very easily re-created via a Table field, but the potential uses are far broader. But for the most part, think tabular data when it comes to the Table field. Multipliers This is good for when you need a range of values (whether text, textarea, numbers, dates, etc.). If you are using repeaters with just one field in them, you might be a lot better off with a Multiplier. Like the Table field, Multipliers are very efficient and low overhead relative to something like Repeaters. Use Multipliers when you need to repeat a single input multiple times, optionally with a min and max number of inputs. Lets say you are building an employee directory, and each employee has between 1 and 3 email addresses. Rather than using 3 separate email fields, you would use 1 multiplier field and specify min=1 and max=3. Repeaters These are infinitely flexible in terms of what they represent, but each row of values is technically a page in the system. As a result, with the flexibility comes significant overhead. This is really only an issue when the quantity of repeater items gets high, or when you have lots (thousands) of pages using repeaters. I recommend repeaters for setting up things like homepage carousels. For example, if you go to the Villas of Distinction homepage, there are 3 separate repeaters in use on that page, each holding a photo, title, description, link. The client can have as many items in each of those sections as they want. Currently it looks like the first repeater as 6 items, the 2nd has 2, and the 3rd has 6. The possibilities of what can be represented with repeaters is endless, but look for potential alternatives when dealing with large quantities (whether large quantities of repeater items, or large quantities of pages using repeaters). PageTable This is one of the ProFields that is available for free (thanks to Avoine sponsorship) on the ProcessWire dev branch. Meaning, it'll be available for everyone to use as part of the core in ProcessWire 2.5. And you can use it now if you don't mind running the dev branch. PageTable has all the flexibility of repeaters, but with lower overhead from the admin/input perspective. Rather than trying to bundle all the inputs on one screen, PageTable shows you a table of items and you click on the item to edit it in a modal window. This enables it to be a lot more efficient from the admin UI perspective. It's also more flexible than repeaters are in terms of where you store your items. PageTable lets you choose where they should live, whether as children of the page being edited, or as children of some other parent page you designate. They might be a little more work to setup than repeaters, but I think that most situations where you need the flexibility of repeaters may be better served by PageTable. PageTable still can't compete with the speed and efficiency of Table or Multiplier, but consider using PageTable anywhere that you might have used Repeaters before. Repeaters and PageTable are fundamentally different from the admin UI/input perspective, so you'd want to compare them yourself to see what suits your individual input needs better. PageTable involves more clicking to create and edit items, making Repeaters potentially faster for entering data rapidly. But PageTable will scale much further in the admin UI than Repeaters will, so I would personally favor PageTable in more situations than Repeaters.
  11. 43 points
    I wanted to give you a little preview of a new module I've been working on recently: the Form Builder module. This module lets you create simple-to-complex forms and place them in your site without any need for development. In this video we show how we can create, publish and test a simple contact form in less than 5 minutes. Then we take a quick look at a more complex form (already produced). I recommend viewing the full screen + HD 720p version so that you can see it more clearly than in this small forum embed. (note: there's no sound, so no need to adjust your volume) More about the Form Builder The Form Builder has many options for the developer to customize output and create their own themes, markup, etc. But the goal here was to build something that could enable you (or your client) to publish a form in less than 5 minutes, so that's the focus of this video. This tool is literally easy enough for your clients to create and manage their own forms. Or from a developers perspective, it takes something that used to consume hours and makes it happen in minutes. Form results save in the database and optionally can be CC'd to you by email. Result entries and be viewed, filtered by date, and even edited in the admin. You can export results to a spreadsheet as well. I'm working on the ability to export to pages, though that isn't quite ready yet. How the Form Builder will be licensed This module will be individually licensed on a per-site basis for a small license fee. It will also be available for free personal use to established forum members. Basically, I want to see if I can substitute some of my client work to focus on ProcessWire instead. And in order to do that, I need to have an income (I have a family to support). So I thought that building a special tool like this would be a way to make a go of it. A lot of you have asked to make donations to the ProcessWire project, and while we still won't be able to take donations, you will be able to support the ProcessWire project by purchasing and using the Form Builder in your own client projects. So a lot of motivation for this module came from wanting to provide something really special for all that had wanted to support ProcessWire. After a few months of work, I'm pretty happy with how this module has turned out -- it's already saved me tons of time in my client projects. So I'm thinking and hoping you guys will really like it too. I look forward to your feedback.
  12. 39 points
    Sorry I'm a little behind with messages here, I'll be caught up by mid week. But I've got a good excuse. Yesterday Karena Savannah Cramer was born. She is our 2nd daughter (Hanna Ryan, age 3 is our first). We will all be coming home from the hospital hopefully tomorrow.
  13. 37 points
    ProcessWire ProFields is new product that will soon be available in the ProcessWire store. It consists of 4 really useful new modules: Textareas (Fieldtype + Inputfield) Multiplier (Fieldtype + Inputfield) Table (Fieldtype + Inputfield) AutoLinks (Textformatter) These modules are currently in beta testing, and I'll be posting screencasts to highlight some of the features of each over the next week or so. To start with, here is a screencast for Textareas: This video includes sound (narration) and I recommend viewing it at a larger size than above (preferably full screen), and bump it up to the 720p resolution so that you can see everything in better detail.
  14. 36 points
    Menu Builder As of 29 December 2017 ProcessWire versions earlier than 3.x are not supported Modules Directory Project Page Read Me (How to install, use, etc..) For highly customisable menus, please see this post. If you want a navigation that mirrors your ProcessWire page tree, the system allows you to easily create recursive menus using either vanilla PHP or Soma's great MarkupSimpleNavigation. In some cases, however, you may wish to create menus that: 1. Do not mirror you site's page tree (hirarchies and ancestry); and 2. You can add custom links (external to your site) to. That is primarily where Menu Builder comes in. It is also helpful if you: 3. Prefer creating menus via drag and drop 4. Have a need for menus (or other listings) that will be changing regularly or that you want to allow your admin users to edit. The issue of custom menus is not new here in the forums. The difference is that this module allows you to easily create such menus via drag and drop in the Admin. Actually, you can even use it to just create some list if you wanted to. In the backend, the module uses the jQueryUI plugin nestedSortable by Manuele J Sarfatti for the drag and drop and is inspired in part by the WP Custom Menu feature. Please read the Read Me completely before using this module. For Complex or highly-customised menus, it is recommended to use the getMenuItems() method as detailed in this post. Features Ability to create menus that do not mirror your ProcessWire Page Tree hierarchy/structure Menus can contain both ProcessWire pages and custom links Create menu hierarchies and nesting via drag and drop Easily add CSS IDs and Classes to each menu item on creating the menu items (both custom and from ProcessWire pages) or post creation. Optionally set custom links to open in a new tab Change menu item titles built from ProcessWire pages (without affecting the original page). E.g. if you have a page titled 'About Us' but you want the menu item title to be 'About' Readily view the structure and settings for each menu item Menus stored as pages (note: just the menu, not the items!) Menu items stored as JSON in a field in the menu pages (empty values not stored) Add menu items from ProcessWire pages using page fields (option to choose between PageAutocomplete and AsmSelect [default]) or a Selector (e.g. template=basic-page, limit=20, sort=title). For page fields, you can specify a selector to return only those specified pages for selection in the page field (i.e. asm and autocomplete) For superusers, optionally allow markup in your menu titles, e.g. <span>About</span> Menu settings for nestedSortable - e.g. maxLevels (limit nesting levels) Advanced features (e.g. add pages via selector, menu settings) currently permissible to superadmins only (may change to be permission-based) Delete single or all menu items without deleting the menu itself Lock down menus for editing Highly configurable MarkupMenuBuilder - e.g. can pass menu id, title, name or array to render(); Passing an array means you can conditionally manipulate it before rendering, e.g. make certain menu branches visible only to certain users [the code is up to you!] Optionally grab menu items only (as a Menu object WireArray or a normal array) and use your own code to create custom highly complex menus to meet any need. More... In the backend, ProcessMenuBuilder does the menu creation. For the frontend, menus are displayed using MarkupMenuBuilder. Credits In this module's infancy (way back!), I wanted to know more about ProcessWire modules as well as improve my PHP skills. As they say, what better way to learn than to actually create something? So, I developed this module (instead of writing PW tutorials as promised, tsk, tsk, naughty, naughty!) in my own summer of code . Props to Wanze, Soma, Pete, Antti and Ryan whose modules I studied (read copied ) to help in my module development and to Teppo for his wonderful write-up on the "Anatomy of fields in ProcessWire" that vastly improved my knowledge and understanding of how PW works. Diogo and marcus for idea about using pages (rather than a custom db table), onjegolders for his helpful UI comments, Martijn Geerts, OrganizedFellow, dazzyweb and Mike Anthony for 'pushing me' to complete this module and netcarver for help with the code. Screens
  15. 36 points
    Lately there have been lots of people that are not enjoying the default admin theme, so we've been working on making something new. Not trying to solve all issues or add every new feature we'd like, but just trying to come up with something to have as an interim replacement for the default admin theme until we can afford the time to do something broader in scope (like Phillip Reiner's great admin theme design for example). So this theme doesn't necessarily break a lot of new ground, but hopefully has some of the improvements that people are looking for. Visually, the goal here was to find a lighter, more modern look and reduce the boxes-in-boxes feel of the current admin theme. I've opted to commit it to the dev branch because it requires the latest version of ProcessWire on the dev branch, and likely will continue to with updates. Meaning, I can't distribute this one as a 3rd party theme very easily. This is because I'm making core updates related to the admin theme at the same time. So if you want to help test this new theme, you'll need to grab the dev branch of ProcessWire. The new admin theme is in /site-default/templates-admin/. That means you'll see it as the default theme if you are installing a new copy of PW. But if upgrading an existing copy, you'll continue to get the old theme. If you want the new theme, then just copy the /site-default/templates-admin/ directory to your /site/templates-admin/ directory on your existing PW install. This would be in addition to replacing your /wire/ directory with the latest one from dev, as usual for PW upgrades. The existing default admin theme also remains in place in /wire/templates-admin/. So if you want to stick with the existing/stable admin theme, then just make sure you don't have a /site/templates-admin/ dir in place (unless you are using a 3rd party admin theme). This admin theme is probably not production ready, as it's not been tested in many browsers yet. I personally haven't yet tested it in anything but Chrome and Firefox in OS X. Please let me know if you experience issues in other browsers. I fully expect things won't be pretty in IE... but they never are. To start, this comes with 3 color schemes (though we'll be adding more too): Warm: Modern (similar to processwire.com site colors): Classic (similar to existing admin theme colors): To switch to one color theme or the other, specify a GET variable of 'colors' in any URL you are accessing in the admin: /processwire/?colors=warm /processwire/?colors=modern /processwire/?colors=classic To specify a default, edit your /site/config.php and set to one of the following: $config->adminThemeColors = 'warm'; $config->adminThemeColors = 'modern'; $config->adminThemeColors = 'classic'; We'll probably make this switchable in the user profile at some point, but that comes later. This theme also comes with some new features (most of which have been copied/inspired from the work of others here, whether in other admin themes, modules or designs): It now uses Font-Awesome icons rather than jQuery UI icons. We now only use jQuery UI icons for actual jQuery UI widgets. Note that asmSelect and our file/image inputfields are built as jQuery UI widgets, but I don't think anything else is. Basically, the majority of icons in the system are now Font-Awesome icons. You can associate Font Awesome icons with templates. When associated with a template, the icons will appear in the page list, in front of the page title. To use this, edit any template and go to the Advanced tab. In the "List of fields to show in admin page list", you can type in any Font Awesome icon name (in addition to the field names you could before). For example, in the Page List screenshots above, I have my "search" template configured with the value: "icon-search title". You can associate Font Awesome icons with fields. When associated with a field, the icon will appear at the beginning of the input. For example, I associated a coffee icon with my "title" field in the screenshot below. To do this, edit the field, click on the Advanced tab, and enter the icon name in the new field provided for it. The top navigation now supports simple dropdowns. A new "user" dropdown has also been added that contains profile and logout links. The main Pages screen contains an "add new..." button, that is itself a dropdown to a list of templates that are configured adequately for us to know where they would be added. To use this, you have to configure your template "family" settings. The search box is now ajax powered, though this was introduced a couple weeks ago in the existing admin theme too. The theme is responsive too, of course. This is all kind of preliminary and open to changes. I'm trying to keep the scope of the project fairly small since I don't have much time to work on it. But just wanted something to quiet the haters a bit for a little while, so that we can take our time on the bigger admin theme project for longer term. We'd appreciate your feedback if anyone has time to help test.
  16. 35 points
    Hi everyone! With Batcher you can batch-edit and create Pages in the Pw Admin. If you install this module, you get a new Page "Batcher" under Setup. Modules page: http://modules.processwire.com/modules/process-batcher/ Github: https://github.com/wanze/ProcessBatcher Editing How does it work? Search your pages with a selector. You can check if you want to include also hidden/unpublished pages with the filters. Select the pages you want to execute an action (the action only gets executed on "checked" pages). Select the action and if necessary, additional data like the new parent or the new template. Execute. Supported actions: Publish/Unpublish Pages Hide/Unhide Pages Lock/Unlock Pages Trash Pages Delete Pages Change Parent Change Template Batcher does the following permission checkings for the current user: Don't display pages that are not editable Remove Actions if the user doesn't have the permissions (page-delete, page-move, page-template, page-lock) Important notes: When changing a template, data in fields of the old template which are not assigned to the new template gets deleted. When changing the parent, the template of the new parent must accept the pages template as children. This is a setting in the template under "family". Creating How does it work? Select a parent where your new pages will be added as children Add as many pages as you want by clicking "add Page" Click "Create Pages" You must enter a title and choose a template. The name is optional: If left empty, Pw will generate this for you. Includes permission checking and Family template restrictions. This means in detail: The selected parent must accept children and their template The pages template must accept the parents template User needs the permission to add children to the selected parents template User needs the permission to create Pages for the chosen Template Batch-creating tips The chosen template and the statuses are always cloned from the last row. So if you need to add 30 pages with the same template, define it first and the click "add Page" - it'll make your life easier ;-) You can drag & drop the table rows should you want to change the order. The dragging looks ugly but it works. For the lazy dogs and keybord hackers among us, you can add a new row by pressing "ctrl+n". This works (at least in firefox) only if no input has focus. After adding a new row, the title input gets the focus. By pressing 3 times tab you arrive at the published-checkbox, here the short-cut works. Restrict Batcher for a user to only allow editing or creating Create permissions "batcher-edit" and/or "batcher-add". As soon those exists, the module checks if the current user has the permissions. If you only need batch creating, check out the following module by Soma: http://processwire.com/talk/topic/2138-process-tools-create-pages-wip/ Cheers
  17. 35 points
    A quick tutorial how to create file downloads using pages You will be able to create a new page using template "PDF" (or any you setup), upload a pdf file. You then can select this page using page fields, or links in Wysiwyg. The url will be to the page and NOT the file itself. This will allow to keep a readable permanent unique url (as you define it), unlike /site/assets/files/1239/download-1.pdf, and you'll be able to update/replace the uploaded file without worring about its filename. Further more the file will also have an id, the one of the page where it lives. Clicking those links will download or open the file (when target="_blank") like it would be a real file on server with a path like /downloads/project/yourfile.pdf. You'll be also able to use the "view" action directly in the page list tree to view the file. Further more you'll be able to esaily track downloads simply by adding a counter integer field to the template and increase it every time the page is viewed. Since the file is basicly a page. This all works very well and requires only minimal setup, no modules and best of it it works in the same way for multi-language fields: Just create the language alternative fields like "pdf, pdf_de, pdf_es" and it will work without modifying any code! Still with me? ok PW setup Download folder: Create a template "folder" or "download-folder" with only a title needed. Create pages in the root like /downloads/project/ using this template. Setup the template for the pdf files 1. Create a new template in PW. Name it pdf 2. Goto template -> URLs tab and set the URL end with slash to no. (So we can have /path/myfile.pdf as the URL) 3. Create a new custom file field, name it pdf. Set its maximal count to 1 under -> Details tab. 4. Add the pdf field created to the pdf template. Easy. 5. Create a new "pdf" page using the pdf template under a download folder you created earlier. 6. Give it the title and in the name field add ".pdf" to the end (could also leave as is) Template PHP file for the pdf files 1. Create the template file pdf.php in your /site/templates folder 2. add the following code: <?php // pdf.php if($page->pdf){ wireSendFile($page->pdf->filename); } Done. To see the options you have with PW's wireSendFile() you can also overwrite defaults <?php // pdf.php if($page->pdf){ $options = array( // boolean: halt program execution after file send 'exit' => true, // boolean|null: whether file should force download (null=let content-type header decide) 'forceDownload' => false, // string: filename you want the download to show on the user's computer, or blank to use existing. 'downloadFilename' => '', ); wireSendFile($page->pdf->filename, $options); } Simple and powerful isn't it? Try it out. Some thoughts advanced Create as many file types as you like. It might also be possible to use one "filedownload" template that isn't restricted to one field type but evaluate it when being output using $page->file->ext, or save the file extension to the page name after uploading using a hook. One last thing. You can add other meta fields or preview images to the template and use those to create lists or detail pages. It's all open to goodness. Again all without "coding" and third-party modules. Further more you can use the excellent TemplateDecorator to add icons per template and have a nice pdf icon for those pages. This as a base one could also easily create a simple admin page for mass uploading files in a simple manner, and create the pages for the files automaticly. ImagesManager work in the same way. Cheers
  18. 35 points
    Here are some API additions to the dev branch, primarily for WireArray/PageArray/etc. I've found these very handy lately, and would have on almost any project I worked on, so decided they'd add value to the core. I'll add these to the cheatsheet once 2.4 replaces 2.3, but for now, here they are. The examples here use PageArray, but note that these API additions apply to any WireArray derived type, not just PageArray. WireArray::implode() Implode all elements to a delimiter-separated string containing the given property from each item. Similar to PHP's implode() function. Usage: $string = $items->implode([$delimiter], $property, [$options]); Arguments: $delimiter - The delimiter to separate each item by (or the glue to tie them together). May be omitted if not needed $property - The property to retrieve from each item (i.e. "title"), or a function that returns the value to store. If a function/closure is provided it is given the $item (argument 1) and the $key (argument 2), and it should return the value (string) to use. [$options] - This argument is optional. When used, it's an array with modifiers to the behavior: skipEmpty: Whether empty items should be skipped (default=true) prepend: String to prepend to result. Ignored if result is blank. append: String to prepend to result. Ignored if result is blank. Examples: $items = $pages->find("template=basic-page"); // render all the titles, each separated by a <br>, for each page in $items echo $items->implode('<br>', 'title'); // render an unordered list of each item's title echo "<ul><li>"; echo $items->implode('</li><li>', 'title'); echo "</li></ul>"; // same as above, but using prepend/append options, // this ensures no list generated when $items is empty echo $items->implode('</li><li>', 'title', array( 'prepend' => '<ul><li>', 'append' => '</li></ul>' )); // same as above, but with all items now presented as links // this demonstrates use of $property as a function. note that // we are also omitting the delimiter here as well, since we don't need it echo $items->implode(function($item) { return "<li><a href='$item->url'>$item->title</a></li>"; }, array('prepend' => '<ul>', 'append' => '</ul>')); WireArray::explode() Return a plain array of the requested property from each item. Similar to PHP's explode() function. The returned PHP array uses the same keys as the original WireArray (if that matters). Usage: $array = $items->explode($property); Arguments: $property - The name of the property (string) to have in each array element (i.e. "title"). You may also provide a function/closure here that should return the value to store. When a function/closure is used it receives the $item as the first argument and the $key (if needed) as the second. Examples: // get an array containing the 'title' of each page $array = $items->explode('title'); // get an array containing the id, url and title of each page $array = $items->explode(function($item) { return array( 'id' => $item->id, 'url' => $item->url, 'title' => $item->title ); }); WireArray::data() Store or retrieve an arbitrary/extra data value in this WireArray. This is exactly the same thing that it is jQuery. I've personally found this useful when building search engines: the search engine can store extra meta data of what was searched for as a data() property. Then any other functions receiving the WireArray/PageArray have access to this additional info. For example, the search engine portion of your site could populate an array of summary data about what was searched for, and the render/output code could render it to the user. Usage: // Setting data $items->data('key', 'value'); // Getting data $value = $items->data('key'); // Get array (indexed by key) of all data $values = $items->data(); Arguments: The above usage section explains all that's needed to know about the arguments. The only additional comments I'd make are that 'key' should always be a string, and 'value' can be anything you want it to be. Example: function findSkyscrapers() { $floors = (int) wire('input')->get->floors; $year = (int) wire('input')->get->year; $items = wire('pages')->find("template=skyscraper, floors=$floors, year=$year"); $items->data('summary', array( 'Number of floors' => $floors, 'Year constructed' => $year )); return $items; } // the render function can focus purely on output function renderSkyscrapers($items) { echo "<h2>You searched for:</h2>"; // render the summary of what was searched for foreach($items->data('summary') as $label => $value) { echo "<p>$label: $value</p>"; } echo "<h3>Skyscrapers found:</h3>"; // note use of new implode() function, though a foreach() would be just as well here echo $items->implode(function($item) { return "<p><a href='$item->url'>$item->title</a></p>"; }); } WireArray::and() WireData::and() Return a new copy of the WireArray with the given item(s) appended. Primarily as a syntax convenience for various situations. This is similar to jQuery's add() and andSelf() functions, but I've always felt "add" implied adding something to the original rather than creating a new combination, so went with "and" in this case. The term "and" is actually a reserved word in PHP, so you can't usually have a function named "and()", but through the magic of hooks, ProcessWire can. This function should reduce the instances in which you'd need to do "$a = new PageArray();" for example. Usage: // create a new WireArray with $items and $item (appended) $myItems = $items->and($item); // create a new WireArray with $items and $moreItems (appended) $myItems = $items->and($moreItems); // create a new WireArray with $items and $item (prepended) $myItems = $item->and($items); // create a new WireArray with $item and $anotherItem (appended) $myItems = $item->and($anotherItem); // create a new WireArray 4 items $family = $pappa->and($mamma)->and($brother)->and($sister); Examples: // generate breadcrumb trail that includes current page foreach($page->parents->and($page) as $item) { echo "<a href='$item->url'>$item->title</a> / "; } // check if page or its children has a featured checkbox if($page->and($page->children)->has("featured=1")) { echo "<p>Featured!</p>"; }
  19. 34 points
    This module is improved and extended successor to Version Control For Text Fields. It handles everything it's predecessor did -- providing basic version control features for page content -- and quite a bit more. Download or clone from GitHub: https://github.com/teppokoivula/VersionControl. This module requires ProcessWire 2.4.1 or later, mostly because of the file features, which require certain Pagefile and Pageimage methods to be hookable. There's no sensible way around this limitation; for those stuck with < 2.4.1, Version Control For Text Fields will remain a viable option. What does it do? While editing pages, fields with old revisions available show up with a new icon in their header bars. By hovering that icon you get a list of available revisions and by clicking any one of those the value of that particular field is reverted to that revision. No changes are made to page until you choose a revision and save the page, which means that you can keep switching between revisions to get an idea what's really changed without inadvertently causing any content to change. The module also adds a History tab to page edit. This tab opens a view to the history of current page in the form of "revisions" -- each of which is a group of changes to page fields processed during one page save (similar to revisions in various source control applications). There are three actions you can perform on these revisions: adding comments, live previewing what the page might've looked in that revision and restoring the page to specific revision. One specific feature that has been a big thing for me personally is support for file (and image) fields, as the original version control module felt rather incomplete without it. I'm hoping to take this a lot further performance, stability and feature wise, but as it stands right now, it's already included here and should be fully functional. Watch the video preview here I prepared a little screencast outlining most of this: http://youtu.be/AkEt3W7meic. Considering that it was my first screencast ever, I'd like to think that it wasn't that bad.. but I might give it another shot at some point, this time planning a bit before hitting "record" Upgrading from Version Control For Text Fields For those already using Version Control For Text Fields, I've added something extra. If you upgrade that module to it's latest version, you should see a new checkbox in it's settings screen saying "Don't drop tables during uninstall". If you check this, uninstall the module and then remove it's files (this is required in order to install Version Control), your old data should be automagically imported to Version Control. Import has only been tested with limited amounts of demo data. Proper tests are yet to come, so please be careful with this feature! Update, 21.6.2015: as of today, this module is no longer in beta. While all the regular warnings still apply (making changes, including installing any new modules, on a production site should always be considered risky) Version Control has gone through pretty extensive testing, and should be as stable as any other module out there.
  20. 33 points
    --------------------------------------------------------------------------------------------------------------------------------- when working with PW version 2.6+, please use Pim2, not Pim! read more here on how to change from the older to the newer version in existing sites --------------------------------------------------------------------------------------------------------------------------------- PageImage Manipulator The Page Image Manipulator is a module that let you in a first place do ImageManipulations with your PageImages. - And in a second place there is the possibility to let it work on any imagefile that exists in your servers filesystem, regardless if it is a 'known PW-image'. The Page Image Manipulator is a Toolbox for Users and Moduledevelopers. It is written to be as close to the Core ImageSizer as possible. Besides the GD-filterfunctions it contains resize, crop, canvas, rotate, flip, sharpen, unsharpMask and 3 watermark methods. How does it work? You can enter the ImageManipulator by calling the method pim2Load(). After that you can chain together how many actions in what ever order you like. If your manipulation is finished, you call pimSave() to write the memory Image into a diskfile. pimSave() returns the PageImage-Object of the new written file so we are able to further use any known PW-image property or method. This way it integrates best into the ProcessWire flow. The three examples above put out the same visual result: a grayscale image with a width of 240px. Only the filenames will slightly differ. You have to define a name-prefix that you pass with the pimLoad() method. If the file with that prefix already exists, all operations are skipped and only the desired PageImage-Object gets returned by pimSave(). If you want to force recreation of the file, you can pass as second param a boolean true: pim2Load('myPrefix', true). You may also want to get rid of all variations at once? Than you can call $pageimage->pim2Load('myPrefix')->removePimVariations()! A complete list of all methods and actions are at the end of this post. You may also visit the post with tips & examples for users and module developers. How to Install Download the module Place the module files in /site/modules/PageImageManipulator/ In your admin, click Modules > Check for new modules Click "install" for PageImageManipulator Done! There are no configuration settings needed, just install and use it. Download (version 0.2.0) get it from the Modules Directory History of origins http://processwire.com/talk/topic/3278-core-imagemanipulation/ ---------------------------------------------------------------------------------------------------------- Page Image Manipulator - Methods * pimLoad or pim2Load, depends on the version you use! pimLoad($prefix, $param2=optional, $param3=optional) param 1: $prefix - (string) = mandatory! param 2: mixed, $forceRecreation or $options param 3: mixed, $forceRecreation or $options return: pim - (class handle) $options - (array) default is empty, see the next method for a list of valid options! $forceRecreation - (bool) default is false It check if the desired image variation exists, if not or if forceRecreation is set to true, it prepares all settings to get ready for image manipulation ------------------------------------------------------------------- * setOptions setOptions(array $options) param: $options - (array) default is empty return: pim - (class handle) Takes an array with any number valid options / properties and set them by replacing the class-defaults and / or the global config defaults optionally set in the site/config.php under imageSizerOptions or imageManipulatorOptions. valid options are: quality = 1 - 100 (integer) upscaling = true | false (boolean) cropping = true | false (boolean) autoRotation =true | false (boolean) sharpening = 'none' | 'soft' | 'medium' | 'strong' (string) bgcolor = (array) css rgb or css rgba, first three values are integer 0-255 and optional 4 value is float 0-1, - default is array(255,255,255,0) thumbnailColorizeCustom = (array) rgb with values for colorize, integer -255 - 255 (this can be used to set a custom color when working together with Thumbnails-Module) outputFormat = 'gif' | 'jpg' | 'png' (Attention: outputFormat cannot be specified as global option in $config->imageManipulatorOptions!) set {singleOption} ($value) For every valid option there is also a single method that you can call, like setQuality(90), setUpscaling(false), etc. ------------------------------------------------------------------- * pimSave pimSave() return: PageImage-Object If a new image is hold in memory, it saves the current content into a diskfile, according to the settings of filename, imagetype, targetFilename and outputFormat. Returns a PageImage-Object! ------------------------------------------------------------------- * release release() return: void (nothing) if you, for what ever reason, first load image into memory but than do not save it, you should call release() to do the dishes! ;-) If you use pimSave() to leave the ImageManipulator, release() is called automatically. ------------------------------------------------------------------- * getOptions getOptions() return: associative array with all final option values example: ["autoRotation"] bool(true) ["upscaling"] bool(false) ["cropping"] bool(true) ["quality"] int(90) ["sharpening"] string(6) "medium" ["targetFilename"] string(96) "/htdocs/site/assets/files/1124/pim_prefix_filename.jpg" ["outputFormat"] string(3) "jpg" get {singleOption} () For every valid option there is also a single method that you can call, like getQuality(), getUpscaling(), etc. See method setOptions for a list of valid options! ------------------------------------------------------------------- * getImageInfo getImageInfo() return: associative array with useful informations of source imagefile example: ["type"] string(3) "jpg" ["imageType"] int(2) ["mimetype"] string(10) "image/jpeg" ["width"] int(500) ["height"] int(331) ["landscape"] bool(true) ["ratio"] float(1.5105740181269) ["bits"] int(8) ["channels"] int(3) ["colspace"] string(9) "DeviceRGB" ------------------------------------------------------------------- * getPimVariations getPimVariations() return: array of Pageimages Collect all pimVariations of this Pageimage as a Pageimages array of Pageimage objects. All variations created by the core ImageSizer are not included in the collection. ------------------------------------------------------------------- * removePimVariations removePimVariations() return: pim - (class handle) Removes all image variations that was created using the PIM, all variations that are created by the core ImageSizer are left untouched! ------------------------------------------------------------------- * width width($dst_width, $sharpen_mode=null) param: $dst_width - (integer) param: $auto_sharpen - (boolean) default is true was deleted with version 0.0.8, - sorry for breaking compatibility param: $sharpen_mode - (string) possible: 'none' | 'soft' | 'medium' | 'strong', default is 'soft' return: pim - (class handle) Is a call to resize where you prioritize the width, like with pageimage. Additionally, after resizing, an automatic sharpening can be done with one of the three modes. ------------------------------------------------------------------- * height height($dst_height, $sharpen_mode=null) param: $dst_height - (integer) param: $auto_sharpen - (boolean) default is true was deleted with version 0.0.8, - sorry for breaking compatibility param: $sharpen_mode - (string) possible: 'none' | 'soft' | 'medium' | 'strong', default is 'soft' return: pim - (class handle) Is a call to resize where you prioritize the height, like with pageimage. Additionally, after resizing, an automatic sharpening can be done with one of the three modes. ------------------------------------------------------------------- * resize resize($dst_width=0, $dst_height=0, $sharpen_mode=null) param: $dst_width - (integer) default is 0 param: $dst_height - (integer) default is 0 param: $auto_sharpen - (boolean) default is true was deleted with version 0.0.8, - sorry for breaking compatibility param: $sharpen_mode - (string) possible: 'none' | 'soft' | 'medium' | 'strong', default is 'soft' return: pim - (class handle) Is a call to resize where you have to set width and / or height, like with pageimage size(). Additionally, after resizing, an automatic sharpening can be done with one of the three modes. ------------------------------------------------------------------- * stepResize stepResize($dst_width=0, $dst_height=0) param: $dst_width - (integer) default is 0 param: $dst_height - (integer) default is 0 return: pim - (class handle) this performs a resizing but with multiple little steps, each step followed by a soft sharpening. That way you can get better result of sharpened images. ------------------------------------------------------------------- * sharpen sharpen($mode='soft') param: $mode - (string) possible values 'none' | 'soft'| 'medium'| 'strong' return: pim - (class handle) Applys sharpening to the current memory image. You can call it with one of the three predefined pattern, or you can pass an array with your own pattern. ------------------------------------------------------------------- * unsharpMask unsharpMask($amount, $radius, $threshold) param: $amount - (integer) 0 - 500, default is 100 param: $radius - (float) 0.1 - 50, default is 0.5 param: $threshold - (integer) 0 - 255, default is 3 return: pim - (class handle) Applys sharpening to the current memory image like the equal named filter in photoshop. Credit for the used unsharp mask algorithm goes to Torstein Hønsi who has created the function back in 2003. ------------------------------------------------------------------- * smooth smooth($level=127) param: $level - (integer) 1 - 255, default is 127 return: pim - (class handle) Smooth is the opposite of sharpen. You can define how strong it should be applied, 1 is low and 255 is strong. ------------------------------------------------------------------- * blur blur() return: pim - (class handle) Blur is like smooth, but cannot called with a value. It seems to be similar like a result of smooth with a value greater than 200. ------------------------------------------------------------------- * crop crop($pos_x, $pos_y, $width, $height) param: $pos_x - (integer) start position left param: $pos_y - (integer) start position top param: $width - (integer) horizontal length of desired image part param: $height - (integer) vertical length of desired image part return: pim - (class handle) This method cut out a part of the memory image. ------------------------------------------------------------------- * canvas canvas($width, $height, $bgcolor, $position, $padding) param: $width = mixed, associative array with options or integer, - mandatory! param: $height = integer, - mandatory if $width is integer! param: $bgcolor = array with rgb or rgba, - default is array(255, 255, 255, 0) param: $position = one out of north, northwest, center, etc, - default is center param: $padding = integer as percent of canvas length, - default is 0 return: pim - (class handle) This method creates a canvas according to the given width and height and position the memory image onto it. You can pass an associative options array as the first and only param. With it you have to set width and height and optionally any other valid param. Or you have to set at least width and height as integers. Hint: If you want use transparency with rgba and your sourceImage isn't of type PNG, you have to define 'png' as outputFormat with your initially options array or, for example, like this: $image->pimLoad('prefix')->setOutputFormat('png')->canvas(300, 300, array(210,233,238,0.5), 'c', 5)->pimSave() ------------------------------------------------------------------- * flip flip($vertical=false) param: $vertical - (boolean) default is false return: pim - (class handle) This flips the image horizontal by default. (mirroring) If the boolean param is set to true, it flips the image vertical instead. ------------------------------------------------------------------- * rotate rotate($degree, $backgroundColor=127) param: $degree - (integer) valid is -360 0 360 param: $backgroundColor - (integer) valid is 0 - 255, default is 127 return: pim - (class handle) This rotates the image. Positive values for degree rotates clockwise, negative values counter clockwise. If you use other values than 90, 180, 270, the additional space gets filled with the defined background color. ------------------------------------------------------------------- * brightness brightness($level) param: $level - (integer) -255 0 255 return: pim - (class handle) You can adjust brightness by defining a value between -255 and +255. Zero lets it unchanged, negative values results in darker images and positive values in lighter images. ------------------------------------------------------------------- * contrast contrast($level) param: $level - (integer) -255 0 255 return: pim - (class handle) You can adjust contrast by defining a value between -255 and +255. Zero lets it unchanged, negative values results in lesser contrast and positive values in higher contrast. ------------------------------------------------------------------- * grayscale grayscale() return: pim - (class handle) Turns an image into grayscale. Remove all colors. ------------------------------------------------------------------- * sepia sepia() return: pim - (class handle) Turns the memory image into a colorized grayscale image with a predefined rgb-color that is known as "sepia". ------------------------------------------------------------------- * colorize colorize($anyColor) param: $anyColor - (array) like css rgb or css rgba - but with values for rgb -255 - +255, - value for alpha is float 0 - 1, 0 = transparent 1 = opaque return: pim - (class handle) Here you can adjust each of the RGB colors and optionally the alpha channel. Zero lets the channel unchanged whereas negative values results in lesser / darker parts of that channel and higher values in stronger saturisation of that channel. ------------------------------------------------------------------- * negate negate() return: pim - (class handle) Turns an image into a "negative". ------------------------------------------------------------------- * pixelate pixelate($blockSize=3) param: $blockSize - (integer) 1 - ??, default is 3 return: pim - (class handle) This apply the well known PixelLook to the memory image. It is stronger with higher values for blockSize. ------------------------------------------------------------------- * emboss emboss() return: pim - (class handle) This apply the emboss effect to the memory image. ------------------------------------------------------------------- * edgedetect edgedetect() return: pim - (class handle) This apply the edge-detect effect to the memory image. ------------------------------------------------------------------- * getMemoryImage getMemoryImage() return: memoryimage - (GD-Resource) If you want apply something that isn't available with that class, you simply can check out the current memory image and apply your image - voodoo - stuff ------------------------------------------------------------------- * setMemoryImage setMemoryImage($memoryImage) param: $memoryImage - (GD-Resource) return: pim - (class handle) If you are ready with your own image stuff, you can check in the memory image for further use with the class. ------------------------------------------------------------------- * watermarkLogo watermarkLogo($pngAlphaImage, $position='center', $padding=2) param: $pngAlphaImage - mixed [systemfilepath or PageImageObject] to/from a PNG with transparency param: $position - (string) is one out of: N, E, S, W, C, NE, SE, SW, NW, - or: north, east, south, west, center, northeast, southeast, southwest, northwest default is 'center' param: $padding - (integer) 0 - 25, default is 5, padding to the borders in percent of the images length! return: pim - (class handle) You can pass a transparent image with its filename or as a PageImage to the method. If the watermark is bigger than the destination-image, it gets shrinked to fit into the targetimage. If it is a small watermark image you can define the position of it: NW - N - NE | | | W - C - E | | | SW - S - SE The easiest and best way I have discovered to apply a big transparency watermark to an image is as follows: create a square transparent png image of e.g. 2000 x 2000 px, place your mark into the center with enough (percent) of space to the borders. You can see an example here! The $pngAlphaImage get centered and shrinked to fit into the memory image. No hassle with what width and / or height should I use?, how many space for the borders?, etc. ------------------------------------------------------------------- * watermarkLogoTiled watermarkLogoTiled($pngAlphaImage) param: $pngAlphaImage - mixed [systemfilepath or PageImageObject] to/from a PNG with transparency return: pim - (class handle) Here you have to pass a tile png with transparency (e.g. something between 150-300 px?) to your bigger images. It got repeated all over the memory image starting at the top left corner. ------------------------------------------------------------------- * watermarkText watermarkText($text, $size=10, $position='center', $padding=2, $opacity=50, $trueTypeFont=null) param: $text - (string) the text that you want to display on the image param: $size - (integer) 1 - 100, unit = points, good value seems to be around 10 to 15 param: $position - (string) is one out of: N, E, S, W, C, NE, SE, SW, NW, - or: north, east, south, west, center, northeast, southeast, southwest, northwest default is 'center' param: $padding - (integer) 0 - 25, default is 2, padding to the borders in percent of the images length! param: $opacity- (integer) 1 - 100, default is 50 param: $trueTypeFont - (string) systemfilepath to a TrueTypeFont, default is freesansbold.ttf (is GPL & comes with the module) return: pim - (class handle) Here you can display (dynamic) text with transparency over the memory image. You have to define your text, and optionally size, position, padding, opacity for it. And if you don't like the default font, freesansbold, you have to point to a TrueTypeFont-File of your choice. Please have a look to example output: http://processwire.com/talk/topic/4264-release-page-image-manipulator/page-2#entry41989 ------------------------------------------------------------------- PageImage Manipulator - Example Output
  21. 33 points
    Easily insert any complex HTML, Javascript or PHP output in your ProcessWire content by creating your own Hanna code tags. This module is based loosely on the WordPress Hana Code Insert plugin. A Hanna code tag looks like [[hello_world]]. A Hanna code tag with attributes looks like [[hello_world foo=bar" bar="foo]] using HTML style attributes or [[hello_world foo=bar, bar=foo]] using ProcessWire selector style attributes. After installing the module, you define your Hanna codes in Setup > Hanna Code. These Hanna codes that you define can then be entered within your body copy (or other text where you allow) and they will be replaced with the values defined or generated by your Hanna code. A common use case is to embed scripts or other bits of HTML or codes that would usually be stripped out by an editor like TinyMCE. However, Hanna codes can be more than just static snippets--they can be dynamic PHP or Javascript that outputs different things according to the request. PHP-based Hanna codes have access to the entire ProcessWire API. Hanna code accepts named attributes in the tag that can be passed as variables to PHP and Javascript Hanna codes. These attributes can be specified either in HTML attribute format or ProcessWire selector format. In either case, quotes should be used around the attribute value when the value contains whitespace or a comma. How to install Place the module files in /site/modules/TextformatterHannaCode/ In your admin, click Modules > Check for new modules Click install for TextformatterHannaCode Now to go Setup > Fields and locate the Textarea field(s) that you want to use Hanna codes with ("body" for instance). When editing the field, click the details tab, and select "Hanna Code" as the Textformatter. Save. Now go to Setup > Hanna Code and start defining your Hanna Codes! You may want to use one of the examples from this document to get started. Tag format Below is a Hanna code tag named hello_world with no attributes. If you pasted this into your body copy, you would get whatever the replacement value is that you defined. [[hello_world]] Below is a Hanna code tag named hello_world being passed attributes of foo, bar and foobar. If this were a PHP-based Hanna code, it would receive the variables $foo, $bar and $foobar: [[hello_world foo="bar" bar="foo" foobar="foo bar"]] Below is the same Hanna code tag as above, but with attributes more like ProcessWire selectors. You can use whatever format you prefer. Just note that unlike regular ProcessWire selectors, quotes (single or double) are required around any value that has whitespace. [[hello_world, foo=bar, bar=foo, foobar="foo bar"]] How to use Please make sure that you have completed the How to install section first. Then in your admin, go to Setup > Hanna Codes. Each Hanna code that you add has a type of either: Text/HTML, Javascript or PHP. The Text/HTML type is literally self explanatory in that your [[custom-tag]] is replaced with exactly the text you paste in. Anywhere that you type your [[custom-tag]] in your body copy will be replaced with exactly the static text you defined. More power opens up with the Javascript and/or PHP types of codes. These codes execute at runtime and thus can contain specific logic to produce different results. In fact, PHP Hanna codes have access to the entire ProcessWire API and are executed in the same manner as template files. Your PHP-based Hanna code should simply "echo" or "print" the replacement value. PHP example Create a new Hanna code with the name "children". Select "PHP" as the type. Paste in the following for the code: foreach($page->children as $child) { echo "<p><a href='$child->url'>$child->title</a>"; } Now go and edit a page that has children. In the body copy, enter [[children]] in the place where you want the output to appear. View the page, and you should see the rendered list of links to children. PHP example, part 2 Now lets take the above example further... Go back and edit your "children" Hanna code, as we are going to modify it to respond to a "parent" attribute. Change the code to this: if(isset($parent)) { // If $parent is an ID or path, lets convert it to a Page $parent = $pages->get($parent); } else { // otherwise lets assume the current page is the parent $parent = $page; } foreach($parent->children as $child) { echo "<p><a href='$child->url'>$child->title</a>"; } Go back and edit the page where you previously inserted the [[children]] tag, and change it to: [[children, parent=1]] (specifying the homepage) or [[children, parent=/path/to/some/parent/]] if you want to try something else. View the page and you should now see it showing the children of the homepage (or of another parent you specified). Please see the Javascript and PHP usage notes on the Hanna code entry screen. Security There are major security implications with a tool that will let you enter unfiltered text and code from your web browser. As a result, Hanna codes are meant for definition only by superusers and we recommend keeping it that way. Download Download the Hanna Code module from the ProcessWire modules page or from GitHub.
  22. 33 points
    Hey, The Form API has CSRF protection build in, but if you for some reason don't want to use the API you can however use the CSRF protection. Its very simple but it took some time for me to find out, so i figured i share my findings with the rest. What is CSRF? First you need to create a token and a token name you do that as following: $tokenName = $this->session->CSRF->getTokenName(); $tokenValue = $this->session->CSRF->getTokenValue(); Very simple. Now what you want to do is create a hidden input field like this: $html .= '<input type="hidden" id="_post_token" name="' . $tokenName . '" value="' . $tokenValue . '"/>'; Now this will generate something that will look like this: You are done on the form side. You can now go to the part where you are receiving the post. Then use: $session->CSRF->validate(); This will return true (1) on a valid request and an exception on a bad request. You can test this out to open up your Firebug/Chrome debug console and change the value of the textbox to something else. Basicly what this does is set a session variable with a name (getTokenName) and gives it a hashed value. If a request has a token in it it has to have the same value or it is not send from the correct form. Well I hope I helped someone.
  23. 33 points
    I just pushed ProcessWire v2.3.1 to the dev branch. This is a fairly major change in that it switches the DB driver from mysqli to PDO. It meant changes to a large percentage of core files. ProcessWire still supports mysqli, but doesn't attempt to use it unless a module or a template asks for it via the $db API variable. The new API variable (for the PDO driver) is $database. More about PDO at php.net If you are using the dev branch, be careful and test thoroughly with this latest commit to it. Before upgrading, you may want to double check that your PHP supports PDO by looking at your phpinfo (CMD-F or CTRL-F for "PDO"), especially if you are running PHP 5.2.x (where PDO wasn't compiled in by default). Though if you are running PHP 5.2 (vs 5.3.8+) then you may want to just stick with ProcessWire 2.3.0 and upgrade your PHP version when possible. If you are using any modules that use the procedural version of mysqli functions (vs. the $this->db object oriented versions), or type-hint mysqli in methods, then those modules will no longer work. If you come across any modules that don't work with 2.3.1, please let me know here so that I can assist the author in updating them. Note that FormBuilder is one of the modules that does not work with 2.3.1, but I have posted an update in the FormBuilder board that corrects it–so be sure to download that version if you are tracking the dev branch of ProcessWire and using FormBuilder. What this new version adds: 1. New API variable $database that refers to the PDO database. The old $db API variable is still there and refers to mysqli for any modules that continue to use it. 2. New API variable $log that lets you easily log messages or errors to the system logs. Usage: $log->message("This saves this line to messages.txt"); $log->error("This saves this line to to errors.txt"); $log->save("my-log", "This saves this line to my-log.txt"); // Get an array of the last few entries saved to the messages log $entries = $log->get('messages'); // Get an array of the last 50 entries saved to my-log $entries = $log->get('my-log', 50); Note that as always, log files are located in /site/assets/logs/. 3. Conditional autoload modules. In PHP 5.3+, modules may now specify an anonymous function OR a selector string, rather than a boolean for the 'autoload' property returned by getModuleInfo(). PW runs the anonymous function after determining the current $page, so your module can make autoload decisions based on the $page (or any other factor you'd like), if desired. Lets say that we have a module that we only want to autoload when the template is 'admin': public static function getModuleInfo() { return array( 'title' => 'Module Title', 'summary' => 'Summary text...', 'version' => 1, 'autoload' => function() { if(wire('page')->template == 'admin') return true; else return false; }); } And the same example but using a selector for autoload: public static function getModuleInfo() { return array( 'title' => 'Module Title', 'summary' => 'Summary text...', 'version' => 1, 'autoload' => 'template=admin' ); } 4. Addition of $pages->add() method. Actually $pages->add($template, $parent, [string $name], [array $values]); This function adds a new page to the database and returns it. This is for syntax convenience, but using the old method is still perfectly fine too. Here's a few examples of usage: // add a new page using template basic-page under /about/ $newpage = $pages->add('basic-page', '/about/'); // same as above, but named 'contact' $newpage = $pages->add('basic-page', '/about/', 'contact'); // same, but populate the title field too $newpage = $pages->add('basic-page', '/about/', 'contact', array('title' => 'Contact Us')); // you can also do this, specifying the values array as 3rd argument: $newpage = $pages->add('basic-page', '/about/', array('title' => 'Contact Us')); $template and $parent are required, but may be objects, IDs, or string identifiers (like name for template, or path for page). When you add a new page and don't specify a 'name', then PW will make one up, guaranteed to be unique. 5. Module files that end in '.module.php' are now supported. So rather than ClassName.module, you may use ClassName.module.php if you prefer it. The purpose here is to support text editors that determine their syntax highlighting based on the file extension. More updates being made almost daily. Please report any issues you experience. Thanks, Ryan
  24. 33 points
    Continuing from my previous post in this thread about some selector enhancements available on the dev branch, we've got a couple more advanced options for use in selectors in case anyone is interested: OR-groups These let you specify multiple expressions and only one of them has to match in order for the selector to match. It's a way of saying "either this has to match OR that has to match". This is useful because selectors always assumed AND – meaning everything has to match. While you have always been able to use the pipe "|" to specify ORs for fields or values or both, the scope of it was just that field=value statement only. Now we have something new called OR-groups. These let you create multiple selector groups and only one of them has to match. You can specify OR-groups by surrounding selectors in parenthesis. An example demonstrates it best. Lets say that we wanted to find all "product" pages that were in stock, and either in a featured date range, or had a highlighted checkbox checked. Previously we would do like this with two separate find operations: $items = $pages->find("template=product, stock>0, featured_from<=today, featured_to>=today"); $items->add($pages->find("template=product, stock>0, highlighted=1")); Now we can do it in one find operation: $items = $pages->find("template=product, stock>0, (featured_from<=today, featured_to>=today), (highlighted=1)"); Above are two selectors surrounded in parenthesis. Only one of them has to match. You can specify as many of them as you want. This type of OR expression is something you couldn't previously do with selectors. Think of the parenthesis as a way of saying "this is optional". But of course, at least one of your parenthesized selectors has to match in order for the full selector to match. I'm guessing the above usage probably covers 99% of the situations where you might need it. But lets say that you want to have different combinations of OR expressions. You can create named groups that OR with each-other by specifying: foo=(selector1), bar=(selector2), foo=(selector3), bar=(selector4) In the above you'd replace "foo" and "bar" with names of your choice. And you'd replace the "selector" with any selector strings. Those foo/bar names aren't referring to fields, instead they are just named groups that you can name however you want. In that selector, at least one of the "foo" named selectors would have to match, and at least one of the "bar" named selectors would have to match. If you didn't use the foo/bar named groups here (but still used the parenthesis), then only one of the 4 selectors would be required to match. Sub-selectors Some of you are already familiar with these because it was committed to the dev branch a couple weeks ago (and I think may have been outlined elsewhere in the forums). Sub-selectors let you put a selector within a selector, enabling you to perform more complex matches that used to require you to use separate API calls. These can be used on the 'id' property of any field that maps to a page. The 'id' property is assumed when referring to a page reference or a parent, so it's not necessary to specify it unless you want to, i.e. "field" and "field.id" mean the same thing in this case. Sub-selectors are specified between [square brackets]. For example, lets say we are matching products and our product template has a "company" page field. Each company also has it's own page field where all the company locations are identified. Lets say we want to find all products that are made by a company that has more than 5 locations and at least one of those locations has "Finland" in the title. Previously we would have had to do it like this: $companies = $pages->find("template=company, locations>5, locations.title%=Finland"); $items = $pages->find("template=product, company=$companies"); That's easy enough. But now it's even simpler, as you can do it in one operation: $items = $pages->find("template=product, company=[locations>5, locations.title%=Finland]"); When you've got a "field=[value]" selector, any properties you refer to in "[value]" assume the "field", so "locations" above is referring to a property of the "company" field.
  25. 33 points
    ProcessWire Site Profile Using Zurb Foundation 4 This is a drop-in replacement for the default ProcessWire site profile. See the live demo at: http://processwire.com/foundation/ It is mobile-first and fully responsive, capturing all the benefits of Zurb Foundation 4. Ready to be expanded upon with built-in support for deeper levels of navigation nesting in the sidebar. Pagination ready with Foundation-specific pagination output, when/if you want it. Improved search engine, relative to the basic profile. Library of Foundation-specific markup generation functions included, primary for generation of navigation (in _nav.php). Uses ProcessWire 2.3+ prepend/append template file settings making it easy to work with. It is largely stock Foundation 4 in terms of look and feel, with a few tweaks. To Install Download this profile from GitHub or mods.pw/4u. Start with a copy of ProcessWire 2.3 or newer, and its default site profile. If starting with an uninstalled copy of ProcessWire Replace the /site-default/templates/ directory with the templates directory from this profile. Replace the /site-default/config.php file with the config.php file from this profile. Run the ProcessWire installer. If starting with an already-installed copy of ProcessWire Replace the /site/templates/ directory with the templates directory from this profile. Add the following two lines to your /site/config.php file: $config->prependTemplateFile = '_init.php'; $config->appendTemplateFile = '_main.php'; Desktop Screenshot Mobile Screenshot
  • From Twitter

    • New post: This week we take a look at what’s in ProcessWire 3.0.126 which focuses largely on resolving issue reports, but also includes a handy new Page if() method— processwire.com/blog/posts/pw-…
      15 February 2019
    • New post: ProcessWire 3.0.125 has several useful new Sanitizer methods & options, plus new ways to access them directly from the Input API variable. This makes handling user input even easier than before. Plus updates to our i18n functions & API docs— processwire.com/blog/posts/pw-…
      25 January 2019
    • New post: In this week’s post, we’ll take a look a look at the new website and focus on some parts of it and how they were built. Then we’ll dive into the latest version of ProcessWire on the dev branch, version 3.0.124— processwire.com/blog/posts/pw-…
      11 January 2019